Skip to content

Commit f9f6538

Browse files
authored
fix: resolve config modules with ESM createRequire (#1082)
ES modules do not have require() available, we must construct it first. This fixes the CLI when passing `--config my-config-package`, broken in v12.0.0. The tests didn't catch this because Jest still doesn't support mocking ESM, and there's really no way to write a test for this right now.
1 parent 893f3d7 commit f9f6538

7 files changed

+77
-14
lines changed

jest.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
const config = {
22
collectCoverage: true,
3-
collectCoverageFrom: ['lib/**/*.js'],
3+
collectCoverageFrom: [
4+
'lib/**/*.js',
5+
// Avoid ESM import.meta parse error.
6+
// (Can't measure coverage anyway, it's always mocked)
7+
'!lib/resolveConfig.js',
8+
],
49
moduleDirectories: ['node_modules'],
510
setupFiles: ['./testSetup.js'],
611
snapshotSerializers: ['jest-snapshot-serializer-ansi'],

lib/loadConfig.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import debug from 'debug'
66
import { lilconfig } from 'lilconfig'
77
import YAML from 'yaml'
88

9+
import { resolveConfig } from './resolveConfig.js'
10+
911
const debugLog = debug('lint-staged:loadConfig')
1012

1113
/**
@@ -49,14 +51,6 @@ const loaders = {
4951
noExt: yamlParse,
5052
}
5153

52-
const resolveConfig = (configPath) => {
53-
try {
54-
return require.resolve(configPath)
55-
} catch {
56-
return configPath
57-
}
58-
}
59-
6054
/**
6155
* @param {object} options
6256
* @param {string} [options.configPath] - Explicit path to a config file

lib/resolveConfig.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createRequire } from 'module'
2+
3+
/**
4+
* require() does not exist for ESM, so we must create it to use require.resolve().
5+
* @see https://nodejs.org/api/module.html#modulecreaterequirefilename
6+
*/
7+
const require = createRequire(import.meta.url)
8+
9+
export function resolveConfig(configPath) {
10+
try {
11+
return require.resolve(configPath)
12+
} catch {
13+
return configPath
14+
}
15+
}

test/index.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ jest.mock('url', () => ({
2828

2929
jest.mock('../lib/getStagedFiles')
3030
jest.mock('../lib/gitWorkflow')
31+
jest.mock('../lib/resolveConfig', () => ({
32+
/** Unfortunately necessary due to non-ESM tests. */
33+
resolveConfig: (configPath) => {
34+
try {
35+
return require.resolve(configPath)
36+
} catch {
37+
return configPath
38+
}
39+
},
40+
}))
3141
jest.mock('../lib/validateOptions', () => ({
3242
validateOptions: jest.fn().mockImplementation(async () => {}),
3343
}))

test/index2.spec.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { Listr } from 'listr2'
44
import makeConsoleMock from 'consolemock'
55

66
jest.mock('listr2')
7+
jest.mock('../lib/resolveConfig', () => ({
8+
/** Unfortunately necessary due to non-ESM tests. */
9+
resolveConfig: (configPath) => {
10+
try {
11+
return require.resolve(configPath)
12+
} catch {
13+
return configPath
14+
}
15+
},
16+
}))
17+
718
jest.mock('../lib/resolveGitRepo')
819

920
import lintStaged from '../lib/index'

test/integration.test.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import normalize from 'normalize-path'
77

88
jest.unmock('lilconfig')
99
jest.unmock('execa')
10+
jest.mock('../lib/resolveConfig', () => ({
11+
/** Unfortunately necessary due to non-ESM tests. */
12+
resolveConfig: (configPath) => {
13+
try {
14+
return require.resolve(configPath)
15+
} catch {
16+
return configPath
17+
}
18+
},
19+
}))
1020

1121
import { execGit as execGitBase } from '../lib/execGit'
1222
import lintStaged from '../lib/index'
@@ -88,6 +98,9 @@ describe('lint-staged', () => {
8898

8999
const globalConsoleTemp = console
90100

101+
// Tests should be resilient to `git config init.defaultBranch` that is _not_ "master"
102+
let defaultBranchName = 'UNSET'
103+
91104
describe('lint-staged', () => {
92105
beforeAll(() => {
93106
console = makeConsoleMock()
@@ -104,6 +117,10 @@ describe('lint-staged', () => {
104117
await appendFile('README.md', '# Test\n')
105118
await execGit(['add', 'README.md'])
106119
await execGit(['commit', '-m initial commit'])
120+
121+
if (defaultBranchName === 'UNSET') {
122+
defaultBranchName = await execGit(['rev-parse', '--abbrev-ref', 'HEAD'])
123+
}
107124
})
108125

109126
afterEach(async () => {
@@ -451,7 +468,7 @@ describe('lint-staged', () => {
451468
await gitCommit(fixJsConfig, ['-m commit a'])
452469
expect(await readFile('test.js')).toEqual(fileInBranchA)
453470

454-
await execGit(['checkout', 'master'])
471+
await execGit(['checkout', defaultBranchName])
455472

456473
// Create another branch
457474
await execGit(['checkout', '-b', 'branch-b'])
@@ -461,7 +478,7 @@ describe('lint-staged', () => {
461478
expect(await readFile('test.js')).toEqual(fileInBranchBFixed)
462479

463480
// Merge first branch
464-
await execGit(['checkout', 'master'])
481+
await execGit(['checkout', defaultBranchName])
465482
await execGit(['merge', 'branch-a'])
466483
expect(await readFile('test.js')).toEqual(fileInBranchA)
467484
expect(await execGit(['log', '-1', '--pretty=%B'])).toMatch('commit a')
@@ -512,7 +529,7 @@ describe('lint-staged', () => {
512529
await gitCommit(fixJsConfig, ['-m commit a'])
513530
expect(await readFile('test.js')).toEqual(fileInBranchA)
514531

515-
await execGit(['checkout', 'master'])
532+
await execGit(['checkout', defaultBranchName])
516533

517534
// Create another branch
518535
await execGit(['checkout', '-b', 'branch-b'])
@@ -522,7 +539,7 @@ describe('lint-staged', () => {
522539
expect(await readFile('test.js')).toEqual(fileInBranchBFixed)
523540

524541
// Merge first branch
525-
await execGit(['checkout', 'master'])
542+
await execGit(['checkout', defaultBranchName])
526543
await execGit(['merge', 'branch-a'])
527544
expect(await readFile('test.js')).toEqual(fileInBranchA)
528545
expect(await execGit(['log', '-1', '--pretty=%B'])).toMatch('commit a')
@@ -1073,7 +1090,7 @@ describe('lintStaged', () => {
10731090
await execGit(['add', 'test.js'], { cwd })
10741091

10751092
await expect(execGit(['log', '-1'], { cwd })).rejects.toThrowErrorMatchingInlineSnapshot(
1076-
`"fatal: your current branch 'master' does not have any commits yet"`
1093+
`"fatal: your current branch '${defaultBranchName}' does not have any commits yet"`
10771094
)
10781095

10791096
await gitCommit({

test/loadConfig.spec.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
jest.mock('../lib/resolveConfig', () => ({
2+
/** Unfortunately necessary due to non-ESM tests. */
3+
resolveConfig: (configPath) => {
4+
try {
5+
return require.resolve(configPath)
6+
} catch {
7+
return configPath
8+
}
9+
},
10+
}))
11+
112
import { dynamicImport } from '../lib/loadConfig.js'
213

314
describe('dynamicImport', () => {

0 commit comments

Comments
 (0)