Repository: TheOdinProject/odin-bot-v2
Pull request #701 (2025-03-21 17:16:13)

Fix: Migrate husky hooks to v9 format
<!-- Thank you for taking the time to contribute to The Odin Project. In order to get this pull request (PR) merged in a reasonable amount of time, you must complete this entire template. -->

## Because

The current installed Husky version is v9 but `package.json` contains the old v4 way of setting hooks. This doesn't work in v9 so `lint-staged` was never being run pre-commit. While we already have GH workflows for the same actions, it helps to have this at the local level too and reduce "appease linter/formatter" commits. Belts and braces.

## This PR

<!-- A bullet point list of one or more items describing the specific changes. -->
- Migrates to husky v9 way of setting a pre-commit hook
- Adds `prepare` npm script to ensure `npm install` also initialises husky (its setup files include a `.gitignore` that ignores themselves) https://typicode.github.io/husky/how-to.html#manual-setup
- Amends `lint-staged` tasks for different file types to also include Prettier

## Issue

<!--
If this PR closes an open issue in this repo, replace the XXXXX below with the issue number, e.g. Closes #2013.

If this PR closes an open issue in another TOP repo, replace the #XXXXX with the URL of the issue, e.g. Closes https://github.com/TheOdinProject/curriculum/issues/XXXXX

If this PR does not close, but is related to another issue or PR, you can link it as above without the 'Closes' keyword, e.g. 'Related to #2013'.
-->

N/A

## Additional Information

Example trying to commit a single change to `index.js` that includes a `let foo;` somewhere.

Current behaviour:

![image](https://github.com/user-attachments/assets/26af0977-ad8b-468e-b508-843ff8861128)

With PR fix:

![image](https://github.com/user-attachments/assets/a8b62956-59d8-4245-a336-a06f7294a870)

## Pull Request Requirements

<!-- Replace the whitespace between the square brackets with an 'x', e.g. [x]. After you create the PR, they will become checkboxes that you can click on. -->

- [x] I have thoroughly read and understand [The Odin Project Contributing Guide](https://github.com/TheOdinProject/.github/blob/main/CONTRIBUTING.md)
- [x] The title of this PR follows the `location of change: brief description of change` format, e.g. `Callbacks command: Update verbiage`
- [x] The `Because` section summarizes the reason for this PR
- [x] The `This PR` section has a bullet point list describing the changes in this PR
- [x] If this PR addresses an open issue, it is linked in the `Issue` section
- [x] If this PR adds new features or functionality, I have added new tests
- [x] If applicable, I have ensured all tests related to any command files included in this PR pass, and/or all snapshots are up to date



Patch:
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..b3db63e
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,2 @@
+lint-staged
+npm run test
diff --git a/bot-commands/points/award-points.js b/bot-commands/points/award-points.js
index 9999dfa..62e6257 100644
--- a/bot-commands/points/award-points.js
+++ b/bot-commands/points/award-points.js
@@ -1,6 +1,6 @@
 const axios = require('axios');
 const config = require('../../config');
-const club40Gifs = require('../club-40/club_40_gifs.json');
+const club40Gifs = require('./club-40-gifs.json');
 const { isAdmin } = require('../../utils/is-admin');
 
 axios.default.defaults.headers.common.Authorization = `Token ${config.pointsbot.token}`;
diff --git a/package-lock.json b/package-lock.json
index 939fcd0..f16a944 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
       "license": "ISC",
       "dependencies": {
         "@discordjs/rest": "^2.4.3",
-        "axios": "^1.8.2",
+        "axios": "^1.8.4",
         "discord-api-types": "^0.37.119",
         "discord.js": "^14.18.0",
         "dotenv": "^16.4.7",
@@ -2963,9 +2963,9 @@
       }
     },
     "node_modules/axios": {
-      "version": "1.8.2",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
-      "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
+      "version": "1.8.4",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+      "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
       "license": "MIT",
       "dependencies": {
         "follow-redirects": "^1.15.6",
@@ -10795,9 +10795,9 @@
       }
     },
     "axios": {
-      "version": "1.8.2",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
-      "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
+      "version": "1.8.4",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+      "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
       "requires": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
diff --git a/package.json b/package.json
index 0209d8a..3da7a8f 100644
--- a/package.json
+++ b/package.json
@@ -4,8 +4,9 @@
   "description": "",
   "main": "index.js",
   "scripts": {
+    "prepare": "husky",
     "start": "node index.js",
-    "dev": "nodemon index.js",
+    "dev": "nodemon --watch . --watch .env index.js",
     "test": "NODE_OPTIONS=--experimental-vm-modules jest",
     "format": "prettier --cache --write",
     "eslint": "eslint '**/*.js'"
@@ -14,7 +15,7 @@
   "license": "ISC",
   "dependencies": {
     "@discordjs/rest": "^2.4.3",
-    "axios": "^1.8.2",
+    "axios": "^1.8.4",
     "discord-api-types": "^0.37.119",
     "discord.js": "^14.18.0",
     "dotenv": "^16.4.7",
@@ -34,12 +35,11 @@
     "lint-staged": "^15.5.0",
     "nodemon": "^3.1.9"
   },
-  "husky": {
-    "hooks": {
-      "pre-commit": "lint-staged && npm run test"
-    }
-  },
   "lint-staged": {
-    "*.js": "npx eslint"
+    "!(*.js)": "prettier --check",
+    "*.js": [
+      "prettier --check",
+      "eslint"
+    ]
   }
 }


Test patch:
diff --git a/bot-commands/club-40/club_40_gifs.json b/bot-commands/points/club-40-gifs.json
similarity index 100%
rename from bot-commands/club-40/club_40_gifs.json
rename to bot-commands/points/club-40-gifs.json
diff --git a/bot-commands/points/points.test.js b/bot-commands/points/points.test.js
index 66ade3e..4c2d027 100644
--- a/bot-commands/points/points.test.js
+++ b/bot-commands/points/points.test.js
@@ -22,7 +22,7 @@ const gifContainer = [
   },
 ];
 
-jest.mock('../club-40/club_40_gifs.json', () => gifContainer);
+jest.mock('./club-40-gifs.json', () => gifContainer);
 
 jest.mock('discord.js', () => ({
   ...jest.requireActual('discord.js'),