chore(create-payload-app): configure db in init next flow

This commit is contained in:
Elliot DeNolf
2024-03-29 14:12:30 -04:00
parent 77f401d977
commit 403a86feca
5 changed files with 94 additions and 62 deletions

View File

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

@@ -90,7 +90,7 @@ export async function createProject(args: {
const spinner = ora('Checking latest Payload version...').start() const spinner = ora('Checking latest Payload version...').start()
await updatePackageJSON({ projectDir, projectName }) await updatePackageJSON({ projectDir, projectName })
await configurePayloadConfig({ dbDetails, projectDir }) await configurePayloadConfig({ dbDetails, projectDirOrConfigPath: { projectDir } })
// Remove yarn.lock file. This is only desired in Payload Cloud. // Remove yarn.lock file. This is only desired in Payload Cloud.
const lockPath = path.resolve(projectDir, 'yarn.lock') const lockPath = path.resolve(projectDir, 'yarn.lock')
@@ -125,6 +125,6 @@ export async function updatePackageJSON(args: {
packageObj.name = projectName packageObj.name = projectName
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 }) await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) { } 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

@@ -24,16 +24,23 @@ import { moveMessage } from '../utils/messages.js'
import { wrapNextConfig } from './wrap-next-config.js' import { wrapNextConfig } from './wrap-next-config.js'
type InitNextArgs = Pick<CliArgs, '--debug'> & { type InitNextArgs = Pick<CliArgs, '--debug'> & {
nextConfigPath: string
packageManager: PackageManager packageManager: PackageManager
projectDir: string projectDir: string
useDistFiles?: boolean useDistFiles?: boolean
} }
type InitNextResult = { nextAppDir?: string; reason?: string; success: boolean } type InitNextResult =
| {
nextAppDir: string
payloadConfigPath: string
success: true
}
| { reason: string; success: false }
export async function initNext(args: InitNextArgs): Promise<InitNextResult> { export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const { packageManager, projectDir } = args const { packageManager, projectDir } = args
// Get app directory // Get app directory. Could be top-level or src/app
const nextAppDir = ( const nextAppDir = (
await globby(['**/app'], { await globby(['**/app'], {
absolute: true, absolute: true,
@@ -49,27 +56,28 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
// Check for top-level layout.tsx // Check for top-level layout.tsx
const layoutPath = path.resolve(nextAppDir, 'layout.tsx') const layoutPath = path.resolve(nextAppDir, 'layout.tsx')
if (fs.existsSync(layoutPath)) { if (fs.existsSync(layoutPath)) {
// Output directions for user to move all files from app to top-level directory named `(name)` // Output directions for user to move all files from app to top-level directory named `(app)`
log(moveMessage({ nextAppDir, projectDir })) log(moveMessage({ nextAppDir, projectDir }))
return { reason: 'Found existing layout.tsx in app directory', success: false } return { reason: 'Found existing layout.tsx in app directory', success: false }
} }
const templateResult = await installAndConfigurePayload({ const configurationResult = installAndConfigurePayload({
...args, ...args,
nextAppDir, nextAppDir,
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
}) })
if (!templateResult.success) return templateResult
if (!configurationResult.success) return configurationResult
const { success: installSuccess } = await installDeps(projectDir, packageManager) const { success: installSuccess } = await installDeps(projectDir, packageManager)
if (!installSuccess) { if (!installSuccess) {
return { ...templateResult, reason: 'Failed to install dependencies', success: false } return { ...configurationResult, reason: 'Failed to install dependencies', success: false }
} }
// Add `@payload-config` to tsconfig.json `paths` // Add `@payload-config` to tsconfig.json `paths`
await addPayloadConfigToTsConfig(projectDir) await addPayloadConfigToTsConfig(projectDir)
return templateResult return configurationResult
} }
async function addPayloadConfigToTsConfig(projectDir: string) { async function addPayloadConfigToTsConfig(projectDir: string) {
@@ -93,10 +101,8 @@ async function addPayloadConfigToTsConfig(projectDir: string) {
} }
} }
async function installAndConfigurePayload( function installAndConfigurePayload(args: InitNextArgs & { nextAppDir: string }): InitNextResult {
args: InitNextArgs & { nextAppDir: string }, const { '--debug': debug, nextAppDir, nextConfigPath, projectDir, useDistFiles } = args
): Promise<InitNextResult> {
const { '--debug': debug, nextAppDir, projectDir, useDistFiles } = args
info('Initializing Payload app in Next.js project', 1) info('Initializing Payload app in Next.js project', 1)
@@ -108,32 +114,12 @@ async function installAndConfigurePayload(
return { reason: `Could not find specified project directory at ${projectDir}`, success: false } 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.`,
success: false,
}
} else {
if (debug) logDebug(`Found Next config at ${nextConfigPath}`)
}
const templateFilesPath = const templateFilesPath =
// dirname.endsWith('dist') || useDistFiles
// ? path.resolve(dirname, '../..', 'dist/template')
// : path.resolve(dirname, '../../../../templates/blank-3.0')
dirname.endsWith('dist') || useDistFiles dirname.endsWith('dist') || useDistFiles
? path.resolve(dirname, '../..', 'dist/template') ? path.resolve(dirname, '../..', 'dist/template')
: path.resolve(dirname, '../../../../templates/blank-3.0') : 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)) { if (!fs.existsSync(templateFilesPath)) {
return { return {
@@ -141,13 +127,7 @@ async function installAndConfigurePayload(
success: false, success: false,
} }
} else { } else {
if (debug) logDebug('Found template source files') logDebug('Found template source files')
}
if (!fs.existsSync(nextAppDir)) {
return { reason: `Could not find user app directory inside ${projectDir}`, success: false }
} else {
logDebug(`Found user app directory: ${nextAppDir}`)
} }
logDebug(`Copying template files from ${templateFilesPath} to ${nextAppDir}`) logDebug(`Copying template files from ${templateFilesPath} to ${nextAppDir}`)
@@ -162,7 +142,11 @@ async function installAndConfigurePayload(
wrapNextConfig({ nextConfigPath }) wrapNextConfig({ nextConfigPath })
success('Successfully initialized.') success('Successfully initialized.')
return { nextAppDir, success: true } return {
nextAppDir,
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
success: true,
}
} }
async function installDeps(projectDir: string, packageManager: PackageManager) { async function installDeps(projectDir: string, packageManager: PackageManager) {

View File

@@ -6,6 +6,7 @@ import path from 'path'
import type { CliArgs, PackageManager } from './types.js' import type { CliArgs, PackageManager } from './types.js'
import { configurePayloadConfig } from './lib/configure-payload-config.js'
import { createProject } from './lib/create-project.js' import { createProject } from './lib/create-project.js'
import { generateSecret } from './lib/generate-secret.js' import { generateSecret } from './lib/generate-secret.js'
import { initNext } from './lib/init-next.js' import { initNext } from './lib/init-next.js'
@@ -14,8 +15,13 @@ import { parseTemplate } from './lib/parse-template.js'
import { selectDb } from './lib/select-db.js' import { selectDb } from './lib/select-db.js'
import { getValidTemplates, validateTemplate } from './lib/templates.js' import { getValidTemplates, validateTemplate } from './lib/templates.js'
import { writeEnvFile } from './lib/write-env-file.js' import { writeEnvFile } from './lib/write-env-file.js'
import { debug, error, log, success } from './utils/log.js' import { error, log, success } from './utils/log.js'
import { helpMessage, successMessage, welcomeMessage } from './utils/messages.js' import {
helpMessage,
successMessage,
successfulNextInit,
welcomeMessage,
} from './utils/messages.js'
export class Main { export class Main {
args: CliArgs args: CliArgs
@@ -59,7 +65,9 @@ export class Main {
} }
async init(): Promise<void> { async init(): Promise<void> {
const initContext = { const initContext: {
nextConfigPath: string | undefined
} = {
nextConfigPath: undefined, nextConfigPath: undefined,
} }
@@ -71,10 +79,10 @@ export class Main {
log(welcomeMessage) log(welcomeMessage)
// Detect if inside Next.js project // Detect if inside Next.js project
const foundConfig = ( const nextConfigPath = (
await globby('next.config.*js', { absolute: true, cwd: process.cwd() }) await globby('next.config.*js', { absolute: true, cwd: process.cwd() })
)?.[0] )?.[0]
initContext.nextConfigPath = foundConfig initContext.nextConfigPath = nextConfigPath
success('Next.js app detected.') success('Next.js app detected.')
@@ -85,22 +93,38 @@ export class Main {
} }
const projectName = await parseProjectName(this.args) const projectName = await parseProjectName(this.args)
const projectDir = foundConfig const projectDir = nextConfigPath
? path.dirname(foundConfig) ? path.dirname(nextConfigPath)
: path.resolve(process.cwd(), slugify(projectName)) : path.resolve(process.cwd(), slugify(projectName))
const packageManager = await getPackageManager(this.args, projectDir) const packageManager = await getPackageManager(this.args, projectDir)
debug(`Using package manager: ${packageManager}`)
if (foundConfig) { if (nextConfigPath) {
const result = await initNext({ ...this.args, packageManager, projectDir }) const result = await initNext({
if (!result.success) { ...this.args,
error(result.reason || 'Failed to initialize Payload app in Next.js project') nextConfigPath,
packageManager,
projectDir,
})
if (result.success === false) {
error(result.reason)
process.exit(1)
} else { } else {
success('Payload app successfully initialized in Next.js project') success('Payload app successfully initialized in Next.js project')
} }
process.exit(result.success ? 0 : 1)
const dbDetails = await selectDb(this.args, projectName)
await configurePayloadConfig({
dbDetails,
projectDirOrConfigPath: {
payloadConfigPath: result.payloadConfigPath,
},
})
// TODO: This should continue the normal prompt flow // TODO: This should continue the normal prompt flow
success('Payload project successfully created')
log(successfulNextInit())
return
} }
const templateArg = this.args['--template'] const templateArg = this.args['--template']

View File

@@ -68,6 +68,20 @@ export function successMessage(projectDir: string, packageManager: string): stri
` `
} }
export function successfulNextInit(): string {
return `
${header('Successful Payload Installation!')}
${header('Documentation:')}
- ${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 { export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
return ` return `
${header('Next Steps:')} ${header('Next Steps:')}
@@ -77,7 +91,7 @@ export function moveMessage(args: { nextAppDir: string; projectDir: string }): s
${chalk.bold('To continue:')} ${chalk.bold('To continue:')}
- Move all files from ${args.nextAppDir} to a top-level directory named ${chalk.bold( - Move all files from ${args.nextAppDir} to a top-level directory named ${chalk.bold(
'(name)', '(app)',
)} or similar. )} or similar.
Once moved, rerun the create-payload-app command again. Once moved, rerun the create-payload-app command again.