diff --git a/packages/create-payload-app/package.json b/packages/create-payload-app/package.json index 5d91ef402..e2ff2895d 100644 --- a/packages/create-payload-app/package.json +++ b/packages/create-payload-app/package.json @@ -27,6 +27,7 @@ "comment-json": "^4.2.3", "degit": "^2.8.4", "detect-package-manager": "^3.0.1", + "esprima": "^4.0.1", "execa": "^5.0.0", "figures": "^3.2.0", "fs-extra": "^9.0.1", @@ -39,9 +40,17 @@ "devDependencies": { "@types/command-exists": "^1.2.0", "@types/degit": "^2.8.3", + "@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" + }, + "exports": { + "./commands": { + "import": "./src/lib/init-next.ts", + "require": "./src/lib/init-next.ts", + "types": "./src/lib/init-next.ts" + } } } diff --git a/packages/create-payload-app/src/lib/wrap-next-config.spec.ts b/packages/create-payload-app/src/lib/wrap-next-config.spec.ts new file mode 100644 index 000000000..c9dd23525 --- /dev/null +++ b/packages/create-payload-app/src/lib/wrap-next-config.spec.ts @@ -0,0 +1,38 @@ +import { parseAndInsertWithPayload } from './wrap-next-config' + +const defaultNextConfig = `/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; +` + +const nextConfigWithFunc = `const nextConfig = { + // Your Next.js config here +} + +export default someFunc(nextConfig) +` +const nextConfigWithFuncMultiline = `const nextConfig = { + // Your Next.js config here +} + +export default someFunc( + nextConfig +) +` + +describe('parseAndInsertWithPayload', () => { + it('should parse the default next config', () => { + const modifiedConfig = parseAndInsertWithPayload(defaultNextConfig) + expect(modifiedConfig).toMatch(/withPayload\(nextConfig\)/) + }) + it('should parse the config with a function', () => { + const modifiedConfig = parseAndInsertWithPayload(nextConfigWithFunc) + expect(modifiedConfig).toMatch(/withPayload\(someFunc\(nextConfig\)\)/) + }) + + it('should parse the config with a function on a new line', () => { + const modifiedConfig = parseAndInsertWithPayload(nextConfigWithFuncMultiline) + expect(modifiedConfig).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/) + }) +}) diff --git a/packages/create-payload-app/src/lib/wrap-next-config.ts b/packages/create-payload-app/src/lib/wrap-next-config.ts new file mode 100644 index 000000000..9ce565a58 --- /dev/null +++ b/packages/create-payload-app/src/lib/wrap-next-config.ts @@ -0,0 +1,60 @@ +import { parseModule } from 'esprima' +import fs from 'fs' +import globby from 'globby' +import path from 'path' + +export const wrapNextConfig = async (args: { projectDir: string }): Promise => { + const foundConfig = (await globby('next.config.*js', { cwd: args.projectDir }))?.[0] + + if (!foundConfig) { + throw new Error(`No next.config.js found at ${args.projectDir}`) + } + const configPath = path.resolve(args.projectDir, foundConfig) + const configContent = fs.readFileSync(configPath, 'utf8') + const newConfig = parseAndInsertWithPayload(configContent) + fs.writeFileSync(configPath, newConfig) +} + +export function parseAndInsertWithPayload(content: string) { + const ast = parseModule(content, { loc: true }) + const statement = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as + | Directive + | undefined + + if (!statement) { + throw new Error('Could not find ExportDefaultDeclaration in next.config.js') + } + + return insertBeforeAndAfter(content, statement.declaration?.loc) +} + +type Directive = { + declaration?: { + loc: Loc + } +} + +type Loc = { + end: { column: number; line: number } + start: { column: number; line: number } +} + +function insertBeforeAndAfter(content: string, loc: Loc) { + const { end, start } = loc + const lines = content.split('\n') + + const insert = (line: string, column: number, text: string) => { + return line.slice(0, column) + text + line.slice(column) + } + + // insert ) after end + lines[end.line - 1] = insert(lines[end.line - 1], end.column, ')') + // insert withPayload before start + if (start.line === end.line) { + lines[end.line - 1] = insert(lines[end.line - 1], start.column, 'withPayload(') + } else { + lines[start.line - 1] = insert(lines[start.line - 1], start.column, 'withPayload(') + } + + return lines.join('\n') +} diff --git a/packages/create-payload-app/src/main.ts b/packages/create-payload-app/src/main.ts index f297bc2f6..d7616dd86 100644 --- a/packages/create-payload-app/src/main.ts +++ b/packages/create-payload-app/src/main.ts @@ -1,7 +1,8 @@ /* eslint-disable no-console */ import slugify from '@sindresorhus/slugify' import arg from 'arg' -import commandExists from 'command-exists' +import { detect } from 'detect-package-manager' +import path from 'path' import type { CliArgs, PackageManager } from './types.js' @@ -84,8 +85,9 @@ export class Main { const validTemplates = getValidTemplates() const template = await parseTemplate(this.args, validTemplates) - const projectDir = projectName === '.' ? process.cwd() : `./${slugify(projectName)}` - const packageManager = await getPackageManager(this.args) + const projectDir = + projectName === '.' ? path.basename(process.cwd()) : `./${slugify(projectName)}` + const packageManager = await getPackageManager(this.args, projectDir) if (template.type !== 'plugin') { const dbDetails = await selectDb(this.args, projectName) @@ -126,7 +128,7 @@ export class Main { } } -async function getPackageManager(args: CliArgs): Promise { +async function getPackageManager(args: CliArgs, projectDir: string): Promise { let packageManager: PackageManager = 'npm' if (args['--use-npm']) { @@ -136,15 +138,8 @@ async function getPackageManager(args: CliArgs): Promise { } else if (args['--use-pnpm']) { packageManager = 'pnpm' } else { - try { - if (await commandExists('yarn')) { - packageManager = 'yarn' - } else if (await commandExists('pnpm')) { - packageManager = 'pnpm' - } - } catch (error: unknown) { - packageManager = 'npm' - } + const detected = await detect({ cwd: projectDir }) + packageManager = detected || 'npm' } return packageManager } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccb89e704..701e3d381 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -316,6 +316,9 @@ importers: detect-package-manager: specifier: ^3.0.1 version: 3.0.1 + esprima: + specifier: ^4.0.1 + version: 4.0.1 execa: specifier: ^5.0.0 version: 5.1.1 @@ -347,6 +350,9 @@ importers: '@types/degit': specifier: ^2.8.3 version: 2.8.6 + '@types/esprima': + specifier: ^4.0.6 + version: 4.0.6 '@types/fs-extra': specifier: ^9.0.12 version: 9.0.13 @@ -5950,6 +5956,12 @@ packages: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 + /@types/esprima@4.0.6: + resolution: {integrity: sha512-lIk+kSt9lGv5hxK6aZNjiUEGZqKmOTpmg0tKiJQI+Ow98fLillxsiZNik5+RcP7mXL929KiTH/D9jGtpDlMbVw==} + dependencies: + '@types/estree': 1.0.5 + dev: true + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} diff --git a/test/create-payload-app/int.spec.ts b/test/create-payload-app/int.spec.ts index e1256f562..f39dbd56a 100644 --- a/test/create-payload-app/int.spec.ts +++ b/test/create-payload-app/int.spec.ts @@ -1,6 +1,7 @@ import type { CompilerOptions } from 'typescript' import * as CommentJson from 'comment-json' +import { initNext } from 'create-payload-app/commands' import execa from 'execa' import fs from 'fs' import path from 'path' @@ -9,8 +10,6 @@ import tempy from 'tempy' import { fileURLToPath } from 'url' import { promisify } from 'util' -import { initNext } from '../../packages/create-payload-app/src/lib/init-next.js' - const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename)