Skip to content

Commit

Permalink
Merge pull request #3 from toplenboren/dev
Browse files Browse the repository at this point in the history
Enhance UX and add an ability to set command from `simple-pre-commit.json`
  • Loading branch information
toplenboren authored Feb 15, 2021
2 parents 395aa2d + 4efaae8 commit feea958
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 64 deletions.
10 changes: 10 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.github
node_modules
.idea
.vscode

_tests
simple-pre-commit.test.js

.eslintrc.js
.eslintignore
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ You can look up about git hooks [here](https://git-scm.com/book/en/v2/Customizin

## Updating a pre-commit hook command

Run `npx simple-pre-commit`
Run `npx simple-pre-commit` **from root of your project**

Note that you should manually run `npx simple-pre-commit` every time you change the command


## Additional configuration options

You can also add the `.simple-pre-commit.json` to the project and write the command inside it, if you do not want to put command inside `package.json`

That way, `.simple-pre-commit.json` should look like this and `package.json` may not have `simple-pre-commit` configuration inside it

```(json)
{
"simple-pre-commit":"npx lint staged"
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"simple-pre-commit": "test",
"devDependencies": {
"simple-pre-commit": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"devDependencies": {
"simple-pre-commit": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"simple-pre-commit": "test"
}
7 changes: 7 additions & 0 deletions _tests/project_with_simple_pre_commit_in_deps/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"dependencies": {
"simple-pre-commit": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"devDependencies": {
"simple-pre-commit": "1.0.0"
}
}
7 changes: 7 additions & 0 deletions _tests/project_without_configuration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"devDependencies": {
"simple-pre-commit": "1.0.0"
}
}
8 changes: 8 additions & 0 deletions _tests/project_without_simple_pre_commit/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"devDependencies": {
},
"dependencies": {
}
}
8 changes: 5 additions & 3 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ const os = require('os')
* It really has only one function — to set new pre commit hook.
* Checks the package.json for simple-pre-commit hook command and sets the found command as hook
*/
const {getCommandFromPackageJson, setPreCommitHook} = require('./simple-pre-commit')
const {getCommandFromConfig, setPreCommitHook} = require('./simple-pre-commit')

const command = getCommandFromPackageJson()
const command = getCommandFromConfig(process.cwd())

if (!command) {
console.log(`Couldn't parse command! Please add command to package.json or .simpleprecommit.json. See README.md for details`)
console.log(`Couldn't parse command! Please add command to package.json or .simple-pre-commit.json. See README.md for details`)
os.exit(1)
}

setPreCommitHook(command)

console.log('Set pre commit hooK: ' + command)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "simple-pre-commit",
"version": "1.0.1",
"version": "1.1.0",
"description": "A simple, zero dependency tool for setting up git pre-commit hook for small projects",
"author": "Mikhail Gorbunov <toplenboren@gmail.com> (toplenboren.gituhb.io)",
"main": "./simple-pre-commit.js",
Expand Down
16 changes: 8 additions & 8 deletions postinstall.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
#!/usr/bin/env node

const {getCommandFromPackageJson, getPackageJson, simplePreCommitInDevDependencies, getProjectRootDirectory, setPreCommitHook } = require("./simple-pre-commit");
const {getCommandFromConfig, checkSimplePreCommitInDependencies, getProjectRootDirectoryFromNodeModules, setPreCommitHook} = require("./simple-pre-commit");


/**
* Creates the pre-commit hook with npx lint-staged command by default
* Creates the pre-commit from command in config by default
*/
function postinstall() {
let projectDirectory = process.cwd()
let projectDirectory;

/* When script is run after install, the process.cwd() would be like <project_folder>/node_modules/simple-pre-commit
Here we try to get the original project directory by going upwards by 2 levels
If we were not able to get new directory we assume, we are already in the project root */
const parsedProjectDirectory = getProjectRootDirectory(process.cwd())
const parsedProjectDirectory = getProjectRootDirectoryFromNodeModules(process.cwd())
if (parsedProjectDirectory !== undefined) {
projectDirectory = parsedProjectDirectory
} else {
projectDirectory = process.cwd()
}

const { packageJsonContent } = getPackageJson(projectDirectory)

if (simplePreCommitInDevDependencies(packageJsonContent)) {
if (checkSimplePreCommitInDependencies(projectDirectory)) {
try {
const command = getCommandFromPackageJson(projectDirectory)
const command = getCommandFromConfig(projectDirectory)
if (command === undefined) {
console.log('[INFO] Please add the pre-commit command to the "simple-pre-commit" field in package.json')
} else {
Expand Down
107 changes: 82 additions & 25 deletions simple-pre-commit.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function getGitProjectRoot(directory=module.parent.filename) {
* @param projectPath - path to the simple-pre-commit in node modules
* @return {string | undefined} - an absolute path to the project or undefined if projectPath is not in node_modules
*/
function getProjectRootDirectory(projectPath) {
function getProjectRootDirectoryFromNodeModules(projectPath) {
function _arraysAreEqual(a1, a2) {
return JSON.stringify(a1) === JSON.stringify(a2)
}
Expand All @@ -66,23 +66,71 @@ function getProjectRootDirectory(projectPath) {

/**
* Checks the 'simple-pre-commit' in dependencies of the project
* @param {Object} packageJsonData
* @param {string} projectRootPath
* @throws TypeError if packageJsonData not an object
* @return {Boolean}
*/
function simplePreCommitInDevDependencies(packageJsonData) {
if (typeof packageJsonData !== 'object') {
throw TypeError("PackageJson is not found")
function checkSimplePreCommitInDependencies(projectRootPath) {
if (typeof projectRootPath !== 'string') {
throw TypeError("Package json path is not a string!")
}

const {packageJsonContent} = _getPackageJson(projectRootPath)

// if simple-pre-commit in dependencies -> note user that he should remove move it to devDeps!
if ('dependencies' in packageJsonData && 'simple-pre-commit' in packageJsonData.dependencies) {
if ('dependencies' in packageJsonContent && 'simple-pre-commit' in packageJsonContent.dependencies) {
console.log('[WARN] You should move simple-pre-commit to the devDependencies!')
return true // We only check that we are in the correct package, e.g not in a dependency of a dependency
}
if (!('devDependencies' in packageJsonData)) {
if (!('devDependencies' in packageJsonContent)) {
return false
}
return 'simple-pre-commit' in packageJsonData.devDependencies
return 'simple-pre-commit' in packageJsonContent.devDependencies
}


/**
* Gets user-set command either from sources
* First try to get command from .simple-pre-commit.json
* If not found -> try to get command from package.json
* @param {string} projectRootPath
* @throws TypeError if projectRootPath is not string
* @return {string | undefined}
*/
function getCommandFromConfig(projectRootPath) {
if (typeof projectRootPath !== 'string') {
throw TypeError("Check project root path! Expected a string, but got " + typeof projectRootPath)
}

// every function here should accept projectRootPath as first argument and return either string or undefined
const sources = [
_getCommandFromSimplePreCommitJson,
_getCommandFromPackageJson,
]

for (let i = 0; i < sources.length; ++i) {
let command = sources[i](projectRootPath)
if (command) {
return command
}
}

return undefined
}


/**
* Creates or replaces an existing executable script in .git/hooks/pre-commit with provided command
* @param {string} command
*/
function setPreCommitHook(command) {
const gitRoot = getGitProjectRoot(process.cwd())

const preCommitHook = "#!/bin/sh" + os.EOL + command
const preCommitHookPath = path.normalize(gitRoot + '/hooks/pre-commit')

fs.writeFileSync(preCommitHookPath, preCommitHook)
fs.chmodSync(preCommitHookPath, 0o0755)
}


Expand All @@ -93,7 +141,7 @@ function simplePreCommitInDevDependencies(packageJsonData) {
* @throws Error if cant read package.json
* @private
*/
function getPackageJson(projectPath = process.cwd()) {
function _getPackageJson(projectPath = process.cwd()) {
if (typeof projectPath !== "string") {
throw TypeError("projectPath is not a string")
}
Expand All @@ -108,37 +156,46 @@ function getPackageJson(projectPath = process.cwd()) {
return { packageJsonContent: JSON.parse(packageJsonDataRaw), packageJsonPath: targetPackageJson }
}


/**
* Gets current command from package.json[simple-pre-commit]
* @param {string} packageJsonPath
* @param {string} projectRootPath
* @throws TypeError if packageJsonPath is not a string
* @throws Error if package.json couldn't be read
* @return {undefined | string}
*/
function getCommandFromPackageJson(packageJsonPath = process.cwd()) {
const {packageJsonContent} = getPackageJson(packageJsonPath)
function _getCommandFromPackageJson(projectRootPath = process.cwd()) {
const {packageJsonContent} = _getPackageJson(projectRootPath)
return packageJsonContent['simple-pre-commit']
}


/**
* Creates or replaces an existing executable script in .git/hooks/pre-commit with provided command
* @param {string} command
* Gets user-set command from simple-pre-commit.json
* Since the file is not required in node.js projects it returns undefined if something is off
* @param {string} projectRootPath
* @return {string | undefined}
*/
function setPreCommitHook(command) {
const gitRoot = getGitProjectRoot(process.cwd())

const preCommitHook = "#!/bin/sh" + os.EOL + command
const preCommitHookPath = path.normalize(gitRoot + '/hooks/pre-commit')
function _getCommandFromSimplePreCommitJson(projectRootPath) {
if (typeof projectRootPath !== "string") {
throw TypeError("projectRootPath is not a string")
}

fs.writeFileSync(preCommitHookPath, preCommitHook)
fs.chmodSync(preCommitHookPath, 0o0755)
try {
const simplePreCommitJsonPath = path.normalize(projectRootPath + '/simple-pre-commit.json')
const simplePreCommitJsonRaw = fs.readFileSync(simplePreCommitJsonPath)
const simplePreCommitJson = JSON.parse(simplePreCommitJsonRaw)
return simplePreCommitJson['simple-pre-commit']
} catch (err) {
return undefined
}
}


module.exports = {
simplePreCommitInDevDependencies,
checkSimplePreCommitInDependencies,
setPreCommitHook,
getPackageJson,
getCommandFromPackageJson,
getProjectRootDirectory,
getCommandFromConfig,
getProjectRootDirectoryFromNodeModules,
getGitProjectRoot
}
55 changes: 29 additions & 26 deletions simple-pre-commit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ const path = require("path")
// Get project root directory

test('getProjectRootDirectory returns correct dir in typical case:', () => {
expect(spc.getProjectRootDirectory('var/my-project/node_modules/simple-pre-commit')).toBe('var/my-project')
expect(spc.getProjectRootDirectoryFromNodeModules('var/my-project/node_modules/simple-pre-commit')).toBe('var/my-project')
})

test('getProjectRootDirectory returns correct dir when used with windows delimiters:', () => {
expect(spc.getProjectRootDirectory('user\\allProjects\\project\\node_modules\\simple-pre-commit')).toBe('user/allProjects/project')
expect(spc.getProjectRootDirectoryFromNodeModules('user\\allProjects\\project\\node_modules\\simple-pre-commit')).toBe('user/allProjects/project')
})

test('getProjectRootDirectory falls back to undefined when we are not in node_modules:', () => {
expect(spc.getProjectRootDirectory('var/my-project/simple-pre-commit')).toBe(undefined)
expect(spc.getProjectRootDirectoryFromNodeModules('var/my-project/simple-pre-commit')).toBe(undefined)
})


Expand All @@ -38,35 +38,38 @@ test('get git root works from any file', () => {

// Check if simple-pre-commit is in devDependencies or dependencies in package json

const correctPackageJson = {
devDependencies: {
"simple-pre-commit":"1.0.0"
}
}

const correctPackageJson2 = {
dependencies: {
"simple-pre-commit":"1.0.0"
}
}

const incorrectPackageJson = {
dependencies: {
"not-our-dependency":"1.0.0"
},
devDependencies: {
"not-our-dependency":"1.0.0"
}
}
const correctPackageJsonProjectPath = path.normalize(path.join(process.cwd(), '_tests', 'project_with_simple_pre_commit_in_deps'))
const correctPackageJsonProjectPath_2 = path.normalize(path.join(process.cwd(), '_tests', 'project_with_simple_pre_commit_in_dev_deps'))
const incorrectPackageJsonProjectPath = path.normalize(path.join(process.cwd(), '_tests', 'project_without_simple_pre_commit'))

test('returns true if simple pre commit really in devDeps', () => {
expect(spc.simplePreCommitInDevDependencies(correctPackageJson)).toBe(true)
expect(spc.checkSimplePreCommitInDependencies(correctPackageJsonProjectPath)).toBe(true)
})

test('returns true if simple pre commit really in deps', () => {
expect(spc.simplePreCommitInDevDependencies(correctPackageJson2)).toBe(true)
expect(spc.checkSimplePreCommitInDependencies(correctPackageJsonProjectPath_2)).toBe(true)
})

test('returns false if simple pre commit isn`t in deps', () => {
expect(spc.simplePreCommitInDevDependencies(incorrectPackageJson)).toBe(false)
expect(spc.checkSimplePreCommitInDependencies(incorrectPackageJsonProjectPath)).toBe(false)
})


// Get command from configuration

const commandInPackageJsonProjectPath = path.normalize(path.join(process.cwd(), '_tests', 'project_with_configuration_in_package_json'))
const commandInSeparateJsonProjectPath = path.normalize(path.join(process.cwd(), '_tests', 'project_with_configuration_in_separate_json'))
const notConfiguredProjectPath = path.normalize(path.join(process.cwd(), '_tests', 'project_without_configuration'))

test('returns command if configured from package.json', () => {
expect(spc.getCommandFromConfig(commandInPackageJsonProjectPath)).toBe("test")
})

test('returns command if configured from simple-pre-commit.json', () => {
expect(spc.getCommandFromConfig(commandInSeparateJsonProjectPath)).toBe("test")
})

test('returns undefined if were not able to parse command', () => {
expect(spc.getCommandFromConfig(notConfiguredProjectPath)).toBe(undefined)
})

0 comments on commit feea958

Please sign in to comment.