CI & Hooks

Use jscpd in Pre-Commit Hook

Run jscpd before every commit to prevent duplicated code from entering the repository.

Run jscpd before every commit to prevent duplicated code from entering the repository.

Using pre-commit framework

The pre-commit framework manages git hooks for you. After configuring the hook, it runs automatically on every git commit.

Install pre-commit

Terminal
# pip
pip install pre-commit

# brew
brew install pre-commit

# npm (wrapper around the Python tool)
npm install -g pre-commit

Add the hook config

Add the hook config to .pre-commit-config.yaml in your repo.

Option A: language: node — pre-commit installs jscpd automatically:

.pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: jscpd
        name: jscpd - copy/paste detector
        entry: jscpd
        language: node
        additional_dependencies: ['jscpd@5']
        args: [--threshold, "5", --reporters, console,silent]
        pass_filenames: false
        always_run: true

Option B: language: system — jscpd must be pre-installed globally:

.pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: jscpd
        name: jscpd - copy/paste detector
        entry: jscpd
        language: system
        args: [--threshold, "5", --reporters, console,silent]
        pass_filenames: false
        always_run: true

If using Option B, install jscpd globally first: npm install -g jscpd@5 or cargo install jscpd.

Install the hook into git

Terminal
pre-commit install

jscpd now runs on every git commit. If duplication exceeds the threshold, the commit is blocked.

To run manually without committing:

Terminal
pre-commit run jscpd --all-files

Using Husky

Terminal
npm install -D husky
npx husky init

Add the hook:

Terminal
echo 'npx jscpd@5 --threshold 5 --reporters console,silent .' > .husky/pre-commit

Manual git hook

No extra tools required — just a shell script in .git/hooks/.

Create the hook

Create .git/hooks/pre-commit:

.git/hooks/pre-commit
#!/bin/sh
jscpd --threshold 5 --reporters console,silent .

Make it executable:

Terminal
chmod +x .git/hooks/pre-commit

Hooks in .git/hooks/ are not version-controlled. To share the hook with your team, store it in the repo and symlink or copy it.

Store the hook logic in the repo (e.g. scripts/pre-commit), then symlink:

Terminal
ln -s ../../scripts/pre-commit .git/hooks/pre-commit

Each developer runs the symlink command once after cloning.

Option B: core.hooksPath (Git 2.9+)

Point Git at a versioned hooks directory:

Terminal
git config core.hooksPath .githooks

Create .githooks/pre-commit:

.githooks/pre-commit
#!/bin/sh
jscpd --threshold 5 --reporters console,silent .
Terminal
chmod +x .githooks/pre-commit

Commit .githooks/ to the repo. New contributors run the git config command once after cloning. Add it to your onboarding docs or a scripts/setup.sh:

scripts/setup.sh
#!/bin/sh
git config core.hooksPath .githooks

Option C: npm prepare script

Add to package.json:

package.json
{
  "scripts": {
    "prepare": "git config core.hooksPath .githooks"
  }
}

npm install (and npm ci) automatically run prepare, so the hooks path is set with no manual steps.

Option D: Makefile

Makefile
.PHONY: hooks
hooks:
    git config core.hooksPath .githooks

Contributors run make hooks after cloning.

Tips

  • Use --reporters console,silent to show clone details without writing report files on every commit
  • Use --threshold to set a failure threshold — the hook exits with code 1 if exceeded
  • Use --ignore to exclude generated files, test fixtures, or vendor directories
  • For large repos, use the Rust engine (jscpd@5 / cpd) — it runs 24-37x faster, keeping commit latency low
  • Consider --format to limit detection to specific languages during the hook, with a full scan in CI