chore: implement AST parsing of next config
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"comment-json": "^4.2.3",
|
"comment-json": "^4.2.3",
|
||||||
"degit": "^2.8.4",
|
"degit": "^2.8.4",
|
||||||
"detect-package-manager": "^3.0.1",
|
"detect-package-manager": "^3.0.1",
|
||||||
|
"esprima": "^4.0.1",
|
||||||
"execa": "^5.0.0",
|
"execa": "^5.0.0",
|
||||||
"figures": "^3.2.0",
|
"figures": "^3.2.0",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
@@ -39,9 +40,17 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/command-exists": "^1.2.0",
|
"@types/command-exists": "^1.2.0",
|
||||||
"@types/degit": "^2.8.3",
|
"@types/degit": "^2.8.3",
|
||||||
|
"@types/esprima": "^4.0.6",
|
||||||
"@types/fs-extra": "^9.0.12",
|
"@types/fs-extra": "^9.0.12",
|
||||||
"@types/jest": "^27.0.3",
|
"@types/jest": "^27.0.3",
|
||||||
"@types/node": "^16.6.2",
|
"@types/node": "^16.6.2",
|
||||||
"@types/prompts": "^2.4.1"
|
"@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 */
|
/* eslint-disable no-console */
|
||||||
import slugify from '@sindresorhus/slugify'
|
import slugify from '@sindresorhus/slugify'
|
||||||
import arg from 'arg'
|
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'
|
import type { CliArgs, PackageManager } from './types.js'
|
||||||
|
|
||||||
@@ -84,8 +85,9 @@ export class Main {
|
|||||||
const validTemplates = getValidTemplates()
|
const validTemplates = getValidTemplates()
|
||||||
const template = await parseTemplate(this.args, validTemplates)
|
const template = await parseTemplate(this.args, validTemplates)
|
||||||
|
|
||||||
const projectDir = projectName === '.' ? process.cwd() : `./${slugify(projectName)}`
|
const projectDir =
|
||||||
const packageManager = await getPackageManager(this.args)
|
projectName === '.' ? path.basename(process.cwd()) : `./${slugify(projectName)}`
|
||||||
|
const packageManager = await getPackageManager(this.args, projectDir)
|
||||||
|
|
||||||
if (template.type !== 'plugin') {
|
if (template.type !== 'plugin') {
|
||||||
const dbDetails = await selectDb(this.args, projectName)
|
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'
|
let packageManager: PackageManager = 'npm'
|
||||||
|
|
||||||
if (args['--use-npm']) {
|
if (args['--use-npm']) {
|
||||||
@@ -136,15 +138,8 @@ async function getPackageManager(args: CliArgs): Promise<PackageManager> {
|
|||||||
} else if (args['--use-pnpm']) {
|
} else if (args['--use-pnpm']) {
|
||||||
packageManager = 'pnpm'
|
packageManager = 'pnpm'
|
||||||
} else {
|
} else {
|
||||||
try {
|
const detected = await detect({ cwd: projectDir })
|
||||||
if (await commandExists('yarn')) {
|
packageManager = detected || 'npm'
|
||||||
packageManager = 'yarn'
|
|
||||||
} else if (await commandExists('pnpm')) {
|
|
||||||
packageManager = 'pnpm'
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
packageManager = 'npm'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return packageManager
|
return packageManager
|
||||||
}
|
}
|
||||||
|
|||||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -316,6 +316,9 @@ importers:
|
|||||||
detect-package-manager:
|
detect-package-manager:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
|
esprima:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
execa:
|
execa:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.1.1
|
version: 5.1.1
|
||||||
@@ -347,6 +350,9 @@ importers:
|
|||||||
'@types/degit':
|
'@types/degit':
|
||||||
specifier: ^2.8.3
|
specifier: ^2.8.3
|
||||||
version: 2.8.6
|
version: 2.8.6
|
||||||
|
'@types/esprima':
|
||||||
|
specifier: ^4.0.6
|
||||||
|
version: 4.0.6
|
||||||
'@types/fs-extra':
|
'@types/fs-extra':
|
||||||
specifier: ^9.0.12
|
specifier: ^9.0.12
|
||||||
version: 9.0.13
|
version: 9.0.13
|
||||||
@@ -5950,6 +5956,12 @@ packages:
|
|||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
'@types/json-schema': 7.0.15
|
'@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:
|
/@types/estree@1.0.5:
|
||||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { CompilerOptions } from 'typescript'
|
import type { CompilerOptions } from 'typescript'
|
||||||
|
|
||||||
import * as CommentJson from 'comment-json'
|
import * as CommentJson from 'comment-json'
|
||||||
|
import { initNext } from 'create-payload-app/commands'
|
||||||
import execa from 'execa'
|
import execa from 'execa'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -9,8 +10,6 @@ import tempy from 'tempy'
|
|||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
|
|
||||||
import { initNext } from '../../packages/create-payload-app/src/lib/init-next.js'
|
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user