chore: edge case for parsing next config

This commit is contained in:
Elliot DeNolf
2024-03-26 00:07:59 -04:00
parent 5cf49aa166
commit ff65f10c2f
2 changed files with 67 additions and 5 deletions

View File

@@ -21,6 +21,13 @@ export default someFunc(
)
`
const nextConfigExportNamedDefault = `const nextConfig = {
// Your Next.js config here
}
const wrapped = someFunc(asdf)
export { wrapped as default }
`
describe('parseAndInsertWithPayload', () => {
it('should parse the default next config', () => {
const modifiedConfig = parseAndInsertWithPayload(defaultNextConfig)
@@ -35,4 +42,10 @@ describe('parseAndInsertWithPayload', () => {
const modifiedConfig = parseAndInsertWithPayload(nextConfigWithFuncMultiline)
expect(modifiedConfig).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const modifiedConfig = parseAndInsertWithPayload(nextConfigExportNamedDefault)
expect(modifiedConfig).toHaveProperty('error')
})
})

View File

@@ -7,25 +7,54 @@ 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}`)
throw new Error(`No Next config found at ${args.projectDir}`)
}
const configPath = path.resolve(args.projectDir, foundConfig)
const configContent = fs.readFileSync(configPath, 'utf8')
const newConfig = parseAndInsertWithPayload(configContent)
if (typeof newConfig === 'object') {
throw new Error(newConfig.error)
}
fs.writeFileSync(configPath, newConfig)
}
export function parseAndInsertWithPayload(content: string) {
export function parseAndInsertWithPayload(content: string): { error: string } | string {
const ast = parseModule(content, { loc: true })
const statement = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
| Directive
| undefined
if (!statement) {
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
| ExportNamedDeclaration
| undefined
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
}
return insertBeforeAndAfter(content, statement.declaration?.loc)
if (exportDefaultDeclaration) {
return insertBeforeAndAfter(content, exportDefaultDeclaration.declaration?.loc)
} else if (exportNamedDeclaration) {
const exportSpecifier = exportNamedDeclaration.specifiers.find(
(s) =>
s.type === 'ExportSpecifier' &&
s.exported?.name === 'default' &&
s.local?.type === 'Identifier' &&
s.local?.name,
)
if (exportSpecifier) {
// TODO: Improve with this example and/or link to docs
return {
error: `Automatic wrapping of named exports as default not supported yet.
Please manually wrap your Next config with the withPayload function`,
}
}
} else {
throw new Error('Could not automatically wrap next.config.js with withPayload')
}
return insertBeforeAndAfter(content, exportDefaultDeclaration.declaration?.loc)
}
type Directive = {
@@ -34,6 +63,26 @@ type Directive = {
}
}
type ExportNamedDeclaration = {
declaration: null
loc: Loc
specifiers: {
exported: {
loc: Loc
name: string
type: string
}
loc: Loc
local: {
loc: Loc
name: string
type: string
}
type: string
}[]
type: string
}
type Loc = {
end: { column: number; line: number }
start: { column: number; line: number }