Skip to content

Commit fc2acfc

Browse files
authored
feat: support esm format configs (#120)
1 parent 72edba9 commit fc2acfc

File tree

29 files changed

+171
-77
lines changed

29 files changed

+171
-77
lines changed

.changeset/selfish-owls-collect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"simple-git-hooks": minor
3+
---
4+
5+
feat: support esm format configs

.eslintrc.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,13 @@ module.exports = {
88
"extends": "eslint:recommended",
99
"parserOptions": {
1010
"ecmaVersion": 12
11-
}
11+
},
12+
"overrides": [
13+
{
14+
"files": ["*.mjs"],
15+
"parserOptions": {
16+
"sourceType": "module"
17+
}
18+
}
19+
]
1220
};

.github/workflows/codeql-analysis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939

4040
steps:
4141
- name: Checkout repository
42-
uses: actions/checkout@v3
42+
uses: actions/checkout@v4
4343

4444
# Initializes the CodeQL tools for scanning.
4545
- name: Initialize CodeQL

.github/workflows/lint.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
- name: Lint
1717
uses: actions/setup-node@v4
1818
with:
19-
node-version: 14.x
20-
- run: yarn --frozen-lockfile
19+
node-version: 18.x
20+
cache: yarn
21+
- run: yarn --frozen-lockfile --ignore-engines
2122
- run: yarn lint

.github/workflows/tests.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
- name: Lint
1717
uses: actions/setup-node@v4
1818
with:
19-
node-version: 14.x
20-
- run: yarn --frozen-lockfile
19+
node-version: 18.x
20+
cache: yarn
21+
- run: yarn --frozen-lockfile --ignore-engines
2122
- run: yarn test

README.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,13 @@ Note that you should manually run `npx simple-git-hooks` **every time you change
120120

121121
### Additional configuration options
122122

123-
You can also add a `.simple-git-hooks.cjs`, `.simple-git-hooks.js`, `simple-git-hooks.cjs`, `simple-git-hooks.js`, `.simple-git-hooks.json` or `simple-git-hooks.json` file to the project and write the configuration inside it.
123+
You can also add a `.simple-git-hooks.cjs`, `.simple-git-hooks.js`, `.simple-git-hooks.mjs`, `simple-git-hooks.cjs`, `simple-git-hooks.js`, `simple-git-hooks.mjs`, `.simple-git-hooks.json` or `simple-git-hooks.json` file to the project and write the configuration inside it.
124124

125125
This way `simple-git-hooks` configuration in `package.json` will not take effect any more.
126126

127-
`.simple-git-hooks.cjs`, `.simple-git-hooks.js` or `simple-git-hooks.cjs`, `simple-git-hooks.js` should look like the following.
127+
`.simple-git-hooks.cjs`, `.simple-git-hooks.js`, `.simple-git-hooks.mjs` or `simple-git-hooks.cjs`, `simple-git-hooks.js`, `simple-git-hooks.mjs` should look like the following.
128+
129+
#### CommonJS
128130

129131
```js
130132
module.exports = {
@@ -133,6 +135,16 @@ module.exports = {
133135
};
134136
```
135137

138+
139+
#### ES Modules
140+
141+
```js
142+
export default {
143+
"pre-commit": "npx lint-staged",
144+
"pre-push": "npm run format",
145+
};
146+
```
147+
136148
`.simple-git-hooks.json` or `simple-git-hooks.json` should look like the following.
137149

138150
```json

_tests/project_with_configuration_in_alternative_separate_cjs/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-alternative-cjs",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_configuration_in_alternative_separate_js/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-alternative-js",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_configuration_in_alternative_separate_json/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-alternative-json",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
"pre-push": "exit 1",
3+
"pre-commit": "exit 1"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "simple-git-hooks-test-package-alternative-mjs",
3+
"version": "1.0.0",
4+
"devDependencies": {
5+
"simple-git-hooks": "1.0.0"
6+
}
7+
}

_tests/project_with_configuration_in_package_json/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-pkg-json",
33
"version": "1.0.0",
44
"simple-git-hooks": {
55
"pre-commit": "exit 1"

_tests/project_with_configuration_in_separate_cjs/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-cjs",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_configuration_in_separate_js/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-js",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_configuration_in_separate_json/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-json",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "simple-git-hooks-test-package-mjs",
3+
"version": "1.0.0",
4+
"devDependencies": {
5+
"simple-git-hooks": "1.0.0"
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
"pre-push": "exit 1",
3+
"pre-commit": "exit 1"
4+
}

_tests/project_with_custom_configuration/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-custom",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_incorrect_configuration_in_package_json/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-incorrect",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_simple_git_hooks_in_deps/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-deps",
33
"version": "1.0.0",
44
"dependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_simple_git_hooks_in_dev_deps/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-dev-deps",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_with_unused_configuration_in_package_json/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-unused",
33
"version": "1.0.0",
44
"simple-git-hooks": {
55
"pre-commit": "exit 1",

_tests/project_without_configuration/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-without-config",
33
"version": "1.0.0",
44
"devDependencies": {
55
"simple-git-hooks": "1.0.0"

_tests/project_without_simple_git_hooks/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "simple-git-hooks-test-package",
2+
"name": "simple-git-hooks-test-package-without",
33
"version": "1.0.0",
44
"devDependencies": {
55
},

cli.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ if (['1', 'true'].includes(SKIP_INSTALL_SIMPLE_GIT_HOOKS)) {
1313
return
1414
}
1515

16-
try {
17-
setHooksFromConfig(process.cwd(), process.argv)
18-
console.log('[INFO] Successfully set all git hooks')
19-
} catch (e) {
20-
console.log('[ERROR], Was not able to set git hooks. Error: ' + e)
21-
}
16+
setHooksFromConfig(process.cwd(), process.argv)
17+
.then(() => console.log('[INFO] Successfully set all git hooks'))
18+
.catch(e => console.log('[ERROR], Was not able to set git hooks. Error: ' + e))

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "simple-git-hooks",
3+
"type": "commonjs",
34
"version": "2.11.1",
45
"description": "A simple, zero dependency tool for setting up git hooks for small projects",
56
"author": "Mikhail Gorbunov <[email protected]>",
@@ -15,7 +16,7 @@
1516
"scripts": {
1617
"postinstall": "node ./postinstall.js",
1718
"lint": "eslint *.js",
18-
"test": "jest",
19+
"test": "node --experimental-vm-modules ./node_modules/.bin/jest",
1920
"release": "clean-pkg-json && changeset publish",
2021
"uninstall": "node ./uninstall.js"
2122
},

postinstall.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const {checkSimpleGitHooksInDependencies, getProjectRootDirectoryFromNodeModules
66
/**
77
* Creates the pre-commit from command in config by default
88
*/
9-
function postinstall() {
9+
async function postinstall() {
1010
let projectDirectory;
1111

1212
/* When script is run after install, the process.cwd() would be like <project_folder>/node_modules/simple-git-hooks
@@ -21,7 +21,7 @@ function postinstall() {
2121

2222
if (checkSimpleGitHooksInDependencies(projectDirectory)) {
2323
try {
24-
setHooksFromConfig(projectDirectory)
24+
await setHooksFromConfig(projectDirectory)
2525
} catch (err) {
2626
console.log('[ERROR] Was not able to set git hooks. Reason: ' + err)
2727
}

simple-git-hooks.js

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs')
22
const path = require('path')
3+
const url = require('url')
34

45
const VALID_GIT_HOOKS = [
56
'applypatch-msg',
@@ -158,12 +159,12 @@ function checkSimpleGitHooksInDependencies(projectRootPath) {
158159
* @param {string} projectRootPath
159160
* @param {string[]} [argv]
160161
*/
161-
function setHooksFromConfig(projectRootPath=process.cwd(), argv=process.argv) {
162+
async function setHooksFromConfig(projectRootPath=process.cwd(), argv=process.argv) {
162163
const customConfigPath = _getCustomConfigPath(argv)
163-
const config = _getConfig(projectRootPath, customConfigPath)
164+
const config = await _getConfig(projectRootPath, customConfigPath)
164165

165166
if (!config) {
166-
throw('[ERROR] Config was not found! Please add `.simple-git-hooks.js` or `simple-git-hooks.js` or `.simple-git-hooks.json` or `simple-git-hooks.json` or `simple-git-hooks` entry in package.json.\r\nCheck README for details')
167+
throw('[ERROR] Config was not found! Please add `.simple-git-hooks.cjs` or `.simple-git-hooks.js` or `.simple-git-hooks.mjs` or `simple-git-hooks.cjs` or `simple-git-hooks.js` or `simple-git-hooks.mjs` or `.simple-git-hooks.json` or `simple-git-hooks.json` or `simple-git-hooks` entry in package.json.\r\nCheck README for details')
167168
}
168169

169170
const preserveUnused = Array.isArray(config.preserveUnused) ? config.preserveUnused : config.preserveUnused ? VALID_GIT_HOOKS: []
@@ -274,10 +275,10 @@ function _getCustomConfigPath(argv=[]) {
274275
* @param {string} projectRootPath
275276
* @param {string} [configFileName]
276277
* @throws TypeError if projectRootPath is not string
277-
* @return {{string: string} | undefined}
278+
* @return {Promise<{[key: string]: unknown} | undefined>}
278279
* @private
279280
*/
280-
function _getConfig(projectRootPath, configFileName='') {
281+
async function _getConfig(projectRootPath, configFileName='') {
281282
if (typeof projectRootPath !== 'string') {
282283
throw TypeError("Check project root path! Expected a string, but got " + typeof projectRootPath)
283284
}
@@ -286,8 +287,10 @@ function _getConfig(projectRootPath, configFileName='') {
286287
const sources = [
287288
() => _getConfigFromFile(projectRootPath, '.simple-git-hooks.cjs'),
288289
() => _getConfigFromFile(projectRootPath, '.simple-git-hooks.js'),
290+
() => _getConfigFromFile(projectRootPath, '.simple-git-hooks.mjs'),
289291
() => _getConfigFromFile(projectRootPath, 'simple-git-hooks.cjs'),
290292
() => _getConfigFromFile(projectRootPath, 'simple-git-hooks.js'),
293+
() => _getConfigFromFile(projectRootPath, 'simple-git-hooks.mjs'),
291294
() => _getConfigFromFile(projectRootPath, '.simple-git-hooks.json'),
292295
() => _getConfigFromFile(projectRootPath, 'simple-git-hooks.json'),
293296
() => _getConfigFromPackageJson(projectRootPath),
@@ -299,11 +302,12 @@ function _getConfig(projectRootPath, configFileName='') {
299302
}
300303

301304
for (let executeSource of sources) {
302-
let config = executeSource()
303-
if (config && _validateHooks(config)) {
304-
return config
305-
}
306-
else if (config && !_validateHooks(config)) {
305+
let config = await executeSource()
306+
if (config) {
307+
if (_validateHooks(config)) {
308+
return config
309+
}
310+
307311
throw('[ERROR] Config was not in correct format. Please check git hooks or options name')
308312
}
309313
}
@@ -316,22 +320,22 @@ function _getConfig(projectRootPath, configFileName='') {
316320
* @param {string} projectRootPath
317321
* @throws TypeError if packageJsonPath is not a string
318322
* @throws Error if package.json couldn't be read or was not validated
319-
* @return {{string: string} | undefined}
323+
* @return {Promise<{[key: string]: unknown}> | {[key: string]: unknown} | undefined}
320324
*/
321325
function _getConfigFromPackageJson(projectRootPath = process.cwd()) {
322326
const {packageJsonContent} = _getPackageJson(projectRootPath)
323327
const config = packageJsonContent['simple-git-hooks'];
324-
return typeof config === 'string' ? _getConfig(config) : packageJsonContent['simple-git-hooks']
328+
return typeof config === 'string' ? _getConfig(config) : config
325329
}
326330

327331
/**
328332
* Gets user-set config from file
329333
* Since the file is not required in node.js projects it returns undefined if something is off
330334
* @param {string} projectRootPath
331335
* @param {string} fileName
332-
* @return {{string: string} | undefined}
336+
* @return {Promise<{[key: string]: unknown} | undefined>}
333337
*/
334-
function _getConfigFromFile(projectRootPath, fileName) {
338+
async function _getConfigFromFile(projectRootPath, fileName) {
335339
if (typeof projectRootPath !== "string") {
336340
throw TypeError("projectRootPath is not a string")
337341
}
@@ -347,7 +351,12 @@ function _getConfigFromFile(projectRootPath, fileName) {
347351
if (filePath === __filename) {
348352
return undefined
349353
}
350-
return require(filePath) // handle `.js` and `.json`
354+
// handle `.json` with `require`, `import()` requires `with:{type:'json'}` which does not work for all engines
355+
if (filePath.endsWith('.json')) {
356+
return require(filePath)
357+
}
358+
const result = await import(url.pathToFileURL(filePath)) // handle `.cjs`, `.js`, `.mjs`
359+
return result.default || result
351360
} catch (err) {
352361
return undefined
353362
}

0 commit comments

Comments
 (0)