Skip to content

Commit 160bbf0

Browse files
authored
fix(gatsby): Improve get-config-file error handling (#35776)
1 parent c3a3f68 commit 160bbf0

File tree

9 files changed

+266
-14
lines changed

9 files changed

+266
-14
lines changed

packages/gatsby-cli/src/structured-errors/error-map.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,13 @@ const errors = {
370370
level: Level.ERROR,
371371
category: ErrorCategory.USER,
372372
},
373+
"10127": {
374+
text: (context): string =>
375+
`Your "${context.configName}.ts" file failed to compile to "${context.configName}.js. Please run "gatsby clean" and try again.\n\nIf the issue persists, please open an issue with a reproduction at https://github.com/gatsbyjs/gatsby/issues/new for more help."`,
376+
type: Type.CONFIG,
377+
level: Level.ERROR,
378+
category: ErrorCategory.USER,
379+
},
373380
"10226": {
374381
text: (context): string =>
375382
[
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: `compiled`,
4+
siteUrl: `https://www.yourdomain.tld`,
5+
},
6+
plugins: [],
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: `uncompiled`,
4+
siteUrl: `https://www.yourdomain.tld`,
5+
},
6+
plugins: [],
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: `near-match`,
4+
siteUrl: `https://www.yourdomain.tld`,
5+
},
6+
plugins: [],
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: `in-src`,
4+
siteUrl: `https://www.yourdomain.tld`,
5+
},
6+
plugins: [],
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { GatsbyConfig } from "gatsby"
2+
3+
const config: GatsbyConfig = {
4+
siteMetadata: {
5+
title: `ts`,
6+
siteUrl: `https://www.yourdomain.tld`,
7+
},
8+
plugins: [],
9+
}
10+
11+
export default config
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const something = require(`some-place-that-does-not-exist`)
2+
3+
module.exports = {
4+
siteMetadata: {
5+
title: `user-require-error`,
6+
siteUrl: `https://www.yourdomain.tld`,
7+
},
8+
plugins: [],
9+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import path from "path"
2+
import { isNearMatch, getConfigFile } from "../get-config-file"
3+
import { testRequireError } from "../../utils/test-require-error"
4+
import reporter from "gatsby-cli/lib/reporter"
5+
6+
jest.mock(`path`, () => {
7+
const actual = jest.requireActual(`path`)
8+
return {
9+
...actual,
10+
join: jest.fn((...arg) => actual.join(...arg)),
11+
}
12+
})
13+
14+
jest.mock(`../../utils/test-require-error`, () => {
15+
return {
16+
testRequireError: jest.fn(),
17+
}
18+
})
19+
20+
jest.mock(`../../utils/parcel/compile-gatsby-files`, () => {
21+
const actual = jest.requireActual(`../../utils/parcel/compile-gatsby-files`)
22+
return {
23+
...actual,
24+
COMPILED_CACHE_DIR: `compiled`, // .cache is git ignored
25+
}
26+
})
27+
28+
jest.mock(`gatsby-cli/lib/reporter`, () => {
29+
return {
30+
panic: jest.fn(),
31+
}
32+
})
33+
34+
const pathJoinMock = path.join as jest.MockedFunction<typeof path.join>
35+
36+
const testRequireErrorMock = testRequireError as jest.MockedFunction<
37+
typeof testRequireError
38+
>
39+
40+
const reporterPanicMock = reporter.panic as jest.MockedFunction<
41+
typeof reporter.panic
42+
>
43+
44+
describe(`isNearMatch`, () => {
45+
it(`should NOT find a near match if file name is undefined`, () => {
46+
const nearMatchA = isNearMatch(undefined, `gatsby-config`, 1)
47+
expect(nearMatchA).toBeFalse()
48+
})
49+
50+
it(`should calculate near matches based on distance`, () => {
51+
const nearMatchA = isNearMatch(`gatsby-config`, `gatsby-conf`, 2)
52+
const nearMatchB = isNearMatch(`gatsby-config`, `gatsby-configur`, 2)
53+
expect(nearMatchA).toBeTrue()
54+
expect(nearMatchB).toBeTrue()
55+
})
56+
})
57+
58+
// Separate config directories so cases can be tested separately
59+
const dir = path.resolve(__dirname, `../__mocks__/get-config`)
60+
const compiledDir = `${dir}/compiled-dir`
61+
const userRequireDir = `${dir}/user-require-dir`
62+
const tsDir = `${dir}/ts-dir`
63+
const nearMatchDir = `${dir}/near-match-dir`
64+
const srcDir = `${dir}/src-dir`
65+
66+
describe(`getConfigFile`, () => {
67+
beforeEach(() => {
68+
reporterPanicMock.mockClear()
69+
})
70+
71+
it(`should get an uncompiled gatsby-config.js`, async () => {
72+
const { configModule, configFilePath } = await getConfigFile(
73+
dir,
74+
`gatsby-config`
75+
)
76+
expect(configFilePath).toBe(path.join(dir, `gatsby-config.js`))
77+
expect(configModule.siteMetadata.title).toBe(`uncompiled`)
78+
})
79+
80+
it(`should get a compiled gatsby-config.js`, async () => {
81+
const { configModule, configFilePath } = await getConfigFile(
82+
compiledDir,
83+
`gatsby-config`
84+
)
85+
expect(configFilePath).toBe(
86+
path.join(compiledDir, `compiled`, `gatsby-config.js`)
87+
)
88+
expect(configModule.siteMetadata.title).toBe(`compiled`)
89+
})
90+
91+
it(`should handle user require errors found in compiled gatsby-config.js`, async () => {
92+
await getConfigFile(userRequireDir, `gatsby-config`)
93+
94+
expect(reporterPanicMock).toBeCalledWith({
95+
id: `11902`,
96+
error: expect.toBeObject(),
97+
context: {
98+
configName: `gatsby-config`,
99+
message: expect.toBeString(),
100+
},
101+
})
102+
})
103+
104+
it(`should handle non-require errors`, async () => {
105+
testRequireErrorMock.mockImplementationOnce(() => false)
106+
107+
await getConfigFile(nearMatchDir, `gatsby-config`)
108+
109+
expect(reporterPanicMock).toBeCalledWith({
110+
id: `10123`,
111+
error: expect.toBeObject(),
112+
context: {
113+
configName: `gatsby-config`,
114+
message: expect.toBeString(),
115+
},
116+
})
117+
})
118+
119+
it(`should handle case where gatsby-config.ts exists but no compiled gatsby-config.js exists`, async () => {
120+
// Force outer and inner errors so we can hit the code path that checks if gatsby-config.ts exists
121+
pathJoinMock
122+
.mockImplementationOnce(() => `force-outer-error`)
123+
.mockImplementationOnce(() => `force-inner-error`)
124+
testRequireErrorMock.mockImplementationOnce(() => true)
125+
126+
await getConfigFile(tsDir, `gatsby-config`)
127+
128+
expect(reporterPanicMock).toBeCalledWith({
129+
id: `10127`,
130+
error: expect.toBeObject(),
131+
context: {
132+
configName: `gatsby-config`,
133+
},
134+
})
135+
})
136+
137+
it(`should handle near matches`, async () => {
138+
testRequireErrorMock.mockImplementationOnce(() => true)
139+
140+
await getConfigFile(nearMatchDir, `gatsby-config`)
141+
142+
expect(reporterPanicMock).toBeCalledWith({
143+
id: `10124`,
144+
error: expect.toBeObject(),
145+
context: {
146+
configName: `gatsby-config`,
147+
nearMatch: `gatsby-confi.js`,
148+
},
149+
})
150+
})
151+
152+
it(`should handle gatsby config incorrectly located in src dir`, async () => {
153+
testRequireErrorMock.mockImplementationOnce(() => true)
154+
155+
await getConfigFile(srcDir, `gatsby-config`)
156+
157+
expect(reporterPanicMock).toBeCalledWith({
158+
id: `10125`,
159+
context: {
160+
configName: `gatsby-config`,
161+
},
162+
})
163+
})
164+
})

packages/gatsby/src/bootstrap/get-config-file.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import path from "path"
66
import { sync as existsSync } from "fs-exists-cached"
77
import { COMPILED_CACHE_DIR } from "../utils/parcel/compile-gatsby-files"
88

9-
function isNearMatch(
9+
export function isNearMatch(
1010
fileName: string | undefined,
1111
configName: string,
1212
distance: number
@@ -27,8 +27,8 @@ export async function getConfigFile(
2727
let configFilePath = ``
2828
let configModule: any
2929

30+
// Attempt to find compiled gatsby-config.js in .cache/compiled/gatsby-config.js
3031
try {
31-
// Try .cache/compiled/gatsby-config first
3232
configPath = path.join(`${siteDirectory}/${COMPILED_CACHE_DIR}`, configName)
3333
configFilePath = require.resolve(configPath)
3434
configModule = require(configFilePath)
@@ -42,6 +42,7 @@ export async function getConfigFile(
4242
const isThisFileRequireError =
4343
outerError?.requireStack?.[0]?.includes(`get-config-file`) ?? true
4444

45+
// User's module require error inside gatsby-config.js
4546
if (!(isModuleNotFoundError && isThisFileRequireError)) {
4647
report.panic({
4748
id: `11902`,
@@ -53,19 +54,14 @@ export async function getConfigFile(
5354
})
5455
}
5556

56-
// Fallback to regular rootDir gatsby-config
57+
// Attempt to find uncompiled gatsby-config.js in root dir
5758
configPath = path.join(siteDirectory, configName)
59+
5860
try {
5961
configFilePath = require.resolve(configPath)
6062
configModule = require(configFilePath)
6163
} catch (innerError) {
62-
// Only then hard fail
63-
const nearMatch = await fs.readdir(siteDirectory).then(files =>
64-
files.find(file => {
65-
const fileName = file.split(siteDirectory).pop()
66-
return isNearMatch(fileName, configName, distance)
67-
})
68-
)
64+
// Some other error that is not a require error
6965
if (!testRequireError(configPath, innerError)) {
7066
report.panic({
7167
id: `10123`,
@@ -75,7 +71,43 @@ export async function getConfigFile(
7571
message: innerError.message,
7672
},
7773
})
78-
} else if (nearMatch) {
74+
}
75+
76+
const files = await fs.readdir(siteDirectory)
77+
78+
let tsConfig = false
79+
let nearMatch = ``
80+
81+
for (const file of files) {
82+
if (tsConfig || nearMatch) {
83+
break
84+
}
85+
86+
const { name, ext } = path.parse(file)
87+
88+
if (name === configName && ext === `.ts`) {
89+
tsConfig = true
90+
break
91+
}
92+
93+
if (isNearMatch(name, configName, distance)) {
94+
nearMatch = file
95+
}
96+
}
97+
98+
// gatsby-config.ts exists but compiled gatsby-config.js does not
99+
if (tsConfig) {
100+
report.panic({
101+
id: `10127`,
102+
error: innerError,
103+
context: {
104+
configName,
105+
},
106+
})
107+
}
108+
109+
// gatsby-config is misnamed
110+
if (nearMatch) {
79111
report.panic({
80112
id: `10124`,
81113
error: innerError,
@@ -84,9 +116,10 @@ export async function getConfigFile(
84116
nearMatch,
85117
},
86118
})
87-
} else if (
88-
existsSync(path.join(siteDirectory, `src`, configName + `.js`))
89-
) {
119+
}
120+
121+
// gatsby-config.js is incorrectly located in src/gatsby-config.js
122+
if (existsSync(path.join(siteDirectory, `src`, configName + `.js`))) {
90123
report.panic({
91124
id: `10125`,
92125
context: {

0 commit comments

Comments
 (0)