chore: implement AST parsing of next config
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
packages/create-payload-app/src/lib/wrap-next-config.spec.ts
Normal file
38
packages/create-payload-app/src/lib/wrap-next-config.spec.ts
Normal file
@@ -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\)\)/)
|
||||
})
|
||||
})
|
||||
60
packages/create-payload-app/src/lib/wrap-next-config.ts
Normal file
60
packages/create-payload-app/src/lib/wrap-next-config.ts
Normal file
@@ -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<void> => {
|
||||
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')
|
||||
}
|
||||
@@ -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<PackageManager> {
|
||||
async function getPackageManager(args: CliArgs, projectDir: string): Promise<PackageManager> {
|
||||
let packageManager: PackageManager = 'npm'
|
||||
|
||||
if (args['--use-npm']) {
|
||||
@@ -136,15 +138,8 @@ async function getPackageManager(args: CliArgs): Promise<PackageManager> {
|
||||
} 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
|
||||
}
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -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==}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user