New Features
-
runJscpd()runs the jscpd binary from Node.js.require("jscpd")andimport { runJscpd } from "jscpd"now exportrunJscpd(args, options). It finds the platform binary the way thejscpdcommand does and returns the result ofspawnSync, so a script no longer starts a second Node.js process to run the command, and tools such as knip see that the script usesjscpd. Where the command prints an error and exits, the function throws anErrorwith the same message. The types are inindex.d.ts. Detection still runs only in the Rust binary, and nothing parses or returns clones in JavaScript. By @abrahamguo. (#1238) -
The HTML report shows both fragments of a clone and highlights the code. "Show code" used to open only the first fragment. Now it opens both side by side, or one above the other on a narrow screen, each under its file with line numbers in a gutter, and a long line scrolls inside its pane. The report highlights the code with Prism 1.30.0 again, as the v4 report did, and inlines only the languages it shows. A block inside another file, such as
guide.md:typescript, keeps the lines that detection gives it. (#1263, #1265)
Bug Fixes
-
The GitHub Action installed the newest jscpd, whatever ref you pinned. The
versioninput defaulted tolatest, sokucherenko/jscpd@v5.4.1installed 5.4.2 once it came out. Now an emptyversioninstalls the release that the action's own ref belongs to, so pinning the action pins jscpd, and a Dependabot bump of the action updates both.version: lateststill installs the newest release. This works from v5.4.3 on: older tags still install the newest jscpd. Reported by @dannystaple, fixed by @sarankumar1325. (#1240, #1249) -
A
/*or//inside a JS or TS string ended ajscpd:ignoreregion. The tokenizer looked for the markers with a byte scan that took every/*and//for a comment, so'a /* b'inside a marked region opened a comment that ran to the end marker, and the code after the string came back as clones. The markers are now found by their text, as in the other languages, so a region runs fromjscpd:ignore-startto the nextjscpd:ignore-end. Markers inside template literals, such as the CSS of a styled component, keep working. Reported by @bauchdj, fixed by @sarankumar1325. (#1257, #1258) -
"Ignore this clone" in the language server broke JSX. It wrote
// jscpd:ignore-startamong JSX children, where the page shows it as text, and could put a marker inside a tag, such as< /* jscpd:ignore-end *//ul>, which TypeScript can't parse. Now it writes{/* jscpd:ignore-start */}among children and never puts a marker inside a tag. The start goes before the tag and the end after it, so the region still covers the clone. Each copy of a--cross-formatsclone gets the comment of its own format, JSX in the<script lang="tsx">of a Vue, Svelte or Astro component gets the JSX markers, and a marker after/gets a space so it doesn't turn into a line comment. (#1260, #1264) -
--dead-codereported a file loaded withimport(`./x`)orimport('$lib/x')as unused.import()read only quoted specifiers and turned away any specifier with a$in it, so a backtickimport()with nothing interpolated and SvelteKit's$libalias were never followed. The importing file also lost 30 points of confidence on every finding. jscpd droppedimport.meta.glob(`./pages/*.ts`)the same way. Now only${inside backticks counts as interpolation. On six repositories nothing new was reported. Reported with the cause by @abrahamguo. (#1239, #1251) -
--dead-codereported constructors and dunder methods as unused members.constructorin JavaScript and__init__or__repr__in Python are called by the runtime, not by name, and the unused-member check didn't skip them. It does now. Reported by @abrahamguo, fixed by @sarankumar1325. (#1253, #1254) -
--dead-codereported knip'sknip.tsandknip.jsas unused files. They are knip's config, but no rule knew them. The new knip framework entry keeps them as entry points, and knip shows up in the console'sFrameworks:line. Reported with the fix by @abrahamguo. (#1255, #1256) -
The Linux binaries accepted certificates from the leaked emSign intermediate CAs. rustls-webpki moved to 0.103.16 and webpki-roots and webpki-root-certs to 1.0.10, so they reject them. This matters for
--semantic-urlwith a remote API on Linux. Model downloads are pinned by SHA-256, and macOS and Windows use the system TLS stack. (RUSTSEC-2026-0381, 0382, 0383, #1236) -
nix run github:kucherenko/jscpddidn't build. The flake built the dependencies without the workspace'spasteshim and left out files that the crates include at compile time. It takes the wholerust/folder now, and a new Nix workflow builds and runs the package on Linux and macOS. (#1272)
Other
- The release pipeline asks npmmirror to sync every npm package after a release, because the mirror never copied
jscpd-linux-x64-gnu@5.4.2, and pnpm installs from it lost the Linux binary without a warning. Reported by @icret. (#1261, #1262) - The release pipeline waits up to 30 minutes for npm to serve a new version, and the release tag now points at the release commit. (#1232, #1250)
- The Super Linter section of
docs/ci-and-hooks.mdshows where the config file goes and how to gate on new clones only, with Super Linter v9.0.0 or later. (#994) - The README's "Who Uses jscpd" lists 28 projects and the file in each one that runs jscpd. (#1287)
Dependencies
candlemoved to 0.11.1 andzipto 9.3.0, which fixes RUSTSEC-2026-0429 and the three zip advisories that were ignored until now (#1278, #1279)askamabuilds without itsconfigfeature, so the unmaintainedbasic-tomlleaves the build (RUSTSEC-2026-0374, #1230)oxc,ruff,tree-sitter0.27.1,tree-sitter-scala0.26.4,serde-saphyr2.0.0,ureq3.5.1,ignore0.4.34,memmap20.9.12 andlog0.4.35 moved to newer releases
Thank You ❤️
- @abrahamguo for
runJscpd()and for three dead-code reports that came with the cause and the fix (#1238, #1239, #1253, #1255) - @sarankumar1325 for the Action version pin, the ignore markers and the dead-code member fix (#1249, #1254, #1258)
- @dannystaple, @bauchdj, @crystalfp and @icret for the reports (#1240, #1257, #1263, #1261)
Published Packages
basta@0.3.3on crates.iocpd-core@0.1.23on crates.iocpd-finder@0.1.22on crates.iocpd-reporter@0.1.24on crates.iocpd-semantic@0.1.4on crates.iocpd-similarity@0.1.1on crates.iocpd-tokenizer@0.1.22on crates.iojscpd@5.4.3on crates.iocpd@5.4.3on npmjscpd@5.4.3on npmjscpd-darwin-arm64@5.4.3on npmjscpd-darwin-x64@5.4.3on npmjscpd-linux-x64-gnu@5.4.3on npmjscpd-linux-arm64-gnu@5.4.3on npmjscpd-linux-x64-musl@5.4.3on npmjscpd-linux-arm64-musl@5.4.3on npmjscpd-windows-x64-msvc@5.4.3on npmjscpd-windows-arm64-msvc@5.4.3on npmjscpd==5.4.3on PyPI
Verify
Archives are signed with Sigstore (keyless, <asset>.sigstore.json)
and carry SLSA build provenance. Replace jscpd-linux-x64-gnu.tar.gz with your asset:
cosign verify-blob \
--bundle jscpd-linux-x64-gnu.tar.gz.sigstore.json \
--certificate-identity-regexp '^https://github\.com/kucherenko/jscpd/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
jscpd-linux-x64-gnu.tar.gz
gh attestation verify jscpd-linux-x64-gnu.tar.gz --repo kucherenko/jscpd
sha256sum --check --ignore-missing checksums.txt