diff --git a/package.json b/package.json index 83595bb85..2779d1ad9 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,8 @@ }, "scripts": { "test": "npm run lint && jest", - "_install": "node husky install", + "install": "node husky install", "preuninstall": "node husky uninstall", - "devinstall": "npm run build && cross-env HUSKY_DEBUG=1 npm run _install -- node_modules/husky && node scripts/dev-fix-path", - "devuninstall": "npm run build && cross-env HUSKY_DEBUG=1 npm run preuninstall -- node_modules/husky", "build": "del-cli lib && tsc", "version": "jest -u && git add -A src/installer/__tests__/__snapshots__", "postversion": "git push && git push --tags", @@ -23,7 +21,7 @@ "lint": "eslint . --ext .js,.ts --ignore-path .gitignore", "fix": "npm run lint -- --fix", "doc": "markdown-toc -i README.md", - "_postinstall": "opencollective-postinstall || exit 0" + "postinstall": "opencollective-postinstall || exit 0" }, "repository": { "type": "git", diff --git a/scripts/install.sh b/scripts/install.sh index c19777c98..765df5b58 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -30,7 +30,6 @@ commit() { HUSKY_SKIP_HOOKS=$2 git commit -m "$1 msg" } - # --- # Setup # --- @@ -47,8 +46,11 @@ mv husky-*.tgz $projectDir # Init a blank git/npm project and install husky cd $projectDir git init +git config user.name foo # Needed by AppVeyor +git config user.email foo@example.com # Needed by AppVeyor npm init -y + # Create .huskyrc with skipCI: false before installing husky cat > .huskyrc << EOL { @@ -63,7 +65,6 @@ npm install husky-*.tgz # Show hook content cat .git/hooks/commit-msg - # --- # Tests # --- diff --git a/src/installer/getScript.ts b/src/installer/getScript.ts index bd2979725..fba89a34d 100644 --- a/src/installer/getScript.ts +++ b/src/installer/getScript.ts @@ -1,5 +1,6 @@ import fs from 'fs' import path from 'path' +import slash from 'slash' interface Context { createdAt: string @@ -132,7 +133,8 @@ export default function( createdAt, homepage, packageManager, - pathToUserPkgDir, + // Normalize path + pathToUserPkgDir: slash(pathToUserPkgDir), pkgDirectory, pkgHomepage, version diff --git a/src/runner/__tests__/index.ts b/src/runner/__tests__/index.ts index 92666ccd1..2f488c8f5 100644 --- a/src/runner/__tests__/index.ts +++ b/src/runner/__tests__/index.ts @@ -1,19 +1,34 @@ -import execa from 'execa' import fs from 'fs' +import cp from 'child_process' import mkdirp from 'mkdirp' import path from 'path' import tempy from 'tempy' -import index from '../' +import index, { Env } from '../' let spy: jest.SpyInstance +// On AppVeyor $SHELL is not set +process.env.SHELL = process.env.SHELL || 'sh' + function getScriptPath(dir: string): string { return path.join(dir, 'node_modules/husky/runner/index.js') } +function expectSpawnSyncToHaveBeenCalledWith( + cwd: string, + cmd: string, + env: Env = {} +): void { + expect(cp.spawnSync).toHaveBeenCalledWith(process.env.SHELL, ['-c', cmd], { + cwd, + env: { ...process.env, ...env }, + stdio: 'inherit' + }) +} + describe('run', (): void => { beforeEach((): void => { - spy = jest.spyOn(execa, 'shellSync') + spy = jest.spyOn(cp, 'spawnSync') }) afterEach((): void => { @@ -38,11 +53,7 @@ describe('run', (): void => { ) const status = await index(['', getScriptPath(dir), 'pre-commit']) - expect(execa.shellSync).toHaveBeenCalledWith('echo success', { - cwd: dir, - env: {}, - stdio: 'inherit' - }) + expectSpawnSyncToHaveBeenCalledWith(dir, 'echo success') expect(status).toBe(0) }) @@ -65,11 +76,7 @@ describe('run', (): void => { ) const status = await index(['', getScriptPath(subDir), 'pre-commit']) - expect(execa.shellSync).toHaveBeenCalledWith('echo success', { - cwd: subDir, - env: {}, - stdio: 'inherit' - }) + expectSpawnSyncToHaveBeenCalledWith(subDir, 'echo success') expect(status).toBe(0) }) @@ -88,7 +95,7 @@ describe('run', (): void => { ) const status = await index(['', getScriptPath(dir), 'pre-commit']) - expect(execa.shellSync).not.toBeCalled() + expect(cp.spawnSync).not.toBeCalled() expect(status).toBe(0) }) @@ -109,11 +116,7 @@ describe('run', (): void => { ) const status = await index(['', getScriptPath(dir), 'pre-commit']) - expect(execa.shellSync).toHaveBeenCalledWith('echo fail && exit 2', { - cwd: dir, - env: {}, - stdio: 'inherit' - }) + expectSpawnSyncToHaveBeenCalledWith(dir, 'echo fail && exit 2') expect(status).toBe(2) }) @@ -132,11 +135,7 @@ describe('run', (): void => { ) const status = await index(['', getScriptPath(dir), 'pre-commit']) - expect(execa.shellSync).toHaveBeenCalledWith('echo success', { - cwd: dir, - env: {}, - stdio: 'inherit' - }) + expectSpawnSyncToHaveBeenCalledWith(dir, 'echo success') expect(status).toBe(0) }) @@ -160,12 +159,8 @@ describe('run', (): void => { ['', getScriptPath(dir), 'pre-push'], (): Promise => Promise.resolve('foo') ) - expect(execa.shellSync).toHaveBeenCalledWith('echo success', { - cwd: dir, - env: { - HUSKY_GIT_STDIN: 'foo' - }, - stdio: 'inherit' + expectSpawnSyncToHaveBeenCalledWith(dir, 'echo success', { + HUSKY_GIT_STDIN: 'foo' }) expect(status).toBe(0) }) @@ -191,12 +186,8 @@ describe('run', (): void => { 'commit-msg', 'git fake param' ]) - expect(execa.shellSync).toHaveBeenCalledWith('echo success', { - cwd: dir, - env: { - HUSKY_GIT_PARAMS: 'git fake param' - }, - stdio: 'inherit' + expectSpawnSyncToHaveBeenCalledWith(dir, 'echo success', { + HUSKY_GIT_PARAMS: 'git fake param' }) expect(status).toBe(0) }) diff --git a/src/runner/index.ts b/src/runner/index.ts index c3892697d..93f7091ca 100644 --- a/src/runner/index.ts +++ b/src/runner/index.ts @@ -1,4 +1,4 @@ -import execa from 'execa' +import { spawnSync } from 'child_process' import getStdin from 'get-stdin' import path from 'path' import readPkg from 'read-pkg' @@ -10,6 +10,38 @@ export interface Env extends NodeJS.ProcessEnv { HUSKY_GIT_PARAMS?: string } +function runCmd(cwd: string, hookName: string, cmd: string, env: Env): number { + const shellPath = process.env.SHELL || 'sh' + let status + + console.log(`husky > ${hookName} (node ${process.version})`) + + try { + status = spawnSync(shellPath as string, ['-c', cmd], { + cwd, + env: { ...process.env, ...env }, + stdio: 'inherit' + }).status + } catch { + status = 1 + } + + if (status !== 0) { + const noVerifyMessage = [ + 'commit-msg', + 'pre-commit', + 'pre-rebase', + 'pre-push' + ].includes(hookName) + ? '(add --no-verify to bypass)' + : '(cannot be bypassed with --no-verify due to Git specs)' + + console.log(`husky > ${hookName} hook failed ${noVerifyMessage}`) + } + + return status || 0 +} + /** * @param {array} argv - process.argv * @param {promise} getStdinFn - used for mocking only @@ -43,56 +75,44 @@ export default async function run( const oldCommand: string | undefined = pkg && pkg.scripts && pkg.scripts[hookName.replace('-', '')] - // Run command - try { - const env: Env = {} - - if (HUSKY_GIT_PARAMS) { - env.HUSKY_GIT_PARAMS = HUSKY_GIT_PARAMS - } + // Add HUSKY_GIT_PARAMS to env + const env: Env = {} - if ( - ['pre-push', 'pre-receive', 'post-receive', 'post-rewrite'].includes( - hookName - ) - ) { - // Wait for stdin - env.HUSKY_GIT_STDIN = await getStdinFn() - } + if (HUSKY_GIT_PARAMS) { + env.HUSKY_GIT_PARAMS = HUSKY_GIT_PARAMS + } - if (oldCommand) { - console.log() - console.log( - `Warning: Setting ${hookName} script in package.json > scripts will be deprecated` - ) - console.log( - `Please move it to husky.hooks in package.json, a .huskyrc file, or a husky.config.js file` - ) - console.log( - `Or run ./node_modules/.bin/husky-upgrade for automatic update` - ) - console.log() - console.log(`See https://github.com/typicode/husky for usage`) - console.log() - } + // Read stdin + if ( + ['pre-push', 'pre-receive', 'post-receive', 'post-rewrite'].includes( + hookName + ) + ) { + // Add HUSKY_GIT_STDIN to env + env.HUSKY_GIT_STDIN = await getStdinFn() + } - if (command || oldCommand) { - console.log(`husky > ${hookName} (node ${process.version})`) - execa.shellSync(command || oldCommand, { cwd, env, stdio: 'inherit' }) - } + if (oldCommand) { + console.log() + console.log( + `Warning: Setting ${hookName} script in package.json > scripts will be deprecated` + ) + console.log( + `Please move it to husky.hooks in package.json, a .huskyrc file, or a husky.config.js file` + ) + console.log(`Or run ./node_modules/.bin/husky-upgrade for automatic update`) + console.log() + console.log(`See https://github.com/typicode/husky for usage`) + console.log() + } - return 0 - } catch (err) { - const noVerifyMessage = [ - 'commit-msg', - 'pre-commit', - 'pre-rebase', - 'pre-push' - ].includes(hookName) - ? '(add --no-verify to bypass)' - : '(cannot be bypassed with --no-verify due to Git specs)' + if (command) { + return runCmd(cwd, hookName, command, env) + } - console.log(`husky > ${hookName} hook failed ${noVerifyMessage}`) - return err.code + if (oldCommand) { + return runCmd(cwd, hookName, oldCommand, env) } + + return 0 }