[{"data":1,"prerenderedAt":821},["ShallowReactive",2],{"article-\u002Fblog\u002Fgit-hooks-with-husky":3},{"id":4,"title":5,"author":6,"body":7,"description":808,"extension":809,"intro":810,"meta":811,"name":810,"navigation":214,"path":813,"pubDate":814,"role":810,"seo":815,"stem":816,"tags":817,"__hash__":820},"content\u002Fblog\u002Fgit-hooks-with-husky.md","Using Git Hooks With Husky.js","Danny Krämer",{"type":8,"value":9,"toc":800},"minimark",[10,15,19,39,48,52,91,96,136,139,143,150,153,158,165,696,700,706,779,787,796],[11,12,14],"h2",{"id":13},"what-are-git-hooks-anyway","What are git hooks anyway?",[16,17,18],"p",{},"Everybody knows this situation: You set up linters and formatting, you make sure the unit tests are running and all is green. You check out the development branch and the first thing you see is the linter screaming at you. Someone pushed to the development branch without pleasing the allmighty linter overloard. You jump onto slack and ask if the person could fix their code, because you know, we are caring about code quality here. Or more often than not you fix it yourself.",[16,20,21,22,29,30,34,35,38],{},"Here ",[23,24,28],"a",{"href":25,"rel":26},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgithooks",[27],"nofollow","git hooks"," come in handy. Git hooks are a built in feature of git. Hooks are scripts that can be run in most of the stages of your git workflow (pre-commit, pre-push etc). All you have to do is put your scripts (they must be executable, so you can write them in whatever language you like) in the ",[31,32,33],"code",{},"$GIT_DIR\u002Fhooks"," directory. But there is a problem: The ",[31,36,37],{},".git"," directoy is not part of the repository. You cannot commit them and share them with your team easily. Here husky.js comes in handy.",[40,41,42],"blockquote",{},[16,43,44,45],{},"TIPP: Your scripts must be executable. So make sure that the access permissions are set right. On Linux machines you can do this with ",[31,46,47],{},"chmod +x \u003Cscript-file>",[11,49,51],{"id":50},"huskyjs-for-managing-git-hooks","Husky.js for managing Git Hooks",[16,53,54,59,60,63,64,67,68,71,72,74,75,78,79,82,83,86,87,90],{},[23,55,58],{"href":56,"rel":57},"https:\u002F\u002Ftypicode.github.io\u002Fhusky",[27],"Husky.js"," is a npm package that helps you to organize and share your git hooks. In theory you could just let every developer change the ",[31,61,62],{},"core.hooksPath"," property in their local git repository to a directory with the shared scripts. Husky helps you to automate this process. It adds a ",[31,65,66],{},"prepare"," script to your ",[31,69,70],{},"package.json"," that sets the ",[31,73,62],{}," property to a ",[31,76,77],{},".husky"," folder in the repository and builds some scaffolding around your hooks. All a new Dev in your project has to do is checkout the repository, run ",[31,80,81],{},"npm install"," and ",[31,84,85],{},"npm run prepare"," and the hooks will work for her. She can also add some of her own hooks for example with ",[31,88,89],{},"npx husky add .husky\u002Fpre-commit \"npm test\"",".",[40,92,93],{},[16,94,95],{},"TIPP: Here is a basic setup for a new project.",[97,98,99,105,121,130],"ol",{},[100,101,102],"li",{},[31,103,104],{},"npm install husky --save-dev",[100,106,107,108,110,111,113,114,117,118],{},"If your ",[31,109,70],{}," is in the same directory as your ",[31,112,37],{}," directory run ",[31,115,116],{},"npx husky install"," otherwise you can install husky in a custom directory ",[31,119,120],{},"npx husky install ~\u002Fproject\u002F.husky",[100,122,123,124,126,127,129],{},"You can add the prepare lifecycle script into your ",[31,125,70],{},". The script will be run after ",[31,128,81],{},". If you don't want to auto install husky you can just give it another name and run it manually.",[100,131,132,133],{},"Add a hook with ",[31,134,135],{},"npm husky add .husky\u002Fpre-commit \"npm test\"",[16,137,138],{},"So Husky is a lightweight package that takes an annoying task of setting up the shared git hooks of your team and basically wraps it into one terminal command.",[11,140,142],{"id":141},"useful-scripts","Useful Scripts",[16,144,145],{},[146,147],"img",{"alt":148,"src":149},"Automate all the things meme","https:\u002F\u002Fwww.kitchensoap.com\u002Fwp-content\u002Fuploads\u002F2012\u002F07\u002Fautomate_all_the_things1.jpeg",[16,151,152],{},"But what are some useful scripts to run in your git workflow?",[154,155,157],"h3",{"id":156},"prepare-commit-msg-check-for-jira-id","Prepare-commit-msg check for Jira ID",[16,159,160,161,164],{},"This hook will run if you ",[31,162,163],{},"git commit"," right after preparing the default log message, and before the editor is started. Here is a bash script that checks if the branch name contains a Jira ID. If so it checks if the Jira ID of the branch name and the ID of the commit message are the same. If the branch name contains a Jira ID and the commit message does not, it adds the Jira ID of the branch name to the commit mesasge.",[166,167,172],"pre",{"className":168,"code":169,"language":170,"meta":171,"style":171},"language-bash shiki shiki-themes github-dark github-light","#!\u002Fusr\u002Fbin\u002Fenv bash\n. \"$(dirname -- \"$3\")\u002F_\u002Fhusky.sh\"\n\n# Git Hook for JIRA_TASK_ID\n# Adds to the top of your commit message `JIRA_TASK_ID`, based on the prefix of the current branch `feature\u002FAWG-562-add-linter`\n# Example: `Add SwiftLint -> `AWG-562 Add SwiftLint\n\nif [ -z \"$BRANCHES_TO_SKIP\" ]; then\n  BRANCHES_TO_SKIP='master feature'\nfi\n\nCURRENT_BRANCH=$(git branch --show-current)\nfor BRANCH in $BRANCHES_TO_SKIP; do\n  if [ \"$BRANCH\" = \"$CURRENT_BRANCH\" ]; then\n    echo \"Info, skipping Jira ID check since current branch is included in the 'BRANCHES_TO_SKIP' variable\"\n    exit 0\n  fi\ndone\n\nCOMMIT_FILE=$1\nCOMMIT_MSG=$(cat \"$1\")\nCURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\nJIRA_ID_REGEX=\"[A-Z0-9]{1,10}-?[A-Z0-9]+\"\nJIRA_ID_IN_CURRENT_BRANCH_NAME=$(echo \"$CURRENT_BRANCH\" | { grep -Eo \"$JIRA_ID_REGEX\" || true; })\nJIRA_ID_IN_COMMIT_MESSAGE=$(echo \"$COMMIT_MSG\" | { grep -Eo \"$JIRA_ID_REGEX\" || true; })\n\nif [ -n \"$JIRA_ID_IN_COMMIT_MESSAGE\" ]; then\n  if [ \"$JIRA_ID_IN_COMMIT_MESSAGE\" != \"$JIRA_ID_IN_CURRENT_BRANCH_NAME\" ]; then\n    echo \"Error, your commit message JIRA_TASK_ID='$JIRA_ID_IN_COMMIT_MESSAGE' is not equal to current branch JIRA_TASK_ID='$JIRA_ID_IN_CURRENT_BRANCH_NAME'\"\n    exit 1\n  fi\nelif [ -n \"$JIRA_ID_IN_CURRENT_BRANCH_NAME\" ]; then\n  echo \"$JIRA_ID_IN_CURRENT_BRANCH_NAME $COMMIT_MSG\" > \"$COMMIT_FILE\"\n  echo \"JIRA ID '$JIRA_ID_IN_CURRENT_BRANCH_NAME', matched in current branch name, prepended to commit message. (Use --no-verify to skip)\"\nfi\n","bash","",[31,173,174,183,209,216,222,228,234,239,267,279,285,290,313,331,360,369,378,384,390,395,407,429,451,462,509,548,553,574,601,619,627,632,652,678,691],{"__ignoreMap":171},[175,176,179],"span",{"class":177,"line":178},"line",1,[175,180,182],{"class":181},"sryI4","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[175,184,186,189,193,197,200,203,206],{"class":177,"line":185},2,[175,187,90],{"class":188},"s0DvM",[175,190,192],{"class":191},"sg6BJ"," \"$(",[175,194,196],{"class":195},"s-Z4r","dirname",[175,198,199],{"class":188}," --",[175,201,202],{"class":191}," \"",[175,204,205],{"class":188},"$3",[175,207,208],{"class":191},"\")\u002F_\u002Fhusky.sh\"\n",[175,210,212],{"class":177,"line":211},3,[175,213,215],{"emptyLinePlaceholder":214},true,"\n",[175,217,219],{"class":177,"line":218},4,[175,220,221],{"class":181},"# Git Hook for JIRA_TASK_ID\n",[175,223,225],{"class":177,"line":224},5,[175,226,227],{"class":181},"# Adds to the top of your commit message `JIRA_TASK_ID`, based on the prefix of the current branch `feature\u002FAWG-562-add-linter`\n",[175,229,231],{"class":177,"line":230},6,[175,232,233],{"class":181},"# Example: `Add SwiftLint -> `AWG-562 Add SwiftLint\n",[175,235,237],{"class":177,"line":236},7,[175,238,215],{"emptyLinePlaceholder":214},[175,240,242,246,250,253,255,258,261,264],{"class":177,"line":241},8,[175,243,245],{"class":244},"scx8i","if",[175,247,249],{"class":248},"sQ3_J"," [ ",[175,251,252],{"class":244},"-z",[175,254,202],{"class":191},[175,256,257],{"class":248},"$BRANCHES_TO_SKIP",[175,259,260],{"class":191},"\"",[175,262,263],{"class":248}," ]; ",[175,265,266],{"class":244},"then\n",[175,268,270,273,276],{"class":177,"line":269},9,[175,271,272],{"class":248},"  BRANCHES_TO_SKIP",[175,274,275],{"class":244},"=",[175,277,278],{"class":191},"'master feature'\n",[175,280,282],{"class":177,"line":281},10,[175,283,284],{"class":244},"fi\n",[175,286,288],{"class":177,"line":287},11,[175,289,215],{"emptyLinePlaceholder":214},[175,291,293,296,298,301,304,307,310],{"class":177,"line":292},12,[175,294,295],{"class":248},"CURRENT_BRANCH",[175,297,275],{"class":244},[175,299,300],{"class":248},"$(",[175,302,303],{"class":195},"git",[175,305,306],{"class":191}," branch",[175,308,309],{"class":188}," --show-current",[175,311,312],{"class":248},")\n",[175,314,316,319,322,325,328],{"class":177,"line":315},13,[175,317,318],{"class":244},"for",[175,320,321],{"class":248}," BRANCH ",[175,323,324],{"class":244},"in",[175,326,327],{"class":248}," $BRANCHES_TO_SKIP; ",[175,329,330],{"class":244},"do\n",[175,332,334,337,339,341,344,346,349,351,354,356,358],{"class":177,"line":333},14,[175,335,336],{"class":244},"  if",[175,338,249],{"class":248},[175,340,260],{"class":191},[175,342,343],{"class":248},"$BRANCH",[175,345,260],{"class":191},[175,347,348],{"class":244}," =",[175,350,202],{"class":191},[175,352,353],{"class":248},"$CURRENT_BRANCH",[175,355,260],{"class":191},[175,357,263],{"class":248},[175,359,266],{"class":244},[175,361,363,366],{"class":177,"line":362},15,[175,364,365],{"class":188},"    echo",[175,367,368],{"class":191}," \"Info, skipping Jira ID check since current branch is included in the 'BRANCHES_TO_SKIP' variable\"\n",[175,370,372,375],{"class":177,"line":371},16,[175,373,374],{"class":188},"    exit",[175,376,377],{"class":188}," 0\n",[175,379,381],{"class":177,"line":380},17,[175,382,383],{"class":244},"  fi\n",[175,385,387],{"class":177,"line":386},18,[175,388,389],{"class":244},"done\n",[175,391,393],{"class":177,"line":392},19,[175,394,215],{"emptyLinePlaceholder":214},[175,396,398,401,403],{"class":177,"line":397},20,[175,399,400],{"class":248},"COMMIT_FILE",[175,402,275],{"class":244},[175,404,406],{"class":405},"sFbx2","$1\n",[175,408,410,413,415,417,420,422,425,427],{"class":177,"line":409},21,[175,411,412],{"class":248},"COMMIT_MSG",[175,414,275],{"class":244},[175,416,300],{"class":248},[175,418,419],{"class":195},"cat",[175,421,202],{"class":191},[175,423,424],{"class":188},"$1",[175,426,260],{"class":191},[175,428,312],{"class":248},[175,430,432,434,436,438,440,443,446,449],{"class":177,"line":431},22,[175,433,295],{"class":248},[175,435,275],{"class":244},[175,437,300],{"class":248},[175,439,303],{"class":195},[175,441,442],{"class":191}," rev-parse",[175,444,445],{"class":188}," --abbrev-ref",[175,447,448],{"class":191}," HEAD",[175,450,312],{"class":248},[175,452,454,457,459],{"class":177,"line":453},23,[175,455,456],{"class":248},"JIRA_ID_REGEX",[175,458,275],{"class":244},[175,460,461],{"class":191},"\"[A-Z0-9]{1,10}-?[A-Z0-9]+\"\n",[175,463,465,468,470,472,475,477,479,481,484,487,490,493,495,498,500,503,506],{"class":177,"line":464},24,[175,466,467],{"class":248},"JIRA_ID_IN_CURRENT_BRANCH_NAME",[175,469,275],{"class":244},[175,471,300],{"class":248},[175,473,474],{"class":188},"echo",[175,476,202],{"class":191},[175,478,353],{"class":248},[175,480,260],{"class":191},[175,482,483],{"class":244}," |",[175,485,486],{"class":248}," { ",[175,488,489],{"class":195},"grep",[175,491,492],{"class":188}," -Eo",[175,494,202],{"class":191},[175,496,497],{"class":248},"$JIRA_ID_REGEX",[175,499,260],{"class":191},[175,501,502],{"class":244}," ||",[175,504,505],{"class":188}," true",[175,507,508],{"class":248},"; })\n",[175,510,512,515,517,519,521,523,526,528,530,532,534,536,538,540,542,544,546],{"class":177,"line":511},25,[175,513,514],{"class":248},"JIRA_ID_IN_COMMIT_MESSAGE",[175,516,275],{"class":244},[175,518,300],{"class":248},[175,520,474],{"class":188},[175,522,202],{"class":191},[175,524,525],{"class":248},"$COMMIT_MSG",[175,527,260],{"class":191},[175,529,483],{"class":244},[175,531,486],{"class":248},[175,533,489],{"class":195},[175,535,492],{"class":188},[175,537,202],{"class":191},[175,539,497],{"class":248},[175,541,260],{"class":191},[175,543,502],{"class":244},[175,545,505],{"class":188},[175,547,508],{"class":248},[175,549,551],{"class":177,"line":550},26,[175,552,215],{"emptyLinePlaceholder":214},[175,554,556,558,560,563,565,568,570,572],{"class":177,"line":555},27,[175,557,245],{"class":244},[175,559,249],{"class":248},[175,561,562],{"class":244},"-n",[175,564,202],{"class":191},[175,566,567],{"class":248},"$JIRA_ID_IN_COMMIT_MESSAGE",[175,569,260],{"class":191},[175,571,263],{"class":248},[175,573,266],{"class":244},[175,575,577,579,581,583,585,587,590,592,595,597,599],{"class":177,"line":576},28,[175,578,336],{"class":244},[175,580,249],{"class":248},[175,582,260],{"class":191},[175,584,567],{"class":248},[175,586,260],{"class":191},[175,588,589],{"class":244}," !=",[175,591,202],{"class":191},[175,593,594],{"class":248},"$JIRA_ID_IN_CURRENT_BRANCH_NAME",[175,596,260],{"class":191},[175,598,263],{"class":248},[175,600,266],{"class":244},[175,602,604,606,609,611,614,616],{"class":177,"line":603},29,[175,605,365],{"class":188},[175,607,608],{"class":191}," \"Error, your commit message JIRA_TASK_ID='",[175,610,567],{"class":248},[175,612,613],{"class":191},"' is not equal to current branch JIRA_TASK_ID='",[175,615,594],{"class":248},[175,617,618],{"class":191},"'\"\n",[175,620,622,624],{"class":177,"line":621},30,[175,623,374],{"class":188},[175,625,626],{"class":188}," 1\n",[175,628,630],{"class":177,"line":629},31,[175,631,383],{"class":244},[175,633,635,638,640,642,644,646,648,650],{"class":177,"line":634},32,[175,636,637],{"class":244},"elif",[175,639,249],{"class":248},[175,641,562],{"class":244},[175,643,202],{"class":191},[175,645,594],{"class":248},[175,647,260],{"class":191},[175,649,263],{"class":248},[175,651,266],{"class":244},[175,653,655,658,660,662,665,667,670,672,675],{"class":177,"line":654},33,[175,656,657],{"class":188},"  echo",[175,659,202],{"class":191},[175,661,594],{"class":248},[175,663,664],{"class":248}," $COMMIT_MSG",[175,666,260],{"class":191},[175,668,669],{"class":244}," >",[175,671,202],{"class":191},[175,673,674],{"class":248},"$COMMIT_FILE",[175,676,677],{"class":191},"\"\n",[175,679,681,683,686,688],{"class":177,"line":680},34,[175,682,657],{"class":188},[175,684,685],{"class":191}," \"JIRA ID '",[175,687,594],{"class":248},[175,689,690],{"class":191},"', matched in current branch name, prepended to commit message. (Use --no-verify to skip)\"\n",[175,692,694],{"class":177,"line":693},35,[175,695,284],{"class":244},[154,697,699],{"id":698},"pre-push-run-unit-tests-and-linters","Pre-push run unit tests and linters",[16,701,702,703,705],{},"This script runs before you push to remote. It goes to the directory of the ",[31,704,70],{}," and runs the unit tests and linting scripts. If one of those ends with an exit code other than 0 the push is aborted.",[166,707,709],{"className":168,"code":708,"language":170,"meta":171,"style":171},"#!\u002Fusr\u002Fbin\u002Fenv sh\n. \"$(dirname -- \"$3\")\u002F_\u002Fhusky.sh\"\n\n# runs frontend unit tests and linters\ncd ~\u002Fproject\u002F && npm run test:prod && npm run lint:js && npm run lint:style\n",[31,710,711,716,732,736,741],{"__ignoreMap":171},[175,712,713],{"class":177,"line":178},[175,714,715],{"class":181},"#!\u002Fusr\u002Fbin\u002Fenv sh\n",[175,717,718,720,722,724,726,728,730],{"class":177,"line":185},[175,719,90],{"class":188},[175,721,192],{"class":191},[175,723,196],{"class":195},[175,725,199],{"class":188},[175,727,202],{"class":191},[175,729,205],{"class":188},[175,731,208],{"class":191},[175,733,734],{"class":177,"line":211},[175,735,215],{"emptyLinePlaceholder":214},[175,737,738],{"class":177,"line":218},[175,739,740],{"class":181},"# runs frontend unit tests and linters\n",[175,742,743,746,749,752,755,758,761,763,765,767,770,772,774,776],{"class":177,"line":224},[175,744,745],{"class":188},"cd",[175,747,748],{"class":191}," ~\u002Fproject\u002F",[175,750,751],{"class":248}," && ",[175,753,754],{"class":195},"npm",[175,756,757],{"class":191}," run",[175,759,760],{"class":191}," test:prod",[175,762,751],{"class":248},[175,764,754],{"class":195},[175,766,757],{"class":191},[175,768,769],{"class":191}," lint:js",[175,771,751],{"class":248},[175,773,754],{"class":195},[175,775,757],{"class":191},[175,777,778],{"class":191}," lint:style\n",[16,780,781,782],{},"If you want to see some more scripts that could be useful check out this repository: ",[23,783,786],{"href":784,"rel":785},"https:\u002F\u002Fgithub.com\u002Faitemr\u002Fawesome-git-hooks",[27],"awesome-git-hooks",[40,788,789],{},[16,790,791,792,795],{},"TIPP: You can skip all the git hooks if you add the flag ",[31,793,794],{},"--no-verify"," to your git command.",[797,798,799],"style",{},"html pre.shiki code .sryI4, html code.shiki .sryI4{--shiki-dark:#6A737D;--shiki-default:#6A737D}html pre.shiki code .s0DvM, html code.shiki .s0DvM{--shiki-dark:#79B8FF;--shiki-default:#005CC5}html pre.shiki code .sg6BJ, html code.shiki .sg6BJ{--shiki-dark:#9ECBFF;--shiki-default:#032F62}html pre.shiki code .s-Z4r, html code.shiki .s-Z4r{--shiki-dark:#B392F0;--shiki-default:#6F42C1}html pre.shiki code .scx8i, html code.shiki .scx8i{--shiki-dark:#F97583;--shiki-default:#D73A49}html pre.shiki code .sQ3_J, html code.shiki .sQ3_J{--shiki-dark:#E1E4E8;--shiki-default:#24292E}html pre.shiki code .sFbx2, html code.shiki .sFbx2{--shiki-dark:#FFAB70;--shiki-default:#E36209}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":171,"searchDepth":185,"depth":185,"links":801},[802,803,804],{"id":13,"depth":185,"text":14},{"id":50,"depth":185,"text":51},{"id":141,"depth":185,"text":142,"children":805},[806,807],{"id":156,"depth":211,"text":157},{"id":698,"depth":211,"text":699},"Git hooks are useful to automate tasks in your git workflow, but they are hard to maintain. Husky.js is a great way to share and maintain your git hooks with your team.","md",null,{"image":812},{"url":171,"alt":171},"\u002Fblog\u002Fgit-hooks-with-husky","2024-01-24",{"title":5,"description":808},"blog\u002Fgit-hooks-with-husky",[303,818,819],"software development","tooling","sxWEm6H91rhfDTOENfob3kj2sLQnt-Xv0BM3-OOc8S0",1775933922489]