Skip to content

Commit

Permalink
feat(cpa): update existing payload installation
Browse files Browse the repository at this point in the history
  • Loading branch information
denolfe committed May 3, 2024
1 parent 07f2d74 commit cf4a57c
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 40 deletions.
23 changes: 23 additions & 0 deletions packages/create-payload-app/src/lib/get-package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ts-expect-error no types
import { detect } from 'detect-package-manager'

import type { CliArgs, PackageManager } from '../types.js'

export async function getPackageManager(
args: CliArgs,
projectDir: string,
): Promise<PackageManager> {
let packageManager: PackageManager = 'npm'

if (args['--use-npm']) {
packageManager = 'npm'
} else if (args['--use-yarn']) {
packageManager = 'yarn'
} else if (args['--use-pnpm']) {
packageManager = 'pnpm'
} else {
const detected = await detect({ cwd: projectDir })
packageManager = detected || 'npm'
}
return packageManager
}
45 changes: 25 additions & 20 deletions packages/create-payload-app/src/lib/init-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,23 @@ import execa from 'execa'
import fs from 'fs'
import fse from 'fs-extra'
import globby from 'globby'
import { fileURLToPath } from 'node:url'
import path from 'path'
import { promisify } from 'util'

const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)

const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)

import { fileURLToPath } from 'node:url'

import type { CliArgs, DbType, PackageManager } from '../types.js'
import type { CliArgs, DbType, NextAppDetails, NextConfigType, PackageManager } from '../types.js'

import { copyRecursiveSync } from '../utils/copy-recursive-sync.js'
import { debug as origDebug, warning } from '../utils/log.js'
import { moveMessage } from '../utils/messages.js'
import { wrapNextConfig } from './wrap-next-config.js'

const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)

const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)

type InitNextArgs = Pick<CliArgs, '--debug'> & {
dbType: DbType
nextAppDetails?: NextAppDetails
Expand All @@ -32,8 +31,6 @@ type InitNextArgs = Pick<CliArgs, '--debug'> & {
useDistFiles?: boolean
}

type NextConfigType = 'cjs' | 'esm'

type InitNextResult =
| {
isSrcDir: boolean
Expand All @@ -55,7 +52,8 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
nextAppDetails.nextAppDir = createdAppDir
}

const { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails
const { hasTopLevelLayout, isPayloadInstalled, isSrcDir, nextAppDir, nextConfigType } =
nextAppDetails

if (!nextConfigType) {
return {
Expand Down Expand Up @@ -228,6 +226,9 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d

packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)

// Match graphql version of @payloadcms/next
packagesToInstall.push('graphql@^16.8.1')

let exitCode = 0
switch (packageManager) {
case 'npm': {
Expand All @@ -253,20 +254,13 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
return { success: exitCode === 0 }
}

type NextAppDetails = {
hasTopLevelLayout: boolean
isSrcDir: boolean
nextAppDir?: string
nextConfigPath?: string
nextConfigType?: NextConfigType
}

export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))

const nextConfigPath: string | undefined = (
await globby('next.config.*js', { absolute: true, cwd: projectDir })
)?.[0]

if (!nextConfigPath || nextConfigPath.length === 0) {
return {
hasTopLevelLayout: false,
Expand All @@ -275,6 +269,17 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
}
}

// TODO: Check if Payload is already installed
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
if (packageObj.dependencies?.payload) {
return {
hasTopLevelLayout: false,
isPayloadInstalled: true,
isSrcDir,
nextConfigPath,
}
}

let nextAppDir: string | undefined = (
await globby(['**/app'], {
absolute: true,
Expand Down
112 changes: 112 additions & 0 deletions packages/create-payload-app/src/lib/update-payload-in-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// @ts-expect-error no types
import { detect } from 'detect-package-manager'
import execa from 'execa'
import fse from 'fs-extra'
import { fileURLToPath } from 'node:url'
import path from 'path'

const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)

import type { NextAppDetails } from '../types.js'

import { copyRecursiveSync } from '../utils/copy-recursive-sync.js'
import { debug, info, warning } from '../utils/log.js'

export async function updatePayloadInProject(
appDetails: NextAppDetails,
): Promise<{ success: boolean }> {
if (!appDetails.nextConfigPath) return { success: false }

const projectDir = path.dirname(appDetails.nextConfigPath)

const packageObj = (await fse.readJson(path.resolve(projectDir, 'package.json'))) as {
dependencies?: Record<string, string>
}
if (!packageObj?.dependencies) {
throw new Error('No package.json found in this project')
}

const payloadVersion = packageObj.dependencies?.payload
if (!payloadVersion) {
throw new Error('Payload is not installed in this project')
}

const packageManager = await detect({ cwd: projectDir })

// Fetch latest Payload version from npm
const { exitCode: getLatestVersionExitCode, stdout: latestPayloadVersion } = await execa('npm', [
'show',
'payload@beta',
'version',
])
if (getLatestVersionExitCode !== 0) {
throw new Error('Failed to fetch latest Payload version')
}

if (payloadVersion === latestPayloadVersion) {
throw new Error(`Payload v${payloadVersion} is already up to date.`)
}

// Update all existing Payload packages
const payloadPackages = Object.keys(packageObj.dependencies).filter((dep) =>
dep.startsWith('@payloadcms/'),
)
const packagesToUpdate = ['payload', ...payloadPackages].map(
(pkg) => `${pkg}@${latestPayloadVersion}`,
)

info(`Updating ${packagesToUpdate.length} Payload packages to v${latestPayloadVersion}...`)
debug(`Packages to update: ${packagesToUpdate.join(', ')}`)
debug(`Package Manager: ${packageManager}`)
debug(`Project directory: ${projectDir}`)

// Update all packages
let exitCode = 0
let stdout = ''
let stderr = ''
switch (packageManager) {
case 'npm': {
;({ exitCode, stderr, stdout } = await execa(
'npm',
['install', '--save', ...packagesToUpdate],
{
cwd: projectDir,
},
))
break
}
case 'yarn':
case 'pnpm': {
debug(`args: ${[packageManager, 'add', ...packagesToUpdate].join(' ')}`)
;({ exitCode, stderr, stdout } = await execa(packageManager, ['add', ...packagesToUpdate], {
cwd: projectDir,
}))
break
}
case 'bun': {
warning('Bun support is untested.')
;({ exitCode } = await execa('bun', ['add', ...packagesToUpdate], { cwd: projectDir }))
break
}
}
debug(`Exit code: ${exitCode}`)
debug(`stdout: ${stdout}`)
debug(`stderr: ${stderr}`)

if (exitCode !== 0) {
throw new Error('Failed to update Payload packages')
}
info('Payload packages updated successfully.')

info(`Updating Payload Next.js files...`)
const templateFilesPath = dirname.endsWith('dist')
? path.resolve(dirname, '../..', 'dist/template')
: path.resolve(dirname, '../../../../templates/blank-3.0')

const templateSrcDir = path.resolve(templateFilesPath ? '' : 'src')

copyRecursiveSync(templateSrcDir, path.dirname(dirname))

return { success: true }
}
40 changes: 20 additions & 20 deletions packages/create-payload-app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import * as p from '@clack/prompts'
import slugify from '@sindresorhus/slugify'
import arg from 'arg'
import chalk from 'chalk'
// @ts-expect-error no types
import { detect } from 'detect-package-manager'
import figures from 'figures'
import path from 'path'

import type { CliArgs, PackageManager } from './types.js'
import type { CliArgs } 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 { getPackageManager } from './lib/get-package-manager.js'
import { getNextAppDetails, initNext } from './lib/init-next.js'
import { parseProjectName } from './lib/parse-project-name.js'
import { parseTemplate } from './lib/parse-template.js'
import { selectDb } from './lib/select-db.js'
import { getValidTemplates, validateTemplate } from './lib/templates.js'
import { updatePayloadInProject } from './lib/update-payload-in-project.js'
import { writeEnvFile } from './lib/write-env-file.js'
import { error, info } from './utils/log.js'
import {
Expand Down Expand Up @@ -82,7 +82,23 @@ export class Main {

// Detect if inside Next.js project
const nextAppDetails = await getNextAppDetails(process.cwd())
const { hasTopLevelLayout, nextAppDir, nextConfigPath } = nextAppDetails
const { hasTopLevelLayout, isPayloadInstalled, nextAppDir, nextConfigPath } = nextAppDetails

// Upgrade Payload in existing project
if (isPayloadInstalled && nextConfigPath) {
p.log.warn(`Payload is already installed in this project.`)
const shouldUpdate = await p.confirm({
initialValue: false,
message: chalk.bold(`Upgrade Payload in this project?`),
})

if (p.isCancel(shouldUpdate) || !shouldUpdate) {
await updatePayloadInProject(nextAppDetails)
}

p.outro(feedbackOutro())
process.exit(0)
}

if (nextConfigPath) {
this.args['--name'] = slugify(path.basename(path.dirname(nextConfigPath)))
Expand Down Expand Up @@ -209,19 +225,3 @@ export class Main {
}
}
}

async function getPackageManager(args: CliArgs, projectDir: string): Promise<PackageManager> {
let packageManager: PackageManager = 'npm'

if (args['--use-npm']) {
packageManager = 'npm'
} else if (args['--use-yarn']) {
packageManager = 'yarn'
} else if (args['--use-pnpm']) {
packageManager = 'pnpm'
} else {
const detected = await detect({ cwd: projectDir })
packageManager = detected || 'npm'
}
return packageManager
}
11 changes: 11 additions & 0 deletions packages/create-payload-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,14 @@ export type DbDetails = {
}

export type EditorType = 'lexical' | 'slate'

export type NextAppDetails = {
hasTopLevelLayout: boolean
isPayloadInstalled?: boolean
isSrcDir: boolean
nextAppDir?: string
nextConfigPath?: string
nextConfigType?: NextConfigType
}

export type NextConfigType = 'cjs' | 'esm'

0 comments on commit cf4a57c

Please sign in to comment.