<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><script src="https://www.rss.style/js/atom-style.js" xmlns="http://www.w3.org/1999/xhtml"/><title>Tower of Kubes</title><link rel="self" type="application/atom+xml" hreflang="en" href="https://www.towerofkubes.com/tags/pre-commit/feed.xml"/><link rel="alternate" type="text/html" hreflang="en" href="https://www.towerofkubes.com/tags/pre-commit/"/><link rel="alternate" type="application/rss+xml" hreflang="en" href="https://www.towerofkubes.com/tags/pre-commit/index.xml"/><id>/</id><updated>2025-11-10T00:00:00Z</updated><author><name>Ro'i Bandel</name></author><generator>Hugo 0.157.0</generator><entry><title>Pre-commit hooks for Node.js projects</title><link rel="alternate" type="text/html" hreflang="en" href="https://www.towerofkubes.com/articles/pre-commit-hooks/"/><id>https://www.towerofkubes.com/articles/pre-commit-hooks/</id><updated>2025-11-10T00:00:00Z</updated><summary type="html">Step-by-step Node.js pre-commit setup: Husky + lint-staged + Oxc to enforce linting, formatting, and TypeScript checks before every git commit.</summary><content type="html"><![CDATA[<p>This is my workflow for integrating pre-commit hooks for Node.js (NPM) projects. I combine this with my <a href="/articles/oxc-workflow/" >Oxc Workflow</a>.</p>

<h2 class="relative group">What is a Git pre-commit hook?
    <div id="what-is-a-git-pre-commit-hook" class="anchor"></div>
    
</h2>
<p>A <code>pre-commit</code> is a type of <a href="https://git-scm.com/book/ms/v2/Customizing-Git-Git-Hooks"  target="_blank" rel="noreferrer">Git Hook</a> that runs before each commit. It can help with verifying code standards (linting, formatting, testing etc.).</p>

<h2 class="relative group">Pre-commit tools
    <div id="pre-commit-tools" class="anchor"></div>
    
</h2>

<h3 class="relative group"><a href="https://pre-commit.com/"  target="_blank" rel="noreferrer">pre-commit</a>
    <div id="pre-commit" class="anchor"></div>
    
</h3>
<p>A tool written in Python, though can be used with projects in any language. Can be configured to run many hooks including <a href="https://github.com/pre-commit/pre-commit-hooks"  target="_blank" rel="noreferrer">pre-commit/pre-commit-hooks</a> and <a href="https://github.com/gitleaks/gitleaks?tab=readme-ov-file#pre-commit"  target="_blank" rel="noreferrer">Gitleaks</a>. I have used this tool and like it for Python and other projects, though for Node.js projects, I prefer the options below.</p>

<h3 class="relative group"><a href="https://typicode.github.io/husky/"  target="_blank" rel="noreferrer">Husky</a>
    <div id="husky" class="anchor"></div>
    
</h3>
<p>A pre-commit hooks tool written in JavaScript. I prefer this tool for Node.js projects since it can be easily integrated in <code>package.json</code> scripts.</p>

<h3 class="relative group"><a href="https://www.npmjs.com/package/lint-staged"  target="_blank" rel="noreferrer">lint-staged</a>
    <div id="lint-staged" class="anchor"></div>
    
</h3>
<p>Another tool that’s written in JavaScript, to help run checks against staged files (see <a href="/articles/pre-commit-hooks/#guide-husky--lint-staged--oxc-workflow" >Guide</a> below). Lint-staged does not configure git pre-commit hooks on its own, but can be combined with Husky.</p>

<h3 class="relative group"><a href="https://github.com/toplenboren/simple-git-hooks"  target="_blank" rel="noreferrer">simple-git-hooks</a>
    <div id="simple-git-hooks" class="anchor"></div>
    
</h3>
<p>Another git hooks manager written in JavaScript. Use to be more lightweight than Husky, but newer versions of Husky closed the gap.</p>

<h2 class="relative group">Guide: Husky + Lint-staged + Oxc workflow
    <div id="guide-husky--lint-staged--oxc-workflow" class="anchor"></div>
    
</h2>
<ol>
<li>
<p>Configure <code>oxlint</code> and <code>oxfmt</code> based on my <a href="/articles/oxc-workflow/" >Oxc Workflow</a>.</p>
</li>
<li>
<p>Install <code>devDependencies</code>:</p>
</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">npm install --save-dev husky lint-staged</span></span></code></pre></div></div>
<!-- markdownlint-disable MD029 -->
<ol start="3">
<li>Initialize <code>husky</code>:</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Installed</span>
</span></span><span class="line"><span class="cl">./node_modules/.bin/husky --init</span></span></code></pre></div></div>
<hr>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Not installed</span>
</span></span><span class="line"><span class="cl">npx run husky --init</span></span></code></pre></div></div>
<ol start="4">
<li>Configure <code>lint-staged</code> to run Oxc and <code>tsc</code> checks:</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="p">[</span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @filename: lint-staged.config.js
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @type {import('lint-staged').Configuration}
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s1">'**/*.[jt]s?(x)'</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">'oxfmt'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">'oxlint --type-aware --type-check --fix'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="s1">'**/*.ts?(x)'</span><span class="o">:</span> <span class="p">()</span> <span class="o">=></span> <span class="s1">'tsc -p tsconfig.json --noEmit'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>Note</span>
      </div>
      <div class="admonition-content">
        <p>Can be further configured, but this is a good start for a project using TypeScript and Oxc.</p>
      </div>
    </div><ol start="5">
<li>Configure <code>husky</code> to run <code>lint-staged</code> as a pre-commit hook:</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">npm <span class="nb">exec</span> -- lint-staged --config lint-staged.config.js</span></span></code></pre></div></div>
<ol start="6">
<li>Add a <code>prepare</code>script in <code>package.json</code> :</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl">  <span class="s2">"scripts"</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">"prepare"</span><span class="p">:</span> <span class="s2">"husky"</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span><span class="err">,</span></span></span></code></pre></div></div>

    <div class="admonition note">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 125.7-86.8 86.8c-10.3 10.3-17.5 23.1-21 37.2l-18.7 74.9c-2.3 9.2-1.8 18.8 1.3 27.5L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM549.8 235.7l14.4 14.4c15.6 15.6 15.6 40.9 0 56.6l-29.4 29.4-71-71 29.4-29.4c15.6-15.6 40.9-15.6 56.6 0zM311.9 417L441.1 287.8l71 71L382.9 487.9c-4.1 4.1-9.2 7-14.9 8.4l-60.1 15c-5.5 1.4-11.2-.2-15.2-4.2s-5.6-9.7-4.2-15.2l15-60.1c1.4-5.6 4.3-10.8 8.4-14.9z"/></svg>
        <span>This script may have already been added by <code>husky --init</code>.</span>
      </div>
    </div><hr>
<p><em>Featured image by <a href="https://unsplash.com/@yancymin?utm_source=hugo&utm_medium=referral"  target="_blank" rel="noreferrer">Yancy Min</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-text-description-on-a-computer-screen-842ofHC6MaI?utm_source=hugo&utm_medium=referral"  target="_blank" rel="noreferrer">Unsplash</a>.</em></p>
]]></content><author><name>Ro'i Bandel</name></author><category term="node" label="Node" scheme="https://www.towerofkubes.com/tags/node/"/><category term="javascript" label="Javascript" scheme="https://www.towerofkubes.com/tags/javascript/"/><category term="typescript" label="Typescript" scheme="https://www.towerofkubes.com/tags/typescript/"/><category term="pre-commit" label="Pre-Commit" scheme="https://www.towerofkubes.com/tags/pre-commit/"/><category term="git" label="Git" scheme="https://www.towerofkubes.com/tags/git/"/><published>2025-11-10T00:00:00Z</published></entry></feed>