fix(cpa): ast parse error handling (#5793)
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
"comment-json": "^4.2.3",
|
||||
"degit": "^2.8.4",
|
||||
"detect-package-manager": "^3.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"esprima-next": "^6.0.3",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
|
||||
@@ -17,6 +17,11 @@ export default someFunc(
|
||||
nextConfigExportNamedDefault: `const nextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
export { wrapped as default };
|
||||
`,
|
||||
nextConfigWithSpread: `const nextConfig = {
|
||||
...someConfig,
|
||||
};
|
||||
export default nextConfig;
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -38,6 +43,9 @@ module.exports = someFunc(
|
||||
nextConfigExportNamedDefault: `const nextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
module.exports = wrapped;
|
||||
`,
|
||||
nextConfigWithSpread: `const nextConfig = { ...someConfig };
|
||||
module.exports = nextConfig;
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -70,6 +78,15 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
@@ -129,5 +146,14 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Program } from 'esprima-next'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import { parseModule } from 'esprima'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
|
||||
import { warning } from '../utils/log.js'
|
||||
@@ -38,16 +40,29 @@ export function parseAndModifyConfigContent(
|
||||
configType: NextConfigType,
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
const ast = parseModule(content, { loc: true })
|
||||
|
||||
let ast: Program | undefined
|
||||
try {
|
||||
ast = parseModule(content, { loc: true })
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
|
||||
| Directive
|
||||
| undefined
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
|
||||
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
|
||||
| ExportNamedDeclaration
|
||||
| undefined
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
@@ -57,7 +72,6 @@ export function parseAndModifyConfigContent(
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
configType,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
@@ -91,12 +105,12 @@ export function parseAndModifyConfigContent(
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === 'ExpressionStatement' &&
|
||||
p.expression?.type === 'AssignmentExpression' &&
|
||||
p.expression.left?.type === 'MemberExpression' &&
|
||||
p.expression.left.object?.type === 'Identifier' &&
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === 'Identifier' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
@@ -105,7 +119,6 @@ export function parseAndModifyConfigContent(
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
configType,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
@@ -174,7 +187,7 @@ type Loc = {
|
||||
start: { column: number; line: number }
|
||||
}
|
||||
|
||||
function insertBeforeAndAfter(content: string, loc: Loc, configType: NextConfigType) {
|
||||
function insertBeforeAndAfter(content: string, loc: Loc) {
|
||||
const { end, start } = loc
|
||||
const lines = content.split('\n')
|
||||
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -319,9 +319,9 @@ importers:
|
||||
detect-package-manager:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
esprima:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
esprima-next:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
execa:
|
||||
specifier: ^5.0.0
|
||||
version: 5.1.1
|
||||
@@ -9396,6 +9396,12 @@ packages:
|
||||
acorn-jsx: 5.3.2(acorn@8.11.3)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
/esprima-next@6.0.3:
|
||||
resolution: {integrity: sha512-fVfE+9qIOJSbS3AR7roIuL0gCeS+tC86bJV9GlJtwXCRoo67q6tsGGUjThW+JtR5IQSShnHqaDqX8D0IYDfRGA==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
Reference in New Issue
Block a user