“This is the third day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.

Husky source code analysis

preface

Github:github.com/typicode/hu… Git hooks are easy to use

Husky is an essential tool when it comes to front-end engineering. Husky makes it easy to add Git hooks to your project.

The name of the library refers to “Husky”, which is mainly used to detect problems and standardize code before submission, which is supposed to mean: ‘If you don’t standardize your code, you’ll be like a Husky, and you’ll rip it apart’. This implication is similar to lint-Staged, another library often used with Husky

Lint – staged website

Run Linters against staged git files and don’t let 💩 slip into your code base! (Don’t let dad slip into your code base!)

That’s probably where the term ‘shit mountain’ comes from (I guess) personality! It also illustrates the importance of code specification!

Today I will explore the source code, this NPM library does what!

Json and package.json. It can be seen that the compilation entry is SRC and the exit is lib. The main project execution file is lib/index.js

The main files are SRC /index.ts, SRC /bin.ts, and husky.sh

Master file SRC/index. Ts

import cp = require('child_process') / / the child process
import fs = require('fs') // File processing
import p = require('path') // Path method

// Log output
const l = (msg: string): void= > console.log(`husky - ${msg}`)

// Splice git parameters with the node child and return the result
const git = (args: string[]): cp.SpawnSyncReturns<Buffer> =>
  cp.spawnSync('git', args, { stdio: 'inherit' })

/** * Install (); /** * install (); * 2. Check whether the installation directory is the root directory of the project and create the.husky folder * 3. Check whether the.git file exists. If not, there is no Githook file, * 4, * [1] create folder '_' * [2] write.gitignore to '_' file with contents of '*' [3] Copy the husky.sh file from the husky project root directory to the '_' directory of the target project with the same name * [4] Change the execution path of Githook to * [5] in the.husky file of the target project
export function install(dir = '.husky') :void {
  // Ensure that we're inside a git repository
  // If git command is not found, status is null and we should return.
  // That's why status value needs to be checked explicitly.
  / / - 1 -
  if (git(['rev-parse']).status ! = =0) {
    return
  }

  // Custom dir help
  const url = 'https://git.io/Jc3F9'

  / / -- - 2
  // Ensure that we're not trying to install outside of cwd
  if(! p.resolve(process.cwd(), dir).startsWith(process.cwd())) {throw new Error(`.. not allowed (see ${url}) `)}/ / - 3 -
  // Ensure that cwd is git top level
  if(! fs.existsSync('.git')) {
    throw new Error(`.git can't be found (see ${url}) `)}/ / - 4 -
  try {
    / / Create. Husky / _ -- -- -- 4.1
    fs.mkdirSync(p.join(dir, '_'), { recursive: true })

    / / Create. Husky / _ /. Gitignore - 4.2 -
    fs.writeFileSync(p.join(dir, '_/.gitignore'), The '*')

    // Copy husky.sh to .husky/_/husky.sh  ----4.3----
    fs.copyFileSync(p.join(__dirname, '.. /husky.sh'), p.join(dir, '_/husky.sh')) 

    / / Configure repo - 4.4 -
    const { error } = git(['config'.'core.hooksPath', dir])
    if (error) {
      throw error
    }
    / / - 4.5 -
  } catch (e) {
    l('Git hooks failed to install') 
    throw e
  }

  l('Git hooks installed')}/** * set: Create the specified githook file and write the contents of the file * 1, if the file directory does not exist, interrupt and prompt to execute the initial configuration of husky install * 2, write the file, specify the interpreter to execute the shell script for sh, CMD dynamic parameter, NPM run lint * 3. Create successful log */
export function set(file: string, cmd: string) :void {
  const dir = p.dirname(file)
  / / - 1 -
  if(! fs.existsSync(dir)) {throw new Error(
      `can't create hook, ${dir} directory doesn't exist (try running husky install)`)},/ / -- - 2
  fs.writeFileSync(
    file,
    ` #! /bin/sh . "$(dirname "$0")/_/husky.sh"${cmd}
`,
    { mode: 0o0755},)/ / - 3 -
  l(`created ${file}`)}If the githook file exists * 2. If the githook file exists * 3. If the githook file does not exist, run set to add the githook file */
export function add(file: string, cmd: string) :void {
  / / - 1 -
  if (fs.existsSync(file)) {
    / / -- - 2
    fs.appendFileSync(file, `${cmd}\n`)
    l(`updated ${file}`)}else {
    / / - 3 -
    set(file, cmd)
  }
}

/** * uninstall: Uninstall hooksPath specified in install, and restore the default githook path */
export function uninstall() :void {
  git(['config'.'--unset'.'core.hooksPath'])}Copy the code

Import file SRC /bin.ts

Used to take command line arguments and trigger the execution file in SRC /index.ts

#! /usr/bin/env node // specifies the use of node to parse the run file
import p = require('path') // Path method
import h = require('/')   // SRC /index.ts main file method

// Use the process method to obtain the parameters of the command package
// Show usage and exit with code
function help(code: number) {
  console.log(`Usage: husky install [dir] (default: .husky) husky uninstall husky set|add 
      
        [cmd]`
      )
  process.exit(code)
}

// Get CLI arguments
const [, , cmd, ...args] = process.argv
const ln = args.length
const [x, y] = args

// Set or add command in hook
// Execute the function that handles the main file that needs arguments
const hook = (fn: (a1: string, a2: string) => void) = >() :void= >
  // Show usage if no arguments are provided or more than 2! ln || ln >2 ? help(2) : fn(x, y)

// CLI commands
SRC /index.ts (); // Execute SRC /index.ts ()
const cmds: { [key: string]: () = > void } = {
  install: (a) :void= > (ln > 1 ? help(2) : h.install(x)),
  uninstall: h.uninstall, // Call directly with no arguments
  set: hook(h.set),
  add: hook(h.add),
  ['-v'] :() = >
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-var-requires
    console.log(require(p.join(__dirname, '.. /package.json')).version),
}

// Run CLI
try {
  // Run command or show usage for unknown command
  If the command exists, run the specified function. If the command does not exist, print the help log
  cmds[cmd] ? cmds[cmd]() : help(0)}catch (e) {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  console.error(e.message) // Prints error information
  process.exit(1) // Exit with an error
}
Copy the code


Shell script file husky.sh

Remember the two lines of execution that are written by default to custom Githook methods in the set method of the main file?

#!/bin/sh   // Specify that the file is executed using shell parsing

// Execute the files specified in the current current directory and execute the _/husky.sh file in that path, that is, the script file copied to the project from the install method in the main file
// $0 calls the husky.sh script for the current script name
. "$(dirname "$0")/_/husky.sh" 
Copy the code
#!/bin/sh
// Determine whether the length of husky_skip_init is 0
if [ -z "$husky_skip_init" ]; then
// If the value is 0, create a debug function to print error logs
  debug () {
    // HUSKY_DEBUG is printed when it is "1"
    if [ "$HUSKY_DEBUG" = "1" ]; then
      // $1 represents a parameter
      echo "husky (debug) - $1"
    fi
  }

  // Declare a read-only parameter basename + file name
  readonly hook_name="$(basename "$0")"
  debug "starting $hook_name..."

  // HUSKY = 0
  if [ "$HUSKY" = "0" ]; then
    debug "HUSKY env variable is set to 0, skipping hook"
    exit 0
  fi

  // Check whether ~/.huskyrc is a normal file
  if [ -f ~/.huskyrc ]; then
    debug "sourcing ~/.huskyrc"
    . ~/.huskyrc
  fi

  // Declare read-only variables
  export readonly husky_skip_init=1
  // Execute if the current file name exists in the passed argument
  sh -e "$0" "$@"
  exitCode="$?"

  // if exitCode is not equal to 0, print the current hook name and exitCode
  if [ $exitCode != 0 ]; then
    echo "husky - $hook_name hook exited with code $exitCode (error)"
  fi

  exit $exitCode
fi

Copy the code

conclusion

The core code of the tool is

Git config core.hooksPath// Specify the path triggered by githook
git config --unset core.hooksPath // Unmount the specified path and restore the default path
Copy the code

If the path of hook execution is changed and the hook file of corresponding hook is created in this path, the file will be triggered to execute at the specified stage.

Example: SRC /pre-commit executes the file code at Git commit time

 #!/bin/sh

 npm run lint
Copy the code

This library mainly encapsulates git commands and improves the error reporting mechanism for the convenience of developers.

The author printed the help function of the CLI, probably because there are not many commands, but when writing the CLI, it is recommended to use the command library, which can help you deal with a lot of things, of course, will increase the volume of the package.