Merge branch 'temp38' of github.com:payloadcms/payload into temp38

This commit is contained in:
James
2024-04-01 15:29:43 -04:00
29 changed files with 810 additions and 511 deletions

View File

@@ -226,8 +226,8 @@ jobs:
- fields/lexical
- live-preview
- localization
# - plugin-nested-docs
# - plugin-seo
- plugin-nested-docs
- plugin-seo
# - refresh-permissions
# - uploads
- versions

View File

@@ -1,11 +1,32 @@
import baseConfig from '../../jest.config.js'
// import baseConfig from '../../jest.config.js'
/** @type {import('@jest/types').Config} */
// /** @type {import('@jest/types').Config} */
// const customJestConfig = {
// ...baseConfig,
// setupFilesAfterEnv: null,
// testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
// testTimeout: 20000,
// }
// export default customJestConfig
/** @type {import('jest').Config} */
const customJestConfig = {
...baseConfig,
setupFilesAfterEnv: null,
testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
testTimeout: 20000,
extensionsToTreatAsEsm: ['.ts', '.tsx'],
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'\\.(css|scss)$': '<rootDir>/helpers/mocks/emptyModule.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/test/helpers/mocks/fileMock.js',
'^(\\.{1,2}/.*)\\.js$': '$1',
},
testEnvironment: 'node',
testMatch: ['<rootDir>/**/*spec.ts'],
testTimeout: 90000,
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest'],
},
verbose: true,
}
export default customJestConfig

View File

@@ -7,9 +7,9 @@
"create-payload-app": "bin/cli.js"
},
"scripts": {
"build": "pnpm copyfiles && pnpm typecheck && pnpm build:swc",
"build": "pnpm pack-template-files && pnpm typecheck && pnpm build:swc",
"typecheck": "tsc",
"copyfiles": "copyfiles -u 2 \"../../app/(payload)/**\" \"dist\"",
"pack-template-files": "tsx src/scripts/pack-template-files.ts",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"clean": "rimraf {dist,*.tsbuildinfo}",
"test": "jest",
@@ -21,6 +21,7 @@
"bin"
],
"dependencies": {
"@clack/prompts": "^0.7.0",
"@sindresorhus/slugify": "^1.1.0",
"arg": "^5.0.0",
"chalk": "^4.1.0",
@@ -33,9 +34,6 @@
"figures": "^3.2.0",
"fs-extra": "^9.0.1",
"globby": "11.1.0",
"handlebars": "^4.7.7",
"ora": "^5.1.0",
"prompts": "^2.4.2",
"terminal-link": "^2.1.1"
},
"devDependencies": {
@@ -44,8 +42,7 @@
"@types/esprima": "^4.0.6",
"@types/fs-extra": "^9.0.12",
"@types/jest": "^27.0.3",
"@types/node": "^16.6.2",
"@types/prompts": "^2.4.1"
"@types/node": "^16.6.2"
},
"exports": {
"./commands": {

View File

@@ -9,19 +9,27 @@ import { dbReplacements } from './packages.js'
/** Update payload config with necessary imports and adapters */
export async function configurePayloadConfig(args: {
dbDetails: DbDetails | undefined
projectDir: string
projectDirOrConfigPath: { payloadConfigPath: string } | { projectDir: string }
}): Promise<void> {
if (!args.dbDetails) {
return
}
try {
const payloadConfigPath = (
await globby('**/payload.config.ts', { absolute: true, cwd: args.projectDir })
)?.[0]
let payloadConfigPath: string | undefined
if (!('payloadConfigPath' in args.projectDirOrConfigPath)) {
payloadConfigPath = (
await globby('**/payload.config.ts', {
absolute: true,
cwd: args.projectDirOrConfigPath.projectDir,
})
)?.[0]
} else {
payloadConfigPath = args.projectDirOrConfigPath.payloadConfigPath
}
if (!payloadConfigPath) {
warning('Unable to update payload.config.ts with plugins')
warning('Unable to update payload.config.ts with plugins. Could not find payload.config.ts.')
return
}
@@ -59,6 +67,8 @@ export async function configurePayloadConfig(args: {
fse.writeFileSync(payloadConfigPath, configLines.join('\n'))
} catch (err: unknown) {
warning('Unable to update payload.config.ts with plugins')
warning(
`Unable to update payload.config.ts with plugins: ${err instanceof Error ? err.message : ''}`,
)
}
}

View File

@@ -5,6 +5,7 @@ import { createProject } from './create-project.js'
import { fileURLToPath } from 'node:url'
import { dbReplacements } from './packages.js'
import { getValidTemplates } from './templates.js'
import globby from 'globby'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -104,12 +105,17 @@ describe('createProject', () => {
Object.keys(packageJson.dependencies).filter((n) => n.startsWith('@payloadcms/db-')),
).toHaveLength(1)
let payloadConfigPath = path.resolve(projectDir, 'payload.config.ts')
const payloadConfigPath = (
await globby('**/payload.config.ts', {
absolute: true,
cwd: projectDir,
})
)?.[0]
// Website and ecommerce templates have payload.config.ts in src/payload
if (!fse.existsSync(payloadConfigPath)) {
payloadConfigPath = path.resolve(projectDir, 'src/payload/payload.config.ts')
if (!payloadConfigPath) {
throw new Error(`Could not find payload.config.ts inside ${projectDir}`)
}
const content = fse.readFileSync(payloadConfigPath, 'utf-8')
// Check payload.config.ts

View File

@@ -1,14 +1,14 @@
import * as p from '@clack/prompts'
import chalk from 'chalk'
import degit from 'degit'
import execa from 'execa'
import fse from 'fs-extra'
import { fileURLToPath } from 'node:url'
import ora from 'ora'
import path from 'path'
import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../types.js'
import { debug, error, success, warning } from '../utils/log.js'
import { debug, error, warning } from '../utils/log.js'
import { configurePayloadConfig } from './configure-payload-config.js'
const filename = fileURLToPath(import.meta.url)
@@ -60,14 +60,12 @@ export async function createProject(args: {
const { cliArgs, dbDetails, packageManager, projectDir, projectName, template } = args
if (cliArgs['--dry-run']) {
console.log(`\n Dry run: Creating project in ${chalk.green(projectDir)}\n`)
debug(`Dry run: Creating project in ${chalk.green(projectDir)}`)
return
}
await createOrFindProjectDir(projectDir)
console.log(`\n Creating project in ${chalk.green(projectDir)}\n`)
if (cliArgs['--local-template']) {
// Copy template from local path. For development purposes.
const localTemplate = path.resolve(
@@ -86,10 +84,12 @@ export async function createProject(args: {
await emitter.clone(projectDir)
}
const spinner = ora('Checking latest Payload version...').start()
const spinner = p.spinner()
spinner.start('Checking latest Payload version...')
await updatePackageJSON({ projectDir, projectName })
await configurePayloadConfig({ dbDetails, projectDir })
spinner.message('Configuring Payload...')
await configurePayloadConfig({ dbDetails, projectDirOrConfigPath: { projectDir } })
// Remove yarn.lock file. This is only desired in Payload Cloud.
const lockPath = path.resolve(projectDir, 'yarn.lock')
@@ -98,18 +98,16 @@ export async function createProject(args: {
}
if (!cliArgs['--no-deps']) {
spinner.text = 'Installing dependencies...'
spinner.message('Installing dependencies...')
const result = await installDeps({ cliArgs, packageManager, projectDir })
spinner.stop()
spinner.clear()
if (result) {
success('Dependencies installed')
spinner.stop('Dependencies installed')
} else {
error('Error installing dependencies')
spinner.stop('Error installing dependencies', 1)
}
} else {
spinner.stop()
spinner.clear()
spinner.stop('Dependency installation skipped')
}
}
@@ -124,6 +122,6 @@ export async function updatePackageJSON(args: {
packageObj.name = projectName
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) {
warning('Unable to update name in package.json')
warning(`Unable to update name in package.json. ${err instanceof Error ? err.message : ''}`)
}
}

View File

@@ -1,13 +1,13 @@
import type { CompilerOptions } from 'typescript'
import chalk from 'chalk'
import * as p from '@clack/prompts'
import { parse, stringify } from 'comment-json'
import execa from 'execa'
import fs from 'fs'
import fse from 'fs-extra'
import globby from 'globby'
import path from 'path'
import { promisify } from 'util'
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
@@ -16,58 +16,94 @@ const dirname = path.dirname(filename)
import { fileURLToPath } from 'node:url'
import type { CliArgs, PackageManager } from '../types.js'
import type { CliArgs, DbType, PackageManager } from '../types.js'
import { copyRecursiveSync } from '../utils/copy-recursive-sync.js'
import { error, info, debug as origDebug, success, warning } from '../utils/log.js'
import { debug as origDebug, warning } from '../utils/log.js'
import { moveMessage } from '../utils/messages.js'
import { wrapNextConfig } from './wrap-next-config.js'
type InitNextArgs = Pick<CliArgs, '--debug'> & {
dbType: DbType
nextConfigPath: string
packageManager: PackageManager
projectDir?: string
projectDir: string
useDistFiles?: boolean
}
type InitNextResult = { reason?: string; success: boolean; userAppDir?: string }
type InitNextResult =
| {
isSrcDir: boolean
nextAppDir: string
payloadConfigPath: string
success: true
}
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const { packageManager, projectDir } = args
const templateResult = await applyPayloadTemplateFiles(args)
if (!templateResult.success) return templateResult
const { dbType: dbType, packageManager, projectDir } = args
const { success: installSuccess } = await installDeps(projectDir, packageManager)
if (!installSuccess) {
return { ...templateResult, reason: 'Failed to install dependencies', success: false }
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
// Get app directory. Could be top-level or src/app
const nextAppDir = (
await globby(['**/app'], {
absolute: true,
cwd: projectDir,
onlyDirectories: true,
})
)?.[0]
if (!nextAppDir) {
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
}
// Create or find payload.config.ts
const createConfigResult = findOrCreatePayloadConfig(projectDir)
if (!createConfigResult.success) {
return { ...templateResult, ...createConfigResult }
// Check for top-level layout.tsx
const layoutPath = path.resolve(nextAppDir, 'layout.tsx')
if (fs.existsSync(layoutPath)) {
// Output directions for user to move all files from app to top-level directory named `(app)`
p.log.warn(moveMessage({ nextAppDir, projectDir }))
return {
isSrcDir,
nextAppDir,
reason: 'Found existing layout.tsx in app directory',
success: false,
}
}
const installSpinner = p.spinner()
installSpinner.start('Installing Payload and dependencies...')
const configurationResult = installAndConfigurePayload({
...args,
isSrcDir,
nextAppDir,
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
})
if (configurationResult.success === false) {
installSpinner.stop(configurationResult.reason, 1)
return { ...configurationResult, isSrcDir, success: false }
}
const { success: installSuccess } = await installDeps(projectDir, packageManager, dbType)
if (!installSuccess) {
installSpinner.stop('Failed to install dependencies', 1)
return {
...configurationResult,
isSrcDir,
reason: 'Failed to install dependencies',
success: false,
}
}
// Add `@payload-config` to tsconfig.json `paths`
await addPayloadConfigToTsConfig(projectDir)
// Output directions for user to update next.config.js
const withPayloadMessage = `
${chalk.bold(`Wrap your existing next.config.js with the withPayload function. Here is an example:`)}
import withPayload from '@payloadcms/next/withPayload'
const nextConfig = {
// Your Next.js config here
}
export default withPayload(nextConfig)
`
console.log(withPayloadMessage)
return templateResult
await addPayloadConfigToTsConfig(projectDir, isSrcDir)
installSpinner.stop('Successfully installed Payload and dependencies')
return { ...configurationResult, isSrcDir, nextAppDir, success: true }
}
async function addPayloadConfigToTsConfig(projectDir: string) {
async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean) {
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
const userTsConfigContent = await readFile(tsConfigPath, {
encoding: 'utf8',
@@ -82,48 +118,39 @@ async function addPayloadConfigToTsConfig(projectDir: string) {
if (!userTsConfig.compilerOptions.paths?.['@payload-config']) {
userTsConfig.compilerOptions.paths = {
...(userTsConfig.compilerOptions.paths || {}),
'@payload-config': ['./payload.config.ts'],
'@payload-config': [`./${isSrcDir ? 'src/' : ''}payload.config.ts`],
}
await writeFile(tsConfigPath, stringify(userTsConfig, null, 2), { encoding: 'utf8' })
}
}
async function applyPayloadTemplateFiles(args: InitNextArgs): Promise<InitNextResult> {
const { '--debug': debug, projectDir, useDistFiles } = args
info('Initializing Payload app in Next.js project', 1)
function installAndConfigurePayload(
args: InitNextArgs & {
isSrcDir: boolean
nextAppDir: string
},
):
| { payloadConfigPath: string; success: true }
| { payloadConfigPath?: string; reason: string; success: false } {
const { '--debug': debug, isSrcDir, nextAppDir, nextConfigPath, projectDir, useDistFiles } = args
const logDebug = (message: string) => {
if (debug) origDebug(message)
}
if (!fs.existsSync(projectDir)) {
return { reason: `Could not find specified project directory at ${projectDir}`, success: false }
}
// Next.js configs can be next.config.js, next.config.mjs, etc.
const foundConfig = (await globby('next.config.*js', { absolute: true, cwd: projectDir }))?.[0]
if (!foundConfig) {
throw new Error(`No next.config.js found at ${projectDir}`)
}
const nextConfigPath = path.resolve(projectDir, foundConfig)
if (!fs.existsSync(nextConfigPath)) {
return {
reason: `No next.config.js found at ${nextConfigPath}. Ensure you are in a Next.js project directory.`,
reason: `Could not find specified project directory at ${projectDir}`,
success: false,
}
} else {
if (debug) logDebug(`Found Next config at ${nextConfigPath}`)
}
const templateFilesPath =
dirname.endsWith('dist') || useDistFiles
? path.resolve(dirname, '../..', 'dist/app')
: path.resolve(dirname, '../../../../app')
? path.resolve(dirname, '../..', 'dist/template')
: path.resolve(dirname, '../../../../templates/blank-3.0')
if (debug) logDebug(`Using template files from: ${templateFilesPath}`)
logDebug(`Using template files from: ${templateFilesPath}`)
if (!fs.existsSync(templateFilesPath)) {
return {
@@ -131,38 +158,41 @@ async function applyPayloadTemplateFiles(args: InitNextArgs): Promise<InitNextRe
success: false,
}
} else {
if (debug) logDebug('Found template source files')
logDebug('Found template source files')
}
// src/app or app
const userAppDir = (
await globby(['**/app'], {
absolute: true,
cwd: projectDir,
onlyDirectories: true,
})
)?.[0]
logDebug(`Copying template files from ${templateFilesPath} to ${nextAppDir}`)
if (!fs.existsSync(userAppDir)) {
return { reason: `Could not find user app directory inside ${projectDir}`, success: false }
} else {
logDebug(`Found user app directory: ${userAppDir}`)
const templateSrcDir = path.resolve(templateFilesPath, isSrcDir ? '' : 'src')
// const templateSrcDir = path.resolve(templateFilesPath)
logDebug(`templateSrcDir: ${templateSrcDir}`)
logDebug(`nextAppDir: ${nextAppDir}`)
logDebug(`projectDir: ${projectDir}`)
logDebug(`nextConfigPath: ${nextConfigPath}`)
logDebug(
`isSrcDir: ${isSrcDir}. source: ${templateSrcDir}. dest: ${path.dirname(nextConfigPath)}`,
)
// This is a little clunky and needs to account for isSrcDir
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
// Wrap next.config.js with withPayload
wrapNextConfig({ nextConfigPath })
return {
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
success: true,
}
logDebug(`Copying template files from ${templateFilesPath} to ${userAppDir}`)
copyRecursiveSync(templateFilesPath, userAppDir, debug)
success('Successfully initialized.')
return { success: true, userAppDir }
}
async function installDeps(projectDir: string, packageManager: PackageManager) {
info(`Installing dependencies with ${packageManager}`, 1)
const packagesToInstall = [
'payload',
'@payloadcms/db-mongodb',
'@payloadcms/next',
'@payloadcms/richtext-lexical',
].map((pkg) => `${pkg}@alpha`)
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
(pkg) => `${pkg}@alpha`,
)
packagesToInstall.push(`@payloadcms/db-${dbType}@alpha`)
let exitCode = 0
switch (packageManager) {
@@ -186,43 +216,5 @@ async function installDeps(projectDir: string, packageManager: PackageManager) {
}
}
if (exitCode !== 0) {
error(`Failed to install dependencies with ${packageManager}`)
} else {
success(`Successfully installed dependencies`)
}
return { success: exitCode === 0 }
}
function findOrCreatePayloadConfig(projectDir: string) {
const configPath = path.resolve(projectDir, 'payload.config.ts')
if (fs.existsSync(configPath)) {
return { message: 'Found existing payload.config.ts', success: true }
} else {
// Create default config
// TODO: Pull this from templates
const defaultConfig = `import path from "path";
import { mongooseAdapter } from "@payloadcms/db-mongodb"; // database-adapter-import
import { lexicalEditor } from "@payloadcms/richtext-lexical"; // editor-import
import { buildConfig } from "payload/config";
export default buildConfig({
editor: slateEditor({}), // editor-config
collections: [],
secret: "asdfasdf",
typescript: {
outputFile: path.resolve(__dirname, "payload-types.ts"),
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"),
},
db: mongooseAdapter({
url: "mongodb://localhost:27017/next-payload-3",
}),
});
`
fse.writeFileSync(configPath, defaultConfig)
return { message: 'Created default payload.config.ts', success: true }
}
}

View File

@@ -1,25 +1,19 @@
import prompts from 'prompts'
import * as p from '@clack/prompts'
import type { CliArgs } from '../types.js'
export async function parseProjectName(args: CliArgs): Promise<string> {
if (args['--init-next']) return '.'
if (args['--name']) return args['--name']
if (args._[0]) return args._[0]
const response = await prompts(
{
name: 'value',
type: 'text',
message: 'Project name?',
validate: (value: string) => !!value.length,
const projectName = await p.text({
message: 'Project name?',
validate: (value) => {
if (!value) return 'Please enter a project name.'
},
{
onCancel: () => {
process.exit(0)
},
},
)
return response.value
})
if (p.isCancel(projectName)) {
process.exit(0)
}
return projectName
}

View File

@@ -1,11 +1,11 @@
import prompts from 'prompts'
import * as p from '@clack/prompts'
import type { CliArgs, ProjectTemplate } from '../types.js'
export async function parseTemplate(
args: CliArgs,
validTemplates: ProjectTemplate[],
): Promise<ProjectTemplate> {
): Promise<ProjectTemplate | undefined> {
if (args['--template']) {
const templateName = args['--template']
const template = validTemplates.find((t) => t.name === templateName)
@@ -13,29 +13,20 @@ export async function parseTemplate(
return template
}
const response = await prompts(
{
name: 'value',
type: 'select',
choices: validTemplates.map((p) => {
return {
description: p.description,
title: p.name,
value: p.name,
}
}),
message: 'Choose project template',
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
const response = await p.select<{ label: string; value: string }[], string>({
message: 'Choose project template',
options: validTemplates.map((p) => {
return {
label: p.name,
value: p.name,
}
}),
})
if (p.isCancel(response)) {
process.exit(0)
}
const template = validTemplates.find((t) => t.name === response.value)
if (!template) throw new Error('Template is undefined')
const template = validTemplates.find((t) => t.name === response)
return template
}

View File

@@ -1,5 +1,5 @@
import * as p from '@clack/prompts'
import slugify from '@sindresorhus/slugify'
import prompts from 'prompts'
import type { CliArgs, DbDetails, DbType } from '../types.js'
@@ -23,7 +23,7 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
}
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
let dbType: DbType | undefined = undefined
let dbType: DbType | symbol | undefined = undefined
if (args['--db']) {
if (!Object.values(dbChoiceRecord).some((dbChoice) => dbChoice.value === args['--db'])) {
throw new Error(
@@ -34,31 +34,20 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
}
dbType = args['--db'] as DbType
} else {
const dbTypeRes = await prompts(
{
name: 'value',
type: 'select',
choices: Object.values(dbChoiceRecord).map((dbChoice) => {
return {
title: dbChoice.title,
value: dbChoice.value,
}
}),
message: 'Select a database',
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
dbType = dbTypeRes.value
dbType = await p.select<{ label: string; value: DbType }[], DbType>({
initialValue: 'mongodb',
message: `Select a database`,
options: [
{ label: 'MongoDB', value: 'mongodb' },
{ label: 'Postgres', value: 'postgres' },
],
})
if (p.isCancel(dbType)) process.exit(0)
}
const dbChoice = dbChoiceRecord[dbType]
let dbUri: string | undefined = undefined
let dbUri: string | symbol | undefined = undefined
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
}`
@@ -68,21 +57,11 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
} else if (args['--db-connection-string']) {
dbUri = args['--db-connection-string']
} else {
const dbUriRes = await prompts(
{
name: 'value',
type: 'text',
initial: initialDbUri,
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
dbUri = dbUriRes.value
dbUri = await p.text({
initialValue: initialDbUri,
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
})
if (p.isCancel(dbUri)) process.exit(0)
}
return {

View File

@@ -1,4 +1,5 @@
import { parseAndInsertWithPayload, withPayloadImportStatement } from './wrap-next-config.js'
import { parseAndModifyConfigContent, withPayloadImportStatement } from './wrap-next-config.js'
import * as p from '@clack/prompts'
const defaultNextConfig = `/** @type {import('next').NextConfig} */
const nextConfig = {};
@@ -30,25 +31,31 @@ export { wrapped as default }
describe('parseAndInsertWithPayload', () => {
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndInsertWithPayload(defaultNextConfig)
const { modifiedConfigContent } = parseAndModifyConfigContent(defaultNextConfig)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndInsertWithPayload(nextConfigWithFunc)
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFunc)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndInsertWithPayload(nextConfigWithFuncMultiline)
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFuncMultiline)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const { modifiedConfigContent, error } = parseAndInsertWithPayload(nextConfigExportNamedDefault)
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
nextConfigExportNamedDefault,
)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(error).toBeTruthy()
expect(success).toBe(false)
expect(warnLogSpy).toHaveBeenCalledWith(expect.stringContaining('Could not automatically wrap'))
})
})

View File

@@ -1,28 +1,30 @@
import chalk from 'chalk'
import { parseModule } from 'esprima'
import fs from 'fs'
import globby from 'globby'
import path from 'path'
import { warning } from '../utils/log.js'
import { log } from '../utils/log.js'
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
export const wrapNextConfig = async (args: { projectDir: string }): Promise<void> => {
const foundConfig = (await globby('next.config.*js', { cwd: args.projectDir }))?.[0]
export const wrapNextConfig = (args: { nextConfigPath: string }) => {
const { nextConfigPath } = args
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent)
if (!foundConfig) {
throw new Error(`No Next config found at ${args.projectDir}`)
if (!success) {
return
}
const configPath = path.resolve(args.projectDir, foundConfig)
const configContent = fs.readFileSync(configPath, 'utf8')
const { error, modifiedConfigContent: newConfig } = parseAndInsertWithPayload(configContent)
if (error) {
console.warn(error)
}
fs.writeFileSync(configPath, newConfig)
fs.writeFileSync(nextConfigPath, newConfig)
}
export function parseAndInsertWithPayload(content: string): {
error?: string
/**
* Parses config content with AST and wraps it with withPayload function
*/
export function parseAndModifyConfigContent(content: string): {
modifiedConfigContent: string
success: boolean
} {
content = withPayloadImportStatement + content
const ast = parseModule(content, { loc: true })
@@ -43,7 +45,7 @@ export function parseAndInsertWithPayload(content: string): {
content,
exportDefaultDeclaration.declaration?.loc,
)
return { modifiedConfigContent }
return { modifiedConfigContent, success: true }
} else if (exportNamedDeclaration) {
const exportSpecifier = exportNamedDeclaration.specifiers.find(
(s) =>
@@ -54,18 +56,44 @@ export function parseAndInsertWithPayload(content: string): {
)
if (exportSpecifier) {
// TODO: Improve with this example and/or link to docs
warning('Could not automatically wrap next.config.js with withPayload.')
warning('Automatic wrapping of named exports as default not supported yet.')
warnUserWrapNotSuccessful()
return {
error: `Automatic wrapping of named exports as default not supported yet.
Please manually wrap your Next config with the withPayload function`,
modifiedConfigContent: content,
success: false,
}
}
} else {
throw new Error('Could not automatically wrap next.config.js with withPayload')
warning('Could not automatically wrap next.config.js with withPayload.')
warnUserWrapNotSuccessful()
return {
modifiedConfigContent: content,
success: false,
}
}
}
function warnUserWrapNotSuccessful() {
// Output directions for user to update next.config.js
const withPayloadMessage = `
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
import withPayload from '@payloadcms/next/withPayload'
const nextConfig = {
// Your Next.js config here
}
export default withPayload(nextConfig)
`
log(withPayloadMessage)
}
type Directive = {
declaration?: {
loc: Loc

View File

@@ -1,10 +1,9 @@
import chalk from 'chalk'
import fs from 'fs-extra'
import path from 'path'
import type { CliArgs, ProjectTemplate } from '../types.js'
import { error, success } from '../utils/log.js'
import { debug, error } from '../utils/log.js'
/** Parse and swap .env.example values and write .env */
export async function writeEnvFile(args: {
@@ -17,7 +16,7 @@ export async function writeEnvFile(args: {
const { cliArgs, databaseUri, payloadSecret, projectDir, template } = args
if (cliArgs['--dry-run']) {
success(`DRY RUN: .env file created`)
debug(`DRY RUN: .env file created`)
return
}
@@ -51,8 +50,6 @@ export async function writeEnvFile(args: {
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
await fs.outputFile(`${projectDir}/.env`, content)
}
success('.env file created')
} catch (err: unknown) {
error('Unable to write .env file')
if (err instanceof Error) {

View File

@@ -1,11 +1,14 @@
/* eslint-disable no-console */
import * as p from '@clack/prompts'
import slugify from '@sindresorhus/slugify'
import arg from 'arg'
import chalk from 'chalk'
import { detect } from 'detect-package-manager'
import globby from 'globby'
import path from 'path'
import type { CliArgs, PackageManager } from './types.js'
import { configurePayloadConfig } from './lib/configure-payload-config.js'
import { createProject } from './lib/create-project.js'
import { generateSecret } from './lib/generate-secret.js'
import { initNext } from './lib/init-next.js'
@@ -14,8 +17,8 @@ import { parseTemplate } from './lib/parse-template.js'
import { selectDb } from './lib/select-db.js'
import { getValidTemplates, validateTemplate } from './lib/templates.js'
import { writeEnvFile } from './lib/write-env-file.js'
import { error, success } from './utils/log.js'
import { helpMessage, successMessage, welcomeMessage } from './utils/messages.js'
import { error, info } from './utils/log.js'
import { feedbackOutro, helpMessage, successMessage, successfulNextInit } from './utils/messages.js'
export class Main {
args: CliArgs
@@ -35,7 +38,7 @@ export class Main {
'--template-branch': String,
// Next.js
'--init-next': Boolean,
'--init-next': Boolean, // TODO: Is this needed if we detect if inside Next.js project?
// Package manager
'--no-deps': Boolean,
@@ -59,44 +62,92 @@ export class Main {
}
async init(): Promise<void> {
const initContext: {
nextConfigPath: string | undefined
} = {
nextConfigPath: undefined,
}
try {
if (this.args['--help']) {
console.log(helpMessage())
helpMessage()
process.exit(0)
}
const projectName = await parseProjectName(this.args)
const projectDir = path.resolve(
projectName === '.' || this.args['--init-next']
? path.basename(process.cwd())
: `./${slugify(projectName)}`,
)
// eslint-disable-next-line no-console
console.log('\n')
p.intro(chalk.bgCyan(chalk.black(' create-payload-app ')))
p.log.message("Welcome to Payload. Let's create a project!")
// Detect if inside Next.js projeckpt
const nextConfigPath = (
await globby('next.config.*js', { absolute: true, cwd: process.cwd() })
)?.[0]
initContext.nextConfigPath = nextConfigPath
if (initContext.nextConfigPath) {
this.args['--name'] = slugify(path.basename(path.dirname(initContext.nextConfigPath)))
}
const projectName = await parseProjectName(this.args)
const projectDir = nextConfigPath
? path.dirname(nextConfigPath)
: path.resolve(process.cwd(), slugify(projectName))
console.log(welcomeMessage)
const packageManager = await getPackageManager(this.args, projectDir)
if (this.args['--init-next']) {
const result = await initNext({ ...this.args, packageManager })
if (!result.success) {
error(result.reason || 'Failed to initialize Payload app in Next.js project')
} else {
success('Payload app successfully initialized in Next.js project')
if (nextConfigPath) {
// p.note('Detected existing Next.js project.')
p.log.step(chalk.bold('Detected existing Next.js project.'))
const proceed = await p.confirm({
initialValue: true,
message: 'Install Payload in this project?',
})
if (p.isCancel(proceed) || !proceed) {
process.exit(0)
}
process.exit(result.success ? 0 : 1)
// TODO: This should continue the normal prompt flow
const dbDetails = await selectDb(this.args, projectName)
const result = await initNext({
...this.args,
dbType: dbDetails.type,
nextConfigPath,
packageManager,
projectDir,
})
if (result.success === false) {
process.exit(1)
}
await configurePayloadConfig({
dbDetails,
projectDirOrConfigPath: {
payloadConfigPath: result.payloadConfigPath,
},
})
info('Payload project successfully created!')
p.note(successfulNextInit(), chalk.bgGreen(chalk.black(' Documentation ')))
p.outro(feedbackOutro())
return
}
const templateArg = this.args['--template']
if (templateArg) {
const valid = validateTemplate(templateArg)
if (!valid) {
console.log(helpMessage())
helpMessage()
process.exit(1)
}
}
const validTemplates = getValidTemplates()
const template = await parseTemplate(this.args, validTemplates)
if (!template) {
p.log.error('Invalid template given')
process.exit(1)
}
switch (template.type) {
case 'starter': {
@@ -131,10 +182,11 @@ export class Main {
}
}
success('Payload project successfully created')
console.log(successMessage(projectDir, packageManager))
} catch (error: unknown) {
console.log(error)
info('Payload project successfully created!')
p.note(successMessage(projectDir, packageManager), chalk.bgGreen(chalk.black(' Next Steps ')))
p.outro(feedbackOutro())
} catch (err: unknown) {
error(err instanceof Error ? err.message : 'An error occurred')
}
}
}

View File

@@ -0,0 +1,35 @@
import fs from 'fs'
import fsp from 'fs/promises'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main()
/**
* Copy the necessary template files from `templates/blank-3.0` to `dist/template`
*
* Eventually, this should be replaced with using tar.x to stream from the git repo
*/
async function main() {
const root = path.resolve(dirname, '../../../../')
const outputPath = path.resolve(dirname, '../../dist/template')
const sourceTemplatePath = path.resolve(root, 'templates/blank-3.0')
if (!fs.existsSync(sourceTemplatePath)) {
throw new Error(`Source path does not exist: ${sourceTemplatePath}`)
}
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true })
}
// Copy the src directory from `templates/blank-3.0` to `dist`
const srcPath = path.resolve(sourceTemplatePath, 'src')
const distSrcPath = path.resolve(outputPath, 'src')
// Copy entire file structure from src to dist
await fsp.cp(srcPath, distSrcPath, { recursive: true })
}

View File

@@ -1,27 +1,23 @@
/* eslint-disable no-console */
import * as p from '@clack/prompts'
import chalk from 'chalk'
import figures from 'figures'
export const success = (message: string): void => {
console.log(`${chalk.green(figures.tick)} ${chalk.bold(message)}`)
}
export const warning = (message: string): void => {
console.log(chalk.yellow('? ') + chalk.bold(message))
p.log.warn(chalk.yellow('? ') + chalk.bold(message))
}
export const info = (message: string, paddingTop?: number): void => {
console.log(
`${'\n'.repeat(paddingTop || 0)}${chalk.green(figures.pointerSmall)} ${chalk.bold(message)}`,
)
export const info = (message: string): void => {
p.log.step(chalk.bold(message))
}
export const error = (message: string): void => {
console.log(`${chalk.red(figures.cross)} ${chalk.bold(message)}`)
p.log.error(chalk.bold(message))
}
export const debug = (message: string): void => {
console.log(
`${chalk.gray(figures.pointerSmall)} ${chalk.bgGray('[DEBUG]')} ${chalk.gray(message)}`,
)
p.log.step(`${chalk.bgGray('[DEBUG]')} ${chalk.gray(message)}`)
}
export const log = (message: string): void => {
p.log.message(message)
}

View File

@@ -1,13 +1,14 @@
/* eslint-disable no-console */
import chalk from 'chalk'
import figures from 'figures'
import path from 'path'
import terminalLink from 'terminal-link'
import type { ProjectTemplate } from '../types.js'
import type { PackageManager } from '../types.js'
import { getValidTemplates } from '../lib/templates.js'
const header = (message: string): string => `${chalk.yellow(figures.star)} ${chalk.bold(message)}`
const header = (message: string): string => chalk.bold(message)
export const welcomeMessage = chalk`
{green Welcome to Payload. Let's create a project! }
@@ -15,14 +16,14 @@ export const welcomeMessage = chalk`
const spacer = ' '.repeat(8)
export function helpMessage(): string {
export function helpMessage(): void {
const validTemplates = getValidTemplates()
return chalk`
console.log(chalk`
{bold USAGE}
{dim $} {bold npx create-payload-app}
{dim $} {bold npx create-payload-app} my-project
{dim $} {bold npx create-payload-app} -n my-project -t blog
{dim $} {bold npx create-payload-app} -n my-project -t template-name
{bold OPTIONS}
@@ -36,7 +37,7 @@ export function helpMessage(): string {
--use-pnpm Use pnpm to install dependencies
--no-deps Do not install any dependencies
-h Show help
`
`)
}
function formatTemplates(templates: ProjectTemplate[]) {
@@ -45,29 +46,58 @@ function formatTemplates(templates: ProjectTemplate[]) {
.join(`\n${spacer}`)}`
}
export function successMessage(projectDir: string, packageManager: string): string {
export function successMessage(projectDir: string, packageManager: PackageManager): string {
const relativePath = path.relative(process.cwd(), projectDir)
return `
${header('Launch Application:')}
${header('Launch Application:')}
- cd ${projectDir}
- ${
packageManager === 'yarn' ? 'yarn' : 'npm run'
} dev or follow directions in ${createTerminalLink(
'README.md',
`file://${path.resolve(projectDir, 'README.md')}`,
)}
- cd ./${relativePath}
- ${
packageManager === 'npm' ? 'npm run' : packageManager
} dev or follow directions in ${createTerminalLink(
'README.md',
`file://${path.resolve(projectDir, 'README.md')}`,
)}
${header('Documentation:')}
${header('Documentation:')}
- ${createTerminalLink(
'Getting Started',
'https://payloadcms.com/docs/getting-started/what-is-payload',
)}
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
- ${createTerminalLink(
'Getting Started',
'https://payloadcms.com/docs/getting-started/what-is-payload',
)}
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
`
}
export function successfulNextInit(): string {
return `- ${createTerminalLink(
'Getting Started',
'https://payloadcms.com/docs/getting-started/what-is-payload',
)}
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
`
}
export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
const relativePath = path.relative(process.cwd(), args.nextAppDir)
return `
${header('Next Steps:')}
Payload does not support a top-level layout.tsx file in your Next.js app directory.
${chalk.bold('To continue:')}
Move all files from ./${relativePath} to a named directory such as ${chalk.bold('(app)')}
Once moved, rerun the create-payload-app command again.
`
}
export function feedbackOutro(): string {
return `${chalk.bgCyan(chalk.black(' Have feedback? '))} Visit ${createTerminalLink('GitHub', 'https://github.com/payloadcms/payload')}`
}
// Create terminalLink with fallback for unsupported terminals
function createTerminalLink(text: string, url: string) {
return terminalLink(text, url, {

View File

@@ -132,34 +132,48 @@ const seo =
(collection.auth ||
!(typeof collection.auth === 'object' && collection.auth.disableLocalStrategy)) &&
collection.fields?.find((field) => 'name' in field && field.name === 'email')
const hasOnlyEmailField = collection.fields?.length === 1 && emailField
const seoTabs: TabsField[] = [
{
type: 'tabs',
tabs: [
// append a new tab onto the end of the tabs array, if there is one at the first index
// if needed, create a new `Content` tab in the first index for this collection's base fields
...(collection?.fields?.[0]?.type === 'tabs' && collection?.fields?.[0]?.tabs
? collection.fields[0].tabs
: [
{
fields: [
...((emailField
? collection.fields.filter(
(field) => 'name' in field && field.name !== 'email',
)
: collection.fields) || []),
],
label: collection?.labels?.singular || 'Content',
},
]),
const seoTabs: TabsField[] = hasOnlyEmailField
? [
{
fields: seoFields,
label: 'SEO',
type: 'tabs',
tabs: [
{
fields: seoFields,
label: 'SEO',
},
],
},
],
},
]
]
: [
{
type: 'tabs',
tabs: [
// append a new tab onto the end of the tabs array, if there is one at the first index
// if needed, create a new `Content` tab in the first index for this collection's base fields
...(collection?.fields?.[0]?.type === 'tabs' &&
collection?.fields?.[0]?.tabs
? collection.fields[0].tabs
: [
{
fields: [
...(emailField
? collection.fields.filter(
(field) => 'name' in field && field.name !== 'email',
)
: collection.fields),
],
label: collection?.labels?.singular || 'Content',
},
]),
{
fields: seoFields,
label: 'SEO',
},
],
},
]
return {
...collection,

View File

@@ -1,7 +1,127 @@
import en from './en.json'
import es from './es.json'
import fa from './fa.json'
import fr from './fr.json'
import pl from './pl.json'
export const translations = { en, es, fa, fr, pl }
export const translations = {
en: {
$schema: './translation-schema.json',
'plugin-seo': {
almostThere: 'Almost there',
autoGenerate: 'Auto-generate',
bestPractices: 'best practices',
characterCount: '{{current}}/{{minLength}}-{{maxLength}} chars, ',
charactersLeftOver: '{{characters}} left over',
charactersToGo: '{{characters}} to go',
charactersTooMany: '{{characters}} too many',
checksPassing: '{{current}}/{{max}} checks are passing',
good: 'Good',
imageAutoGenerationTip: 'Auto-generation will retrieve the selected hero image.',
lengthTipDescription:
'This should be between {{minLength}} and {{maxLength}} characters. For help in writing quality meta descriptions, see ',
lengthTipTitle:
'This should be between {{minLength}} and {{maxLength}} characters. For help in writing quality meta titles, see ',
noImage: 'No image',
preview: 'Preview',
previewDescription: 'Exact result listings may vary based on content and search relevancy.',
tooLong: 'Too long',
tooShort: 'Too short',
},
},
es: {
$schema: './translation-schema.json',
'plugin-seo': {
almostThere: 'Ya casi está',
autoGenerate: 'Autogénerar',
bestPractices: 'mejores prácticas',
characterCount: '{{current}}/{{minLength}}-{{maxLength}} letras, ',
charactersLeftOver: '{{characters}} letras sobrantes',
charactersToGo: '{{characters}} letras sobrantes',
charactersTooMany: '{{characters}} letras demasiados',
checksPassing: '{{current}}/{{max}} las comprobaciones están pasando',
good: 'Bien',
imageAutoGenerationTip: 'La autogeneración recuperará la imagen de héroe seleccionada.',
lengthTipDescription:
'Esto debe estar entre {{minLength}} y {{maxLength}} caracteres. Para obtener ayuda sobre cómo escribir meta descripciones de calidad, consulte ',
lengthTipTitle:
'Debe tener entre {{minLength}} y {{maxLength}} caracteres. Para obtener ayuda sobre cómo escribir metatítulos de calidad, consulte ',
noImage: 'Sin imagen',
preview: 'Vista previa',
previewDescription:
'Las listas de resultados pueden variar segun la relevancia de buesqueda y el contenido.',
tooLong: 'Demasiado largo',
tooShort: 'Demasiado corto',
},
},
fa: {
$schema: './translation-schema.json',
'plugin-seo': {
almostThere: 'چیزیی باقی نمونده',
autoGenerate: 'تولید خودکار',
bestPractices: 'آموزش بیشتر',
characterCount: '{{current}}/{{minLength}}-{{maxLength}} کلمه، ',
charactersLeftOver: '{{characters}} باقی مانده',
charactersToGo: '{{characters}} باقی مانده',
charactersTooMany: '{{characters}} بیش از حد',
checksPassing: '{{current}}/{{max}} بررسی‌ها با موفقیت انجام شده است',
good: 'خوب',
imageAutoGenerationTip:
'این قابلیت، تصویر فعلی بارگذاری شده در مجموعه محتوای شما را بازیابی می‌کند',
lengthTipDescription:
'این باید بین {{minLength}} و {{maxLength}} کلمه باشد. برای کمک در نوشتن توضیحات متا با کیفیت، مراجعه کنید به ',
lengthTipTitle:
'این باید بین {{minLength}} و {{maxLength}} کلمه باشد. برای کمک در نوشتن عناوین متا با کیفیت، مراجعه کنید به ',
noImage: 'بدون تصویر',
preview: 'پیش‌نمایش',
previewDescription:
'فهرست نتایج ممکن است بر اساس محتوا و متناسب با کلمه کلیدی جستجو شده باشند',
tooLong: 'خیلی طولانی',
tooShort: 'خیلی کوتاه',
},
},
fr: {
$schema: './translation-schema.json',
'plugin-seo': {
almostThere: 'On y est presque',
autoGenerate: 'Auto-générer',
bestPractices: 'bonnes pratiques',
characterCount: '{{current}}/{{minLength}}-{{maxLength}} caractères, ',
charactersLeftOver: '{{characters}} restants',
charactersToGo: '{{characters}} à ajouter',
charactersTooMany: '{{characters}} en trop',
checksPassing: '{{current}}/{{max}} vérifications réussies',
good: 'Bien',
imageAutoGenerationTip: "L'auto-génération récupérera l'image principale sélectionnée.",
lengthTipDescription:
"Ceci devrait contenir entre {{minLength}} et {{maxLength}} caractères. Pour obtenir de l'aide pour rédiger des descriptions meta de qualité, consultez les ",
lengthTipTitle:
"Ceci devrait contenir entre {{minLength}} et {{maxLength}} caractères. Pour obtenir de l'aide pour rédiger des titres meta de qualité, consultez les ",
noImage: "Pas d'image",
preview: 'Aperçu',
previewDescription:
'Les résultats exacts peuvent varier en fonction du contenu et de la pertinence de la recherche.',
tooLong: 'Trop long',
tooShort: 'Trop court',
},
},
pl: {
$schema: './translation-schema.json',
'plugin-seo': {
almostThere: 'Prawie gotowe',
autoGenerate: 'Wygeneruj automatycznie',
bestPractices: 'najlepsze praktyki',
characterCount: '{{current}}/{{minLength}}-{{maxLength}} znaków, ',
charactersLeftOver: 'zostało {{characters}} znaków',
charactersToGo: 'pozostało {{characters}} znaków',
charactersTooMany: '{{characters}} znaków za dużo',
checksPassing: '{{current}}/{{max}} testów zakończonych pomyślnie',
good: 'Dobrze',
imageAutoGenerationTip: 'Automatyczne generowanie pobierze wybrany główny obraz.',
lengthTipDescription:
'Długość powinna wynosić od {{minLength}} do {{maxLength}} znaków. Po porady dotyczące pisania wysokiej jakości meta opisów zobacz ',
lengthTipTitle:
'Długość powinna wynosić od {{minLength}} do {{maxLength}} znaków. Po porady dotyczące pisania wysokiej jakości meta tytułów zobacz ',
noImage: 'Brak obrazu',
preview: 'Podgląd',
previewDescription:
'Dokładne wyniki listowania mogą się różnić w zależności od treści i zgodności z kryteriami wyszukiwania.',
tooLong: 'Zbyt długie',
tooShort: 'Zbyt krótkie',
},
},
}

46
pnpm-lock.yaml generated
View File

@@ -295,6 +295,9 @@ importers:
packages/create-payload-app:
dependencies:
'@clack/prompts':
specifier: ^0.7.0
version: 0.7.0
'@sindresorhus/slugify':
specifier: ^1.1.0
version: 1.1.2
@@ -331,15 +334,6 @@ importers:
globby:
specifier: 11.1.0
version: 11.1.0
handlebars:
specifier: ^4.7.7
version: 4.7.8
ora:
specifier: ^5.1.0
version: 5.4.1
prompts:
specifier: ^2.4.2
version: 2.4.2
terminal-link:
specifier: ^2.1.1
version: 2.1.1
@@ -362,9 +356,6 @@ importers:
'@types/node':
specifier: ^16.6.2
version: 16.18.85
'@types/prompts':
specifier: ^2.4.1
version: 2.4.9
packages/db-mongodb:
dependencies:
@@ -2745,6 +2736,23 @@ packages:
/@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
/@clack/core@0.3.4:
resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==}
dependencies:
picocolors: 1.0.0
sisteransi: 1.0.5
dev: false
/@clack/prompts@0.7.0:
resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==}
dependencies:
'@clack/core': 0.3.4
picocolors: 1.0.0
sisteransi: 1.0.5
dev: false
bundledDependencies:
- is-unicode-supported
/@cspotcode/source-map-support@0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -7451,6 +7459,7 @@ packages:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
dev: true
/body-parser@1.20.1:
resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
@@ -7790,6 +7799,7 @@ packages:
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-cursor@4.0.0:
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
@@ -7801,6 +7811,7 @@ packages:
/cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
dev: true
/cli-truncate@3.1.0:
resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
@@ -7852,6 +7863,7 @@ packages:
/clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
dev: true
/clsx@1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
@@ -8738,6 +8750,7 @@ packages:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
dependencies:
clone: 1.0.4
dev: true
/defer-to-connect@2.0.1:
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
@@ -10725,6 +10738,7 @@ packages:
wordwrap: 1.0.0
optionalDependencies:
uglify-js: 3.17.4
dev: true
/hanji@0.0.5:
resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==}
@@ -11234,6 +11248,7 @@ packages:
/is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
dev: true
/is-interactive@2.0.0:
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
@@ -11366,6 +11381,7 @@ packages:
/is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/is-unicode-supported@1.3.0:
resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
@@ -12595,6 +12611,7 @@ packages:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/log-symbols@6.0.0:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
@@ -13060,6 +13077,7 @@ packages:
/neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
dev: true
/netmask@2.0.2:
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
@@ -13489,6 +13507,7 @@ packages:
log-symbols: 4.1.0
strip-ansi: 6.0.1
wcwidth: 1.0.1
dev: true
/ora@8.0.1:
resolution: {integrity: sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==}
@@ -15472,6 +15491,7 @@ packages:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
dev: true
/restore-cursor@4.0.0:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
@@ -17014,6 +17034,7 @@ packages:
engines: {node: '>=0.8.0'}
hasBin: true
requiresBuild: true
dev: true
optional: true
/unbox-primitive@1.0.2:
@@ -17242,6 +17263,7 @@ packages:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.4
dev: true
/web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}

View File

@@ -1,12 +1,12 @@
import { mongooseAdapter } from '@payloadcms/db-mongodb' // database-adapter-import
import { payloadCloud } from '@payloadcms/plugin-cloud'
// import { payloadCloud } from '@payloadcms/plugin-cloud'
import { lexicalEditor } from '@payloadcms/richtext-lexical' // editor-import
import path from 'path'
import { buildConfig } from 'payload/config'
import sharp from 'sharp'
// import sharp from 'sharp'
import { fileURLToPath } from 'url'
import { Users } from './src/collections/Users'
import { Users } from './collections/Users'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -17,7 +17,7 @@ export default buildConfig({
},
collections: [Users],
editor: lexicalEditor({}),
plugins: [payloadCloud()],
// plugins: [payloadCloud()], // TODO: Re-enable when cloud supports 3.0
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
@@ -33,5 +33,6 @@ export default buildConfig({
// This is temporary - we may make an adapter pattern
// for this before reaching 3.0 stable
sharp,
// sharp,
})

View File

@@ -20,7 +20,7 @@
],
"paths": {
"@/*": ["./src/*"],
"@payload-config": ["./payload.config.ts"]
"@payload-config": ["./src/payload.config.ts"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],

View File

@@ -7,12 +7,8 @@ import fs from 'fs'
import path from 'path'
import shelljs from 'shelljs'
import tempy from 'tempy'
import { fileURLToPath } from 'url'
import { promisify } from 'util'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
@@ -29,13 +25,6 @@ describe('create-payload-app', () => {
shelljs.exec('pnpm build:create-payload-app')
})
describe('Next.js app template files', () => {
it('should exist in dist', () => {
const distPath = path.resolve(dirname, '../../packages/create-payload-app/dist/app/(payload)')
expect(fs.existsSync(distPath)).toBe(true)
})
})
describe.each(Object.keys(nextCreateCommands))(`--init-next with %s`, (nextCmdKey) => {
const projectDir = tempy.directory()
beforeEach(async () => {
@@ -74,19 +63,53 @@ describe('create-payload-app', () => {
it('should install payload app in Next.js project', async () => {
expect(fs.existsSync(projectDir)).toBe(true)
const result = await initNext({
const firstResult = await initNext({
'--debug': true,
projectDir,
nextConfigPath: path.resolve(projectDir, 'next.config.mjs'),
dbType: 'mongodb',
useDistFiles: true, // create-payload-app/dist/app/(payload)
packageManager: 'pnpm',
})
expect(result.success).toBe(true)
// Will fail because we detect top-level layout.tsx file
expect(firstResult.success).toEqual(false)
const payloadFilesPath = path.resolve(result.userAppDir, '(payload)')
// Move all files from app to top-level directory named `(app)`
if (firstResult.success === false && 'nextAppDir' in firstResult) {
fs.mkdirSync(path.resolve(firstResult.nextAppDir, '(app)'))
fs.readdirSync(path.resolve(firstResult.nextAppDir)).forEach((file) => {
if (file === '(app)') return
fs.renameSync(
path.resolve(firstResult.nextAppDir, file),
path.resolve(firstResult.nextAppDir, '(app)', file),
)
})
}
// Rerun after moving files
const result = await initNext({
'--debug': true,
projectDir,
nextConfigPath: path.resolve(projectDir, 'next.config.mjs'),
dbType: 'mongodb',
useDistFiles: true, // create-payload-app/dist/app/(payload)
packageManager: 'pnpm',
})
expect(result.success).toEqual(true)
expect(result.nextAppDir).toEqual(
path.resolve(projectDir, result.isSrcDir ? 'src/app' : 'app'),
)
const payloadFilesPath = path.resolve(result.nextAppDir, '(payload)')
// shelljs.exec(`tree ${projectDir}`)
expect(fs.existsSync(payloadFilesPath)).toBe(true)
const payloadConfig = path.resolve(projectDir, 'payload.config.ts')
const payloadConfig = path.resolve(
projectDir,
result.isSrcDir ? 'src/payload.config.ts' : 'payload.config.ts',
)
expect(fs.existsSync(payloadConfig)).toBe(true)
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
@@ -95,7 +118,7 @@ describe('create-payload-app', () => {
compilerOptions?: CompilerOptions
}
expect(userTsConfig.compilerOptions.paths?.['@payload-config']).toStrictEqual([
'./payload.config.ts',
`./${result.isSrcDir ? 'src/' : ''}payload.config.ts`,
])
// TODO: Start the Next.js app and check if it runs

View File

@@ -149,7 +149,7 @@ describe('Live Preview', () => {
test('global - can edit fields', async () => {
await goToGlobalPreview(page, 'header')
const field = page.locator('input#field-navItems__0__link__newTab')
const field = page.locator('input#field-navItems__0__link____newTab')
await expect(field).toBeVisible()
await expect(field).toBeEnabled()
await field.check()

View File

@@ -2,7 +2,6 @@ import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'path'
import payload from 'payload'
import { fileURLToPath } from 'url'
import type { Page as PayloadPage } from './payload-types.js'
@@ -11,6 +10,7 @@ import { initPageConsoleErrorCatch } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2E } from '../helpers/initPayloadE2E.js'
import config from './config.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -22,22 +22,32 @@ let parentId: string
let draftChildId: string
let childId: string
async function createPage(data: Partial<PayloadPage>): Promise<PayloadPage> {
return payload.create({
collection: 'pages',
data,
}) as unknown as Promise<PayloadPage>
}
describe('Nested Docs Plugin', () => {
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadE2E({ config, dirname })
const { serverURL, payload } = await initPayloadE2E({ config, dirname })
url = new AdminUrlUtil(serverURL, 'pages')
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
async function createPage({
slug,
title = 'Title page',
parent,
_status = 'published',
}: Partial<PayloadPage>): Promise<PayloadPage> {
return payload.create({
collection: 'pages',
data: {
title,
slug,
_status,
parent,
},
}) as unknown as Promise<PayloadPage>
}
const parentPage = await createPage({ slug: 'parent-slug' })
parentId = parentPage.id
@@ -70,41 +80,58 @@ describe('Nested Docs Plugin', () => {
let slug = page.locator(slugClass).nth(0)
await expect(slug).toHaveValue('child-slug')
const parentSlugInChildClass = '#field-breadcrumbs__0__url'
// TODO: remove when error states are fixed
const apiTabButton = page.locator('text=API')
await apiTabButton.click()
const breadcrumbs = page.locator('text=/parent-slug').first()
await expect(breadcrumbs).toBeVisible()
const parentSlugInChild = page.locator(parentSlugInChildClass).nth(0)
await expect(parentSlugInChild).toHaveValue('/parent-slug')
// TODO: add back once error states are fixed
// const parentSlugInChildClass = '#field-breadcrumbs__0__url'
// const parentSlugInChild = page.locator(parentSlugInChildClass).nth(0)
// await expect(parentSlugInChild).toHaveValue('/parent-slug')
await page.goto(url.edit(parentId))
slug = page.locator(slugClass).nth(0)
await slug.fill('updated-parent-slug')
await expect(slug).toHaveValue('updated-parent-slug')
await page.locator(publishButtonClass).nth(0).click()
await page.waitForTimeout(1500)
await page.goto(url.edit(childId))
await expect(parentSlugInChild).toHaveValue('/updated-parent-slug')
// TODO: remove when error states are fixed
await apiTabButton.click()
const updatedBreadcrumbs = page.locator('text=/updated-parent-slug').first()
await expect(updatedBreadcrumbs).toBeVisible()
// TODO: add back once error states are fixed
// await expect(parentSlugInChild).toHaveValue('/updated-parent-slug')
})
test('Draft parent slug does not update child', async () => {
await page.goto(url.edit(draftChildId))
const parentSlugInChildClass = '#field-breadcrumbs__0__url'
// TODO: remove when error states are fixed
const apiTabButton = page.locator('text=API')
await apiTabButton.click()
const breadcrumbs = page.locator('text=/parent-slug-draft').first()
await expect(breadcrumbs).toBeVisible()
const parentSlugInChild = page.locator(parentSlugInChildClass).nth(0)
await expect(parentSlugInChild).toHaveValue('/parent-slug-draft')
// TODO: add back once error states are fixed
// const parentSlugInChildClass = '#field-breadcrumbs__0__url'
// const parentSlugInChild = page.locator(parentSlugInChildClass).nth(0)
// await expect(parentSlugInChild).toHaveValue('/parent-slug-draft')
await page.goto(url.edit(parentId))
await page.locator(slugClass).nth(0).fill('parent-updated-draft')
await page.locator(draftButtonClass).nth(0).click()
await page.waitForTimeout(1500)
await page.goto(url.edit(draftChildId))
await expect(parentSlugInChild).toHaveValue('/parent-slug-draft')
await apiTabButton.click()
const updatedBreadcrumbs = page.locator('text=/parent-slug-draft').first()
await expect(updatedBreadcrumbs).toBeVisible()
// TODO: add back when error states are fixed
// await expect(parentSlugInChild).toHaveValue('/parent-slug-draft')
})
})
})

View File

@@ -0,0 +1,9 @@
export interface Page {
id: string
parent?: string
slug: string
_status?: 'draft' | 'published'
title?: string
updatedAt: string
createdAt: string
}

View File

@@ -37,11 +37,10 @@ export default buildConfigWithDefaults({
plugins: [
seo({
collections: ['users'],
fields: [],
tabbedUI: true,
}),
seo({
collections: ['pages', 'posts'],
collections: ['pages'],
fieldOverrides: {
title: {
required: true,
@@ -58,7 +57,6 @@ export default buildConfigWithDefaults({
generateTitle: (data: any) => `Website.com — ${data?.doc?.title?.value}`,
generateURL: ({ doc, locale }: any) =>
`https://yoursite.com/${locale ? locale + '/' : ''}${doc?.slug?.value || ''}`,
globals: ['settings'],
tabbedUI: true,
uploadsCollection: 'media',
}),

View File

@@ -1,5 +1,4 @@
import type { Page } from '@playwright/test'
import type { Payload } from 'payload/types'
import { expect, test } from '@playwright/test'
import path from 'path'
@@ -11,20 +10,21 @@ import type { Page as PayloadPage } from './payload-types.js'
import { initPageConsoleErrorCatch } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2E } from '../helpers/initPayloadE2E.js'
import config from '../uploads/config.js'
import config from './config.js'
import { mediaSlug } from './shared.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const { beforeAll, describe } = test
let url: AdminUrlUtil
let page: Page
let id: string
let payload: Payload
describe('SEO Plugin', () => {
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadE2E({ config, dirname })
const { serverURL, payload } = await initPayloadE2E({ config, dirname })
url = new AdminUrlUtil(serverURL, 'pages')
const context = await browser.newContext()
@@ -68,15 +68,17 @@ describe('SEO Plugin', () => {
test('Should auto-generate meta title when button is clicked in tabs', async () => {
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'
const autoGenerateButtonClass = '.group-field__wrap .render-fields div:nth-of-type(1) button'
const metaTitleClass = '#field-title'
const metaTitleClass = '#field-meta__title'
const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
const metaTitle = page.locator(metaTitleClass).nth(0)
const metaTitle = page.locator(metaTitleClass)
await expect(metaTitle).toHaveValue('This is a test meta title')
const autoGenButton = page.locator(autoGenerateButtonClass).nth(0)
await expect(autoGenButton).toContainText('Auto-generate')
await autoGenButton.click()
await expect(metaTitle).toHaveValue('Website.com — Test Page')
@@ -108,17 +110,17 @@ describe('SEO Plugin', () => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'
const autoGenerateButtonClass = '.group-field__wrap .render-fields div:nth-of-type(1) button'
const metaDescriptionClass = '#field-description'
const metaDescriptionClass = '#field-meta__description'
const previewClass =
'#field-meta > div > div.render-fields.render-fields--margins-small > div:nth-child(6) > div:nth-child(3)'
'#field-meta > div > div.render-fields.render-fields--margins-small > div:nth-child(6)'
const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
const metaDescription = page.locator(metaDescriptionClass).nth(0)
const metaDescription = page.locator(metaDescriptionClass)
await metaDescription.fill('My new amazing SEO description')
const preview = page.locator(previewClass).nth(0)
const preview = page.locator(previewClass)
await expect(preview).toContainText('https://yoursite.com/en/')
await expect(preview).toContainText('This is a test meta title')
await expect(preview).toContainText('My new amazing SEO description')
@@ -147,6 +149,7 @@ describe('SEO Plugin', () => {
// Change language to Spanish
await languageField.click()
await options.locator('text=Español').click()
await expect(languageField).toContainText('Español')
// Navigate back to the page
await page.goto(url.edit(id))

View File

@@ -11,11 +11,7 @@
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"jsx": "preserve",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"noEmit": true,
"outDir": "./dist",
"resolveJsonModule": true,
@@ -23,11 +19,7 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": false,
"types": [
"jest",
"node",
"@types/jest"
],
"types": ["jest", "node", "@types/jest"],
"incremental": true,
"isolatedModules": true,
"plugins": [
@@ -36,65 +28,26 @@
}
],
"paths": {
"@payload-config": [
"./test/_community/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"
],
"@payloadcms/live-preview-react": [
"./packages/live-preview-react/src/index.ts"
],
"@payloadcms/ui/assets": [
"./packages/ui/src/assets/index.ts"
],
"@payloadcms/ui/elements/*": [
"./packages/ui/src/elements/*/index.tsx"
],
"@payloadcms/ui/fields/*": [
"./packages/ui/src/fields/*/index.tsx"
],
"@payloadcms/ui/forms/*": [
"./packages/ui/src/forms/*/index.tsx"
],
"@payloadcms/ui/graphics/*": [
"./packages/ui/src/graphics/*/index.tsx"
],
"@payloadcms/ui/hooks/*": [
"./packages/ui/src/hooks/*.ts"
],
"@payloadcms/ui/icons/*": [
"./packages/ui/src/icons/*/index.tsx"
],
"@payloadcms/ui/providers/*": [
"./packages/ui/src/providers/*/index.tsx"
],
"@payloadcms/ui/templates/*": [
"./packages/ui/src/templates/*/index.tsx"
],
"@payloadcms/ui/utilities/*": [
"./packages/ui/src/utilities/*.ts"
],
"@payloadcms/ui/scss": [
"./packages/ui/src/scss.scss"
],
"@payloadcms/ui/scss/app.scss": [
"./packages/ui/src/scss/app.scss"
],
"@payloadcms/next/*": [
"./packages/next/src/*"
],
"@payloadcms/next": [
"./packages/next/src/exports/*"
]
"@payload-config": ["./test/_community/config.ts"],
"@payloadcms/live-preview": ["./packages/live-preview/src"],
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
"@payloadcms/ui/assets": ["./packages/ui/src/assets/index.ts"],
"@payloadcms/ui/elements/*": ["./packages/ui/src/elements/*/index.tsx"],
"@payloadcms/ui/fields/*": ["./packages/ui/src/fields/*/index.tsx"],
"@payloadcms/ui/forms/*": ["./packages/ui/src/forms/*/index.tsx"],
"@payloadcms/ui/graphics/*": ["./packages/ui/src/graphics/*/index.tsx"],
"@payloadcms/ui/hooks/*": ["./packages/ui/src/hooks/*.ts"],
"@payloadcms/ui/icons/*": ["./packages/ui/src/icons/*/index.tsx"],
"@payloadcms/ui/providers/*": ["./packages/ui/src/providers/*/index.tsx"],
"@payloadcms/ui/templates/*": ["./packages/ui/src/templates/*/index.tsx"],
"@payloadcms/ui/utilities/*": ["./packages/ui/src/utilities/*.ts"],
"@payloadcms/ui/scss": ["./packages/ui/src/scss.scss"],
"@payloadcms/ui/scss/app.scss": ["./packages/ui/src/scss/app.scss"],
"@payloadcms/next/*": ["./packages/next/src/*"],
"@payloadcms/next": ["./packages/next/src/exports/*"]
}
},
"exclude": [
"dist",
"build",
"temp",
"node_modules"
],
"exclude": ["dist", "build", "temp", "node_modules"],
"composite": true,
"references": [
{
@@ -155,9 +108,5 @@
"path": "./packages/ui"
}
],
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"scripts/**/*.ts"
]
}
"include": ["next-env.d.ts", ".next/types/**/*.ts", "scripts/**/*.ts"]
}