feat(cpa): strict true 😈 (#5587)
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
"detect-package-manager": "^3.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^3.2.0",
|
||||
"figures": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"globby": "11.1.0",
|
||||
"terminal-link": "^2.1.1"
|
||||
|
||||
@@ -128,9 +128,4 @@ describe('createProject', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Templates', () => {
|
||||
it.todo('Verify that all templates are valid')
|
||||
// Loop through all templates.ts that should have replacement comments, and verify that they are present
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ import { wrapNextConfig } from './wrap-next-config.js'
|
||||
|
||||
type InitNextArgs = Pick<CliArgs, '--debug'> & {
|
||||
dbType: DbType
|
||||
nextConfigPath: string
|
||||
nextAppDetails?: NextAppDetails
|
||||
packageManager: PackageManager
|
||||
projectDir: string
|
||||
useDistFiles?: boolean
|
||||
@@ -43,24 +43,16 @@ type InitNextResult =
|
||||
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const { dbType: dbType, packageManager, projectDir } = args
|
||||
|
||||
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
|
||||
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
|
||||
|
||||
// Get app directory. Could be top-level or src/app
|
||||
const nextAppDir = (
|
||||
await globby(['**/app'], {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
onlyDirectories: true,
|
||||
})
|
||||
)?.[0]
|
||||
const { hasTopLevelLayout, isSrcDir, nextAppDir } =
|
||||
nextAppDetails || (await getNextAppDetails(projectDir))
|
||||
|
||||
if (!nextAppDir) {
|
||||
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
|
||||
}
|
||||
|
||||
// Check for top-level layout.tsx
|
||||
const layoutPath = path.resolve(nextAppDir, 'layout.tsx')
|
||||
if (fs.existsSync(layoutPath)) {
|
||||
if (hasTopLevelLayout) {
|
||||
// Output directions for user to move all files from app to top-level directory named `(app)`
|
||||
p.log.warn(moveMessage({ nextAppDir, projectDir }))
|
||||
return {
|
||||
@@ -76,8 +68,7 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
...args,
|
||||
isSrcDir,
|
||||
nextAppDir,
|
||||
nextAppDetails,
|
||||
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
|
||||
})
|
||||
|
||||
@@ -115,7 +106,10 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
userTsConfig.compilerOptions = {}
|
||||
}
|
||||
|
||||
if (!userTsConfig.compilerOptions.paths?.['@payload-config']) {
|
||||
if (
|
||||
!userTsConfig.compilerOptions?.paths?.['@payload-config'] &&
|
||||
userTsConfig.compilerOptions?.paths
|
||||
) {
|
||||
userTsConfig.compilerOptions.paths = {
|
||||
...(userTsConfig.compilerOptions.paths || {}),
|
||||
'@payload-config': [`./${isSrcDir ? 'src/' : ''}payload.config.ts`],
|
||||
@@ -125,14 +119,23 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
}
|
||||
|
||||
function installAndConfigurePayload(
|
||||
args: InitNextArgs & {
|
||||
isSrcDir: boolean
|
||||
nextAppDir: string
|
||||
},
|
||||
args: InitNextArgs & { nextAppDetails: NextAppDetails; useDistFiles?: boolean },
|
||||
):
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
const { '--debug': debug, isSrcDir, nextAppDir, nextConfigPath, projectDir, useDistFiles } = args
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
projectDir,
|
||||
useDistFiles,
|
||||
} = args
|
||||
|
||||
if (!nextAppDir || !nextConfigPath) {
|
||||
return {
|
||||
reason: 'Could not find app directory or next.config.js',
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const logDebug = (message: string) => {
|
||||
if (debug) origDebug(message)
|
||||
@@ -164,7 +167,6 @@ function installAndConfigurePayload(
|
||||
logDebug(`Copying template files from ${templateFilesPath} to ${nextAppDir}`)
|
||||
|
||||
const templateSrcDir = path.resolve(templateFilesPath, isSrcDir ? '' : 'src')
|
||||
// const templateSrcDir = path.resolve(templateFilesPath)
|
||||
|
||||
logDebug(`templateSrcDir: ${templateSrcDir}`)
|
||||
logDebug(`nextAppDir: ${nextAppDir}`)
|
||||
@@ -218,3 +220,43 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
|
||||
|
||||
return { success: exitCode === 0 }
|
||||
}
|
||||
|
||||
type NextAppDetails = {
|
||||
hasTopLevelLayout: boolean
|
||||
isSrcDir: boolean
|
||||
nextAppDir?: string
|
||||
nextConfigPath?: string
|
||||
}
|
||||
|
||||
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,
|
||||
isSrcDir,
|
||||
nextConfigPath: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
let nextAppDir: string | undefined = (
|
||||
await globby(['**/app'], {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
onlyDirectories: true,
|
||||
})
|
||||
)?.[0]
|
||||
|
||||
if (!nextAppDir || nextAppDir.length === 0) {
|
||||
nextAppDir = undefined
|
||||
}
|
||||
|
||||
const hasTopLevelLayout = nextAppDir
|
||||
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
|
||||
: false
|
||||
|
||||
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath }
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ export function parseAndModifyConfigContent(content: string): {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration) {
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration?.loc,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
@@ -65,13 +65,13 @@ export function parseAndModifyConfigContent(content: string): {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warnUserWrapNotSuccessful()
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warnUserWrapNotSuccessful()
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function writeEnvFile(args: {
|
||||
databaseUri: string
|
||||
payloadSecret: string
|
||||
projectDir: string
|
||||
template: ProjectTemplate
|
||||
template?: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { cliArgs, databaseUri, payloadSecret, projectDir, template } = args
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function writeEnvFile(args: {
|
||||
}
|
||||
|
||||
try {
|
||||
if (template.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
|
||||
if (template?.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
|
||||
// Parse .env file into key/value pairs
|
||||
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
|
||||
const envWithValues: string[] = envFile
|
||||
@@ -47,7 +47,7 @@ export async function writeEnvFile(args: {
|
||||
// Write new .env file
|
||||
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
|
||||
} else {
|
||||
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
const content = `DATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
await fs.outputFile(`${projectDir}/.env`, content)
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
|
||||
@@ -2,8 +2,9 @@ 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 globby from 'globby'
|
||||
import figures from 'figures'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, PackageManager } from './types.js'
|
||||
@@ -11,14 +12,20 @@ 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'
|
||||
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 { writeEnvFile } from './lib/write-env-file.js'
|
||||
import { error, info } from './utils/log.js'
|
||||
import { feedbackOutro, helpMessage, successMessage, successfulNextInit } from './utils/messages.js'
|
||||
import {
|
||||
feedbackOutro,
|
||||
helpMessage,
|
||||
moveMessage,
|
||||
successMessage,
|
||||
successfulNextInit,
|
||||
} from './utils/messages.js'
|
||||
|
||||
export class Main {
|
||||
args: CliArgs
|
||||
@@ -62,12 +69,6 @@ export class Main {
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
const initContext: {
|
||||
nextConfigPath: string | undefined
|
||||
} = {
|
||||
nextConfigPath: undefined,
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.args['--help']) {
|
||||
helpMessage()
|
||||
@@ -77,15 +78,14 @@ export class Main {
|
||||
// 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
|
||||
p.note("Welcome to Payload. Let's create a project!")
|
||||
|
||||
if (initContext.nextConfigPath) {
|
||||
this.args['--name'] = slugify(path.basename(path.dirname(initContext.nextConfigPath)))
|
||||
// Detect if inside Next.js project
|
||||
const nextAppDetails = await getNextAppDetails(process.cwd())
|
||||
const { hasTopLevelLayout, nextAppDir, nextConfigPath } = nextAppDetails
|
||||
|
||||
if (nextConfigPath) {
|
||||
this.args['--name'] = slugify(path.basename(path.dirname(nextConfigPath)))
|
||||
}
|
||||
|
||||
const projectName = await parseProjectName(this.args)
|
||||
@@ -96,13 +96,23 @@ export class Main {
|
||||
const packageManager = await getPackageManager(this.args, projectDir)
|
||||
|
||||
if (nextConfigPath) {
|
||||
// p.note('Detected existing Next.js project.')
|
||||
p.log.step(chalk.bold('Detected existing Next.js project.'))
|
||||
p.log.step(
|
||||
chalk.bold(`${chalk.bgBlack(` ${figures.triangleUp} Next.js `)} project detected!`),
|
||||
)
|
||||
|
||||
const proceed = await p.confirm({
|
||||
initialValue: true,
|
||||
message: 'Install Payload in this project?',
|
||||
message: chalk.bold(`Install ${chalk.green('Payload')} in this project?`),
|
||||
})
|
||||
if (p.isCancel(proceed) || !proceed) {
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Check for top-level layout.tsx
|
||||
if (nextAppDir && hasTopLevelLayout) {
|
||||
p.log.warn(moveMessage({ nextAppDir, projectDir }))
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
@@ -111,12 +121,13 @@ export class Main {
|
||||
const result = await initNext({
|
||||
...this.args,
|
||||
dbType: dbDetails.type,
|
||||
nextConfigPath,
|
||||
nextAppDetails,
|
||||
packageManager,
|
||||
projectDir,
|
||||
})
|
||||
|
||||
if (result.success === false) {
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -127,7 +138,14 @@ export class Main {
|
||||
},
|
||||
})
|
||||
|
||||
info('Payload project successfully created!')
|
||||
await writeEnvFile({
|
||||
cliArgs: this.args,
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret: generateSecret(),
|
||||
projectDir,
|
||||
})
|
||||
|
||||
info('Payload project successfully initialized!')
|
||||
p.note(successfulNextInit(), chalk.bgGreen(chalk.black(' Documentation ')))
|
||||
p.outro(feedbackOutro())
|
||||
return
|
||||
@@ -146,6 +164,7 @@ export class Main {
|
||||
const template = await parseTemplate(this.args, validTemplates)
|
||||
if (!template) {
|
||||
p.log.error('Invalid template given')
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import path from 'path'
|
||||
export function copyRecursiveSync(src: string, dest: string, debug?: boolean) {
|
||||
const exists = fs.existsSync(src)
|
||||
const stats = exists && fs.statSync(src)
|
||||
const isDirectory = exists && stats.isDirectory()
|
||||
const isDirectory = exists && stats !== false && stats.isDirectory()
|
||||
if (isDirectory) {
|
||||
fs.mkdirSync(dest, { recursive: true })
|
||||
fs.readdirSync(src).forEach((childItemName) => {
|
||||
|
||||
@@ -84,18 +84,18 @@ export function moveMessage(args: { nextAppDir: string; projectDir: string }): s
|
||||
return `
|
||||
${header('Next Steps:')}
|
||||
|
||||
Payload does not support a top-level layout.tsx file in your Next.js app directory.
|
||||
Payload does not support a top-level layout.tsx file in the app directory.
|
||||
|
||||
${chalk.bold('To continue:')}
|
||||
|
||||
Move all files from ./${relativePath} to a named directory such as ${chalk.bold('(app)')}
|
||||
Move all files from ./${relativePath} to a named directory such as ./${relativePath}/${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')}`
|
||||
return `${chalk.bgCyan(chalk.black(' Have feedback? '))} Visit us on ${createTerminalLink('GitHub', 'https://github.com/payloadcms/payload')}.`
|
||||
}
|
||||
|
||||
// Create terminalLink with fallback for unsupported terminals
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true,
|
||||
},
|
||||
"exclude": ["dist", "build", "tests", "test", "node_modules", ".eslintrc.js"],
|
||||
"include": ["src/**/*.ts", "src/**/*.spec.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"]
|
||||
|
||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -326,8 +326,8 @@ importers:
|
||||
specifier: ^5.0.0
|
||||
version: 5.1.1
|
||||
figures:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
fs-extra:
|
||||
specifier: ^9.0.1
|
||||
version: 9.1.0
|
||||
@@ -10041,6 +10041,14 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
dev: true
|
||||
|
||||
/figures@6.1.0:
|
||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
is-unicode-supported: 2.0.0
|
||||
dev: false
|
||||
|
||||
/file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
@@ -11409,7 +11417,6 @@ packages:
|
||||
/is-unicode-supported@2.0.0:
|
||||
resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==}
|
||||
engines: {node: '>=18'}
|
||||
dev: true
|
||||
|
||||
/is-weakmap@2.0.1:
|
||||
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as CommentJson from 'comment-json'
|
||||
import { initNext } from 'create-payload-app/commands'
|
||||
import execa from 'execa'
|
||||
import fs from 'fs'
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import shelljs from 'shelljs'
|
||||
import tempy from 'tempy'
|
||||
@@ -66,9 +67,8 @@ describe('create-payload-app', () => {
|
||||
const firstResult = await initNext({
|
||||
'--debug': true,
|
||||
projectDir,
|
||||
nextConfigPath: path.resolve(projectDir, 'next.config.mjs'),
|
||||
dbType: 'mongodb',
|
||||
useDistFiles: true, // create-payload-app/dist/app/(payload)
|
||||
useDistFiles: true, // create-payload-app/dist/template
|
||||
packageManager: 'pnpm',
|
||||
})
|
||||
|
||||
@@ -91,7 +91,6 @@ describe('create-payload-app', () => {
|
||||
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',
|
||||
@@ -117,11 +116,22 @@ describe('create-payload-app', () => {
|
||||
const userTsConfig = CommentJson.parse(userTsConfigContent) as {
|
||||
compilerOptions?: CompilerOptions
|
||||
}
|
||||
|
||||
// Check that `@payload-config` path is added to tsconfig
|
||||
expect(userTsConfig.compilerOptions.paths?.['@payload-config']).toStrictEqual([
|
||||
`./${result.isSrcDir ? 'src/' : ''}payload.config.ts`,
|
||||
])
|
||||
|
||||
// TODO: Start the Next.js app and check if it runs
|
||||
// Payload dependencies should be installed
|
||||
const packageJson = fse.readJsonSync(path.resolve(projectDir, 'package.json')) as {
|
||||
dependencies: Record<string, string>
|
||||
}
|
||||
expect(packageJson.dependencies).toMatchObject({
|
||||
payload: expect.any(String),
|
||||
'@payloadcms/db-mongodb': expect.any(String),
|
||||
'@payloadcms/richtext-lexical': expect.any(String),
|
||||
'@payloadcms/next': expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user