jscpd-rs 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 - 2026-06-01
4
+
5
+ ### Changed
6
+
7
+ - Rename the Windows prebuilt npm package to `jscpd-rs-win` to avoid npm
8
+ registry spam-policy false positives on the previous machine-generated name.
9
+ - Move the Linux arm64 prebuilt build to the Ubuntu 22.04 ARM runner for a more
10
+ stable native ARM publication path and older glibc baseline.
11
+ - Allow npm release workflow reruns for a single prebuilt target without
12
+ republishing the already-published main package.
13
+
14
+ ## 0.1.1 - 2026-06-01
15
+
16
+ ### Added
17
+
18
+ - npm prebuilt binary distribution wiring for Linux x64 GNU, Linux arm64 GNU,
19
+ macOS x64, macOS arm64, and Windows x64 MSVC platform packages.
20
+ - Runtime npm shim resolution that prefers installed prebuilt optional
21
+ packages and falls back to the existing Cargo source-build path.
22
+ - npm package checks for platform package metadata, source-build fallback, and
23
+ local prebuilt-package smoke tests.
24
+ - GitHub Release npm publishing workflow that builds platform packages before
25
+ publishing the main `jscpd-rs` package.
26
+
3
27
  ## 0.1.0 - 2026-05-31
4
28
 
5
29
  First release candidate for `jscpd-rs`, a native Rust clone of upstream
@@ -62,8 +86,8 @@ Recorded release-candidate public benchmark measurements from
62
86
  - HTML output is self-contained and practically compatible, not pixel-perfect.
63
87
  - The Rust crate exposes a native Rust API, not the upstream JavaScript package
64
88
  API.
65
- - The npm package currently builds native binaries from source during install;
66
- prebuilt platform packages are planned as a later publication improvement.
89
+ - The `jscpd-rs@0.1.0` npm package builds native binaries from source during
90
+ install.
67
91
  - Full Prism grammar parity for every long-tail format is not attempted in this
68
92
  release. Formats should be promoted from generic tokenization when concrete
69
93
  coverage gates show missed upstream lines.
package/Cargo.lock CHANGED
@@ -483,7 +483,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
483
483
 
484
484
  [[package]]
485
485
  name = "jscpd-rs"
486
- version = "0.1.0"
486
+ version = "0.1.2"
487
487
  dependencies = [
488
488
  "anyhow",
489
489
  "axum",
package/Cargo.toml CHANGED
@@ -1,14 +1,14 @@
1
1
  [package]
2
2
  name = "jscpd-rs"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  edition = "2024"
5
5
  rust-version = "1.93"
6
6
  license = "MIT"
7
- description = "Fast Rust clone of jscpd"
7
+ description = "50x+ faster duplicate-code detector for CI/CD; jscpd-compatible CLI, SARIF, JSON, HTML reports"
8
8
  readme = "README.md"
9
9
  repository = "https://github.com/vv-bogdanov/jscpd-rs"
10
10
  documentation = "https://docs.rs/jscpd-rs"
11
- keywords = ["jscpd", "copy-paste", "duplication", "clone-detection", "cli"]
11
+ keywords = ["jscpd", "duplicate-code", "clone-detection", "sarif", "ci"]
12
12
  categories = ["command-line-utilities", "development-tools"]
13
13
  include = [
14
14
  "/CHANGELOG.md",
package/README.md CHANGED
@@ -7,22 +7,24 @@
7
7
  [![license](https://img.shields.io/github/license/vv-bogdanov/jscpd-rs.svg?style=flat-square)](LICENSE)
8
8
  [![rust](https://img.shields.io/badge/rust-1.93%2B-dea584?style=flat-square)](https://www.rust-lang.org/)
9
9
 
10
- Fast native Rust clone of [`jscpd`](https://github.com/kucherenko/jscpd)
11
- for copy-paste and duplicate-code detection in local development and CI/CD.
12
- It scans a codebase, finds duplicated source fragments across files, writes
13
- reports for humans and automation, and can fail a build when duplication
14
- crosses a configured threshold.
15
-
16
- `jscpd-rs` keeps the upstream command shape, configuration formats, reports,
17
- exit-code workflows, and server workflow, while moving the hot path to native
18
- Rust. The practical goal is simple: keep duplication checks always-on without
19
- spending unnecessary CI minutes, developer waiting time, cloud compute budget,
20
- or electricity on repeated quality gates.
21
-
22
- Recorded public release-candidate benchmarks are currently 50x+ faster than
23
- upstream on the covered repositories. The compatibility gate is coverage-first:
24
- on the same inputs and options, `jscpd-rs` must not miss duplicated source lines
25
- reported by upstream `jscpd`.
10
+ 50x+ faster duplicate-code detector for local development, CI/CD, and code
11
+ quality gates.
12
+ `jscpd-rs` scans a codebase, finds copy-paste fragments across files, writes
13
+ console, JSON, SARIF, HTML, XML, CSV, Markdown, badge, and Xcode reports, and
14
+ can fail a build when duplication crosses a configured threshold.
15
+
16
+ It is a native Rust implementation of the common
17
+ [`jscpd`](https://github.com/kucherenko/jscpd) command-line workflow:
18
+ upstream-style CLI flags, `.jscpd.json` and `package.json#jscpd`
19
+ configuration, report formats, exit-code behavior, Git blame, and server
20
+ snippet checks. The practical goal is simple: keep copy-paste detection
21
+ always-on without spending unnecessary CI minutes, developer waiting time,
22
+ cloud compute budget, or electricity on repeated quality gates.
23
+
24
+ Recorded public benchmark baselines show 50x+ speedups over upstream on the
25
+ covered repositories. The compatibility gate is coverage-first: on the same
26
+ inputs and options, `jscpd-rs` must not miss duplicated source lines reported
27
+ by upstream `jscpd`.
26
28
 
27
29
  ## Install
28
30
 
@@ -43,10 +45,11 @@ npx jscpd-rs --version
43
45
  npx jscpd-rs .
44
46
  ```
45
47
 
46
- The first npm package is a source-build package: install/postinstall compiles
47
- the native Rust binaries with Cargo. A Rust toolchain must be available on the
48
- installing machine. Prebuilt platform packages are a planned publication
49
- improvement.
48
+ Current npm packaging note: `jscpd-rs` installs prebuilt Linux, macOS, and
49
+ Windows binaries where available, then falls back to building from source with
50
+ Cargo for unsupported platforms. The original `0.1.0` npm package was
51
+ source-build only; use `0.1.2+` for the full prebuilt-first path. See
52
+ [docs/prebuilt-binaries.md](docs/prebuilt-binaries.md).
50
53
 
51
54
  From this repository:
52
55
 
@@ -114,7 +117,7 @@ If you already use upstream `jscpd`, see
114
117
 
115
118
  ## GitHub Actions
116
119
 
117
- Install from crates.io after publication:
120
+ Install from crates.io:
118
121
 
119
122
  ```yaml
120
123
  jobs:
@@ -169,6 +172,10 @@ npx skills add vv-bogdanov/jscpd-rs --skill dry-refactoring
169
172
 
170
173
  - **Fast CI/CD gates:** duplicate detection should be cheap enough to run on
171
174
  every pull request.
175
+ - **Low-friction rollout:** npm installs use prebuilt binaries on supported
176
+ Linux, macOS, and Windows targets, with a Cargo fallback for other platforms.
177
+ - **Actionable reports:** console, JSON, SARIF, HTML, XML, CSV, Markdown,
178
+ badge, Xcode, threshold, and AI-oriented reports are implemented natively.
172
179
  - **Lower operating cost:** shorter scans reduce paid compute minutes and
173
180
  repeated developer wait time.
174
181
  - **Native detector path:** the detector does not embed or spawn JavaScript for
@@ -181,8 +188,8 @@ npx skills add vv-bogdanov/jscpd-rs --skill dry-refactoring
181
188
 
182
189
  ## What Works Today
183
190
 
184
- This is pre-release software. The first release target is a coverage-first
185
- compatible CLI replacement for common `jscpd` workflows:
191
+ The current release is a coverage-first compatible CLI replacement for common
192
+ `jscpd` duplicate-code and copy-paste detection workflows:
186
193
 
187
194
  - `jscpd` and `jscpd-server` binaries with upstream-compatible command names;
188
195
  - CLI and config option surface covered by compatibility scripts;
@@ -217,7 +224,7 @@ compare their reports.
217
224
 
218
225
  ## Performance
219
226
 
220
- Latest recorded public benchmark baseline:
227
+ Latest recorded public benchmark baseline for duplicate-code detection:
221
228
 
222
229
  | Repo | Format | Rust avg | Upstream avg | Speedup |
223
230
  | --- | --- | ---: | ---: | ---: |
@@ -231,8 +238,8 @@ Reproduce the public benchmark and coverage suite:
231
238
  PUBLIC=1 PUBLIC_RUNS=3 scripts/release-gate.sh
232
239
  ```
233
240
 
234
- The release-candidate workflow reruns the public suite before publication so
235
- README numbers stay tied to a concrete commit and gate output.
241
+ Release-candidate workflows rerun the public suite before each new publication
242
+ so README numbers stay tied to a concrete commit and gate output.
236
243
 
237
244
  ## Library API
238
245
 
@@ -1,8 +1,10 @@
1
1
  # Migrating From jscpd
2
2
 
3
- `jscpd-rs` is a native Rust implementation of the common upstream `jscpd`
4
- workflow. It scans source trees, reports duplicated fragments, and can fail CI
5
- when duplication crosses a configured threshold.
3
+ `jscpd-rs` is a 50x+ faster duplicate-code detector and native Rust
4
+ implementation of the common upstream `jscpd` workflow. It scans source trees,
5
+ reports copy-paste fragments across files, writes console, JSON, SARIF, HTML,
6
+ XML, CSV, Markdown, badge, and Xcode reports, and can fail CI when duplication
7
+ crosses a configured threshold.
6
8
 
7
9
  The migration goal for the first release is practical CLI/reporting
8
10
  compatibility for CI and local scans, not exact JavaScript package API parity.
@@ -16,7 +18,7 @@ cargo install jscpd-rs --locked
16
18
  jscpd --threshold 5 --exitCode 1 src
17
19
  ```
18
20
 
19
- npm/npx after npm publication:
21
+ npm/npx:
20
22
 
21
23
  ```bash
22
24
  npx jscpd-rs --threshold 5 --exitCode 1 src
@@ -125,8 +127,10 @@ These are intentional first-release limits:
125
127
  tokenizer is needed;
126
128
  - the Rust crate exposes a native Rust API, not the upstream JavaScript package
127
129
  API;
128
- - first npm packaging is source-build: a Rust/Cargo toolchain is required during
129
- install until prebuilt platform packages are added.
130
+ - `jscpd-rs@0.1.2+` npm packaging uses prebuilt binaries on supported Linux,
131
+ macOS, and Windows targets, with a Cargo source-build fallback for
132
+ unsupported platforms. The original `0.1.0` npm package was source-build
133
+ only.
130
134
 
131
135
  ## Compare On Your Repository
132
136
 
@@ -178,7 +182,7 @@ Cargo-based `jscpd-rs` CI:
178
182
  - run: jscpd src --reporters console,json --threshold 5 --exitCode 1
179
183
  ```
180
184
 
181
- npm/npx-based `jscpd-rs` CI after npm publication:
185
+ npm/npx-based `jscpd-rs` CI:
182
186
 
183
187
  ```yaml
184
188
  - uses: dtolnay/rust-toolchain@stable
@@ -188,6 +192,7 @@ npm/npx-based `jscpd-rs` CI after npm publication:
188
192
  - run: npx jscpd-rs src --reporters console,json --threshold 5 --exitCode 1
189
193
  ```
190
194
 
191
- The npm source-build package still needs Rust available during installation.
192
- Prebuilt npm platform packages are planned as a publication improvement.
193
-
195
+ `jscpd-rs@0.1.2+` npm installs use prebuilt binaries where available and fall
196
+ back to Cargo source-build on unsupported platforms. The original `0.1.0` npm
197
+ package still needs Rust available during installation; see the
198
+ [prebuilt binary distribution plan](prebuilt-binaries.md).
@@ -13,18 +13,17 @@ Current npm readiness snapshot:
13
13
  - Packed artifact audit: `jscpd-rs-0.1.0.tgz`, 96 files, about 169 KiB packed,
14
14
  about 708 KiB unpacked.
15
15
 
16
- The first npm package is `jscpd-rs`. It exposes these bin commands:
16
+ The npm package is `jscpd-rs`. It exposes these bin commands:
17
17
 
18
18
  - `jscpd-rs`: primary `npx jscpd-rs` entrypoint, runs the native `jscpd` CLI.
19
19
  - `jscpd`: installed alias for the native `jscpd` CLI.
20
20
  - `jscpd-server`: installed alias for the native server binary.
21
21
 
22
- The package is intentionally source-build for the first release candidate:
23
- `postinstall` runs `cargo build --release --locked --bin jscpd --bin
24
- jscpd-server` inside the installed npm package. This keeps the npm path simple
25
- and verifiable before publication. Users installing from npm need Node, npm, and
26
- a Rust/Cargo toolchain. Prebuilt platform packages can be added later without
27
- changing the CLI behavior.
22
+ `jscpd-rs@0.1.2+` publishes prebuilt platform packages before the main
23
+ `jscpd-rs` package. The CLI behavior stays the same, and the source-build path
24
+ remains the fallback for unsupported platforms. The original `0.1.0` package
25
+ was source-build only; see the
26
+ [prebuilt binary distribution plan](prebuilt-binaries.md).
28
27
 
29
28
  Local verification:
30
29
 
@@ -36,14 +35,20 @@ That script verifies:
36
35
 
37
36
  - `package.json` version matches `Cargo.toml`;
38
37
  - `npm pack` includes the expected Rust source and npm shim files;
38
+ - `optionalDependencies` match `npm/prebuilt-targets.json` and the current
39
+ package version;
39
40
  - `npm pack` includes the advertised `skills/` files used by the terminal tip;
40
41
  - forbidden paths such as `jscpd/`, `target/`, `report/`, `scripts/`, and
41
42
  `node_modules/` are not packed;
42
43
  - `npm publish --dry-run --json` succeeds without publishing;
43
- - installing the packed tarball without Cargo fails with the expected Rust
44
- toolchain hint;
45
- - a local npm install exposes working `jscpd-rs`, `jscpd`, and `jscpd-server`
46
- bin commands;
44
+ if the current version is already published, this dry-run is skipped with an
45
+ explicit message;
46
+ - installing the packed tarball without Cargo and without optional prebuilt
47
+ packages fails with the expected Rust toolchain hint;
48
+ - source-build fallback exposes working `jscpd-rs`, `jscpd`, and
49
+ `jscpd-server` bin commands;
50
+ - a locally generated platform package exposes the same bin commands without
51
+ Cargo;
47
52
  - `npx --package <local-tarball> jscpd-rs --version` works.
48
53
 
49
54
  Before actual publication, run:
@@ -55,9 +60,10 @@ scripts/npm-package-check.sh
55
60
  npm view jscpd-rs version
56
61
  ```
57
62
 
58
- `npm view` should return `E404` for the first publication, or the package must
59
- already be owned by this project. Do not run `npm publish` until explicit
60
- release approval.
63
+ For a new version, `npm view jscpd-rs@X.Y.Z version` should fail before the
64
+ release and return that version after publication. Do not run `npm publish`
65
+ manually unless the GitHub Release workflow fails and explicit fallback
66
+ approval is given.
61
67
 
62
68
  Do not use `scripts/prepublish-check.sh` as the npm-only gate after the Cargo
63
69
  crate and GitHub tag have already been published: that script is the full
@@ -84,15 +90,25 @@ specific GitHub Actions workflow through OIDC, so the workflow can publish
84
90
  without a long-lived npm token. For public packages published from public
85
91
  repositories, npm also generates provenance attestations automatically.
86
92
 
87
- This repository provides a manual publish workflow:
93
+ This repository provides a publish workflow:
88
94
 
89
95
  ```text
90
96
  .github/workflows/npm-publish.yml
91
97
  ```
92
98
 
93
- Configure npm:
99
+ The workflow runs automatically when a non-draft, non-prerelease GitHub Release
100
+ is published. It checks out the release tag, verifies that the tag matches the
101
+ `package.json` version, verifies that the main npm version is not already
102
+ published, builds platform packages in a native runner matrix, publishes those
103
+ platform packages first, runs `scripts/npm-package-check.sh`, and then
104
+ publishes the main `jscpd-rs` package.
94
105
 
95
- 1. Open the `jscpd-rs` package settings on npmjs.com.
106
+ It can also be run manually from GitHub Actions as a fallback by entering
107
+ `jscpd-rs` in the confirmation input.
108
+
109
+ Configure npm for every package listed in `docs/prebuilt-binaries.md`:
110
+
111
+ 1. Open the package settings on npmjs.com.
96
112
  2. Go to **Trusted Publisher**.
97
113
  3. Select **GitHub Actions**.
98
114
  4. Use these values:
@@ -102,7 +118,16 @@ Configure npm:
102
118
  - Environment name: leave empty
103
119
  - Allowed actions: `npm publish`
104
120
 
105
- Then publish from GitHub:
121
+ Then publish automatically from GitHub:
122
+
123
+ 1. Update `Cargo.toml` and `package.json` to the same version.
124
+ 2. Update `package.json#optionalDependencies` to the same version for every
125
+ platform package.
126
+ 3. Run the release gates.
127
+ 4. Create and publish a GitHub Release with tag `vX.Y.Z`.
128
+ 5. GitHub Actions will run `npm-publish` from that release tag.
129
+
130
+ Manual fallback:
106
131
 
107
132
  1. Open GitHub Actions.
108
133
  2. Select the `npm-publish` workflow.
@@ -110,7 +135,12 @@ Then publish from GitHub:
110
135
  4. Enter `jscpd-rs` for `package_name`.
111
136
  5. Run the workflow.
112
137
 
113
- If npm does not allow Trusted Publishing configuration before the first package
114
- version exists, publish the first version with either interactive 2FA or a
115
- short-lived granular token with bypass 2FA enabled, then immediately configure
116
- Trusted Publishing for future releases.
138
+ If npm does not allow Trusted Publishing configuration before a platform package
139
+ version exists, add a short-lived `NPM_TOKEN` GitHub secret for the first
140
+ platform-package bootstrap, then immediately configure Trusted Publishing and
141
+ revoke the token.
142
+
143
+ After Trusted Publishing is verified, revoke temporary npm tokens and consider
144
+ setting the npm package publishing access to disallow traditional tokens. The
145
+ full GitHub Release publishing flow, including crates.io publishing, is tracked
146
+ in `docs/release-checklist.md`.
@@ -0,0 +1,89 @@
1
+ # Prebuilt Binary Distribution
2
+
3
+ `jscpd-rs` is a native Rust CLI. Starting with `jscpd-rs@0.1.2`, npm
4
+ distribution uses a small main package plus platform-specific optional
5
+ packages. The original `0.1.0` npm package was source-build only.
6
+
7
+ The main `jscpd-rs` package keeps the public bin names: `jscpd-rs`, `jscpd`,
8
+ and `jscpd-server`.
9
+
10
+ ## Runtime Behavior
11
+
12
+ - The JS shim first looks for a matching prebuilt optional package.
13
+ - If a prebuilt package is installed, the shim runs its native binary directly.
14
+ - If no prebuilt package exists for the platform, the existing source-build
15
+ path remains the fallback.
16
+ - `JSCPD_RS_FORCE_BUILD=1` forces the fallback build path.
17
+ - `JSCPD_RS_SKIP_POSTINSTALL=1` skips install-time builds for package tests and
18
+ advanced users who provide binaries another way.
19
+
20
+ Avoid default postinstall downloads from GitHub Releases. Optional platform
21
+ packages are more reproducible, work better with npm mirrors and lockfiles, and
22
+ avoid surprising network access during install.
23
+
24
+ ## Platform Packages
25
+
26
+ The target matrix is defined in `npm/prebuilt-targets.json`.
27
+
28
+ | Package | Rust target | Runner |
29
+ | --- | --- | --- |
30
+ | `jscpd-rs-linux-x64-gnu` | `x86_64-unknown-linux-gnu` | `ubuntu-24.04` |
31
+ | `jscpd-rs-linux-arm64-gnu` | `aarch64-unknown-linux-gnu` | `ubuntu-22.04-arm` |
32
+ | `jscpd-rs-darwin-x64` | `x86_64-apple-darwin` | `macos-15-intel` |
33
+ | `jscpd-rs-darwin-arm64` | `aarch64-apple-darwin` | `macos-15` |
34
+ | `jscpd-rs-win` | `x86_64-pc-windows-msvc` | `windows-2025` |
35
+
36
+ Consider Linux musl and Windows arm64 only after install data or user reports
37
+ show demand.
38
+
39
+ ## Package Checks
40
+
41
+ `scripts/npm-package-check.sh` verifies:
42
+
43
+ - the root npm version matches `Cargo.toml`;
44
+ - `optionalDependencies` exactly match `npm/prebuilt-targets.json`;
45
+ - every optional platform dependency uses the same version as the main package;
46
+ - the main npm package contains the runtime shim and target metadata;
47
+ - no Cargo/no prebuilt install fails with the expected Rust toolchain hint;
48
+ - source-build fallback works with optional dependencies omitted;
49
+ - a locally generated platform package works without Cargo;
50
+ - `npx --package <local-tarball> jscpd-rs --version` works.
51
+
52
+ ## Release Workflow
53
+
54
+ The GitHub Release workflow in `.github/workflows/npm-publish.yml` publishes in
55
+ this order:
56
+
57
+ 1. Verify that the GitHub Release tag matches `package.json`.
58
+ 2. Build platform packages in a native runner matrix.
59
+ 3. Smoke-test `jscpd --version` and `jscpd-server --version` for each package.
60
+ 4. Publish the platform packages.
61
+ 5. Run `scripts/npm-package-check.sh`.
62
+ 6. Publish the main `jscpd-rs` package.
63
+
64
+ The workflow publishes with npm provenance enabled. It is designed for Trusted
65
+ Publishing, but `NPM_TOKEN` can be kept as a temporary bootstrap fallback for
66
+ new platform packages if npm does not allow Trusted Publisher setup before the
67
+ first version exists.
68
+
69
+ ## npm Setup
70
+
71
+ Configure Trusted Publishing for each npm package with:
72
+
73
+ - Organization or user: `vv-bogdanov`
74
+ - Repository: `jscpd-rs`
75
+ - Workflow filename: `npm-publish.yml`
76
+ - Environment name: leave empty
77
+ - Allowed actions: `npm publish`
78
+
79
+ Packages:
80
+
81
+ - `jscpd-rs`
82
+ - `jscpd-rs-linux-x64-gnu`
83
+ - `jscpd-rs-linux-arm64-gnu`
84
+ - `jscpd-rs-darwin-x64`
85
+ - `jscpd-rs-darwin-arm64`
86
+ - `jscpd-rs-win`
87
+
88
+ If a temporary npm token is used for the first platform-package bootstrap,
89
+ revoke it after Trusted Publishing succeeds.
@@ -178,12 +178,58 @@ two explicit commands above are a manual smoke equivalent for post-tag checks.
178
178
  Track these after the first release candidate:
179
179
 
180
180
  - Reduce noisy extra Rust findings where they are user-visible false positives.
181
+ - Verify the first npm prebuilt platform-package publication before broad npm
182
+ promotion so Node users can install without Cargo; see the
183
+ [prebuilt binary distribution plan](prebuilt-binaries.md).
181
184
  - Add native persistent store/cache only if release-scale benchmark data needs
182
185
  it.
183
186
  - Tighten MCP Streamable HTTP SDK edge cases if real MCP clients require them.
184
187
  - Promote long-tail tokenizers only from concrete missed-coverage evidence.
185
188
  - File upstream bug reports from `docs/upstream-issue-drafts.md`.
186
189
 
190
+ ## Automated GitHub Release Publishing
191
+
192
+ After the first manual Cargo/npm bootstrap, future releases should use GitHub
193
+ Release publication as the single publish trigger.
194
+
195
+ Configured workflows:
196
+
197
+ - `.github/workflows/crates-publish.yml`
198
+ - `.github/workflows/npm-publish.yml`
199
+
200
+ Both workflows run on `release.published` for non-draft, non-prerelease GitHub
201
+ Releases. Both check out the release tag and verify that `vX.Y.Z` matches the
202
+ package version before publishing. `crates-publish` publishes the Rust crate to
203
+ crates.io; docs.rs builds documentation automatically after crates.io accepts
204
+ the crate. `npm-publish` builds and publishes prebuilt npm platform packages
205
+ before publishing the main npm package.
206
+
207
+ Release flow for future versions:
208
+
209
+ 1. Update `Cargo.toml` and `package.json` to the same version.
210
+ 2. Update `package.json#optionalDependencies` to the same version for every
211
+ platform package.
212
+ 3. Update `CHANGELOG.md`, README benchmark numbers, and release docs.
213
+ 4. Run the release gates, including `scripts/package-check.sh`.
214
+ 5. Commit and push `main`.
215
+ 6. Create and publish a GitHub Release with tag `vX.Y.Z`.
216
+ 7. Confirm `crates-publish` completed successfully.
217
+ 8. Confirm `npm-publish` built and published all platform packages before the
218
+ main `jscpd-rs` package.
219
+ 9. Check crates.io, docs.rs, and npm package pages.
220
+
221
+ Trusted Publishing setup:
222
+
223
+ - crates.io: configure a trusted publisher for repository
224
+ `vv-bogdanov/jscpd-rs` and workflow `crates-publish.yml`.
225
+ - npm: configure a trusted publisher for repository `vv-bogdanov/jscpd-rs` and
226
+ workflow `npm-publish.yml` on `jscpd-rs` and every platform package listed in
227
+ `docs/prebuilt-binaries.md`.
228
+
229
+ Use no GitHub environment name unless the workflow is updated to declare one.
230
+ After Trusted Publishing works, revoke temporary npm/crates tokens and keep
231
+ token-based publishing as an emergency fallback only.
232
+
187
233
  ## CI Speed Plan
188
234
 
189
235
  Keep this as part of the release plan:
@@ -1,6 +1,6 @@
1
1
  # Release Readiness
2
2
 
3
- Last updated: 2026-05-31.
3
+ Last updated: 2026-06-01.
4
4
 
5
5
  This is the working component checklist for the first release. The authoritative
6
6
  policy decisions are still in `docs/release-decisions.md`; this file tracks the
@@ -36,7 +36,7 @@ current implementation status.
36
36
  | Terminal cosmetics | practical parity | Important messages are gated; exact wrapping/order remains lower priority. |
37
37
  | Upstream JavaScript API parity | follow-up | Native Rust helpers cover the practical app/tokenizer/detector/statistics/store concepts, including an embeddable argv runner and tokenizer map generation; exact JS package export shape is not implemented in the Rust crate. See `docs/api-parity.md`. |
38
38
  | Server snippet matching | optimized baseline | Native `/api/check` and MCP `check_duplication` are functional and reuse project token maps from the last scan; add a dedicated window index only if real server benchmarks require it. |
39
- | Npm prebuilt binaries | follow-up | The first npm package is source-build: install/postinstall compiles with Cargo. Add platform-specific prebuilt packages before broad npm promotion if install speed or Rust toolchain requirements become a blocker. |
39
+ | Npm prebuilt binaries | ready for 0.1.2 | Platform-specific optional package metadata, runtime prebuilt resolution, prebuilt/fallback package checks, and GitHub Release publishing automation are wired for `jscpd-rs@0.1.2+`. See the [prebuilt binary distribution plan](prebuilt-binaries.md). |
40
40
  | Latest full publication gate | ready | `scripts/prepublish-check.sh` passed locally on code commit `8c3da0e`, including `scripts/release-candidate.sh`, package/install verification, crate/tag availability checks, npm package/name/npx verification, and `cargo publish --dry-run --locked`. GitHub Actions default `release-gate` passed on code commit `8c3da0e` in run `26710762680`. After benchmark documentation updates, `RUN_RELEASE_CANDIDATE=0 scripts/prepublish-check.sh` is the package/dry-run refresh gate for the exact package contents being tagged. |
41
41
 
42
42
  ## Post-MVP
@@ -1,15 +1,22 @@
1
1
  # jscpd-rs User Guide
2
2
 
3
- `jscpd-rs` is a native Rust implementation of the common `jscpd` workflows:
4
- scan source trees, detect copy-paste fragments, write reports, fail CI on
5
- thresholds, and serve snippet checks over HTTP/MCP.
3
+ `jscpd-rs` is a 50x+ faster duplicate-code detector for local development,
4
+ CI/CD, and code quality gates. It scans source trees, detects copy-paste
5
+ fragments across files, writes console, JSON, SARIF, HTML, XML, CSV, Markdown,
6
+ badge, and Xcode reports, fails CI on duplication thresholds, and serves
7
+ snippet checks over HTTP/MCP.
8
+
9
+ It implements the common upstream `jscpd` CLI workflow with native Rust
10
+ performance: upstream-style flags, `.jscpd.json` and `package.json#jscpd`
11
+ configuration, Git blame, report generation, exit-code behavior, and a native
12
+ server.
6
13
 
7
14
  This guide is intentionally user-facing. Release policy, benchmark evidence,
8
15
  and compatibility details live in the release docs linked from the README.
9
16
 
10
17
  ## Installation
11
18
 
12
- Install the Rust binaries from crates.io after publication:
19
+ Install the Rust binaries from crates.io:
13
20
 
14
21
  ```bash
15
22
  cargo install jscpd-rs --locked
@@ -22,9 +29,10 @@ npm install -g jscpd-rs
22
29
  npx jscpd-rs --version
23
30
  ```
24
31
 
25
- The first npm package builds the native binaries from source during
26
- `postinstall`, so a Rust toolchain must be available. Prebuilt npm platform
27
- packages are planned after the first release.
32
+ `jscpd-rs@0.1.2+` installs prebuilt Linux, macOS, and Windows binaries where
33
+ available and falls back to building from source with Cargo for unsupported
34
+ platforms. The original `0.1.0` npm package was source-build only. See the
35
+ [prebuilt binary distribution plan](prebuilt-binaries.md).
28
36
 
29
37
  Install from a checkout:
30
38
 
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const targets = require("../prebuilt-targets.json");
7
+
8
+ function packageRoot() {
9
+ return path.resolve(__dirname, "..", "..");
10
+ }
11
+
12
+ function exeName(name, platform = process.platform) {
13
+ return platform === "win32" ? `${name}.exe` : name;
14
+ }
15
+
16
+ function sourceBinaryPath(name) {
17
+ const targetDir =
18
+ process.env.CARGO_TARGET_DIR || path.join(packageRoot(), "target");
19
+ return path.join(targetDir, "release", exeName(name));
20
+ }
21
+
22
+ function detectLinuxLibc() {
23
+ if (process.platform !== "linux") {
24
+ return undefined;
25
+ }
26
+
27
+ const report =
28
+ process.report && typeof process.report.getReport === "function"
29
+ ? process.report.getReport()
30
+ : undefined;
31
+ if (report && report.header && report.header.glibcVersionRuntime) {
32
+ return "glibc";
33
+ }
34
+ return "musl";
35
+ }
36
+
37
+ function currentTargetKey() {
38
+ const libc = detectLinuxLibc();
39
+ for (const [key, target] of Object.entries(targets)) {
40
+ if (target.os !== process.platform || target.cpu !== process.arch) {
41
+ continue;
42
+ }
43
+ if (target.os === "linux" && target.libc !== libc) {
44
+ continue;
45
+ }
46
+ return key;
47
+ }
48
+ return undefined;
49
+ }
50
+
51
+ function currentTarget() {
52
+ const key = currentTargetKey();
53
+ return key ? { key, ...targets[key] } : undefined;
54
+ }
55
+
56
+ function resolvePrebuiltBinary(name) {
57
+ if (process.env.JSCPD_RS_FORCE_BUILD === "1") {
58
+ return undefined;
59
+ }
60
+
61
+ const target = currentTarget();
62
+ if (!target) {
63
+ return undefined;
64
+ }
65
+
66
+ let packageJson;
67
+ try {
68
+ packageJson = require.resolve(`${target.packageName}/package.json`, {
69
+ paths: [packageRoot()],
70
+ });
71
+ } catch {
72
+ return undefined;
73
+ }
74
+
75
+ const binary = path.join(path.dirname(packageJson), "bin", exeName(name, target.os));
76
+ return fs.existsSync(binary) ? binary : undefined;
77
+ }
78
+
79
+ function resolveBinary(name) {
80
+ return resolvePrebuiltBinary(name) || sourceBinaryPath(name);
81
+ }
82
+
83
+ module.exports = {
84
+ currentTarget,
85
+ currentTargetKey,
86
+ exeName,
87
+ packageRoot,
88
+ resolveBinary,
89
+ resolvePrebuiltBinary,
90
+ sourceBinaryPath,
91
+ targets,
92
+ };
@@ -3,20 +3,19 @@
3
3
  const fs = require("node:fs");
4
4
  const path = require("node:path");
5
5
  const { spawnSync } = require("node:child_process");
6
-
7
- function packageRoot() {
8
- return path.resolve(__dirname, "..", "..");
9
- }
10
-
11
- function binaryPath(name) {
12
- const exe = process.platform === "win32" ? `${name}.exe` : name;
13
- const targetDir =
14
- process.env.CARGO_TARGET_DIR || path.join(packageRoot(), "target");
15
- return path.join(targetDir, "release", exe);
16
- }
6
+ const {
7
+ packageRoot,
8
+ resolvePrebuiltBinary,
9
+ sourceBinaryPath,
10
+ } = require("./platform");
17
11
 
18
12
  function buildIfMissing(name) {
19
- const binary = binaryPath(name);
13
+ const prebuilt = resolvePrebuiltBinary(name);
14
+ if (prebuilt) {
15
+ return prebuilt;
16
+ }
17
+
18
+ const binary = sourceBinaryPath(name);
20
19
  if (fs.existsSync(binary)) {
21
20
  return binary;
22
21
  }
@@ -0,0 +1,44 @@
1
+ {
2
+ "linux-x64-gnu": {
3
+ "packageName": "jscpd-rs-linux-x64-gnu",
4
+ "description": "Prebuilt Linux x64 GNU binaries for jscpd-rs",
5
+ "os": "linux",
6
+ "cpu": "x64",
7
+ "libc": "glibc",
8
+ "rustTarget": "x86_64-unknown-linux-gnu",
9
+ "runner": "ubuntu-24.04"
10
+ },
11
+ "linux-arm64-gnu": {
12
+ "packageName": "jscpd-rs-linux-arm64-gnu",
13
+ "description": "Prebuilt Linux arm64 GNU binaries for jscpd-rs",
14
+ "os": "linux",
15
+ "cpu": "arm64",
16
+ "libc": "glibc",
17
+ "rustTarget": "aarch64-unknown-linux-gnu",
18
+ "runner": "ubuntu-22.04-arm"
19
+ },
20
+ "darwin-x64": {
21
+ "packageName": "jscpd-rs-darwin-x64",
22
+ "description": "Prebuilt macOS x64 binaries for jscpd-rs",
23
+ "os": "darwin",
24
+ "cpu": "x64",
25
+ "rustTarget": "x86_64-apple-darwin",
26
+ "runner": "macos-15-intel"
27
+ },
28
+ "darwin-arm64": {
29
+ "packageName": "jscpd-rs-darwin-arm64",
30
+ "description": "Prebuilt macOS arm64 binaries for jscpd-rs",
31
+ "os": "darwin",
32
+ "cpu": "arm64",
33
+ "rustTarget": "aarch64-apple-darwin",
34
+ "runner": "macos-15"
35
+ },
36
+ "win32-x64-msvc": {
37
+ "packageName": "jscpd-rs-win",
38
+ "description": "Prebuilt Windows x64 binaries for jscpd-rs",
39
+ "os": "win32",
40
+ "cpu": "x64",
41
+ "rustTarget": "x86_64-pc-windows-msvc",
42
+ "runner": "windows-2025"
43
+ }
44
+ }
@@ -1,24 +1,29 @@
1
1
  "use strict";
2
2
 
3
3
  const fs = require("node:fs");
4
- const path = require("node:path");
5
4
  const { spawnSync } = require("node:child_process");
5
+ const {
6
+ packageRoot,
7
+ resolvePrebuiltBinary,
8
+ sourceBinaryPath,
9
+ } = require("../lib/platform");
6
10
 
7
- const root = path.resolve(__dirname, "..", "..");
11
+ const root = packageRoot();
8
12
  const cargo = process.env.CARGO || "cargo";
9
- const targetDir = process.env.CARGO_TARGET_DIR || path.join(root, "target");
10
- const releaseDir = path.join(targetDir, "release");
11
- const exeSuffix = process.platform === "win32" ? ".exe" : "";
12
- const binaries = ["jscpd", "jscpd-server"].map((name) =>
13
- path.join(releaseDir, `${name}${exeSuffix}`),
14
- );
13
+ const binaryNames = ["jscpd", "jscpd-server"];
14
+ const prebuiltBinaries = binaryNames.map(resolvePrebuiltBinary);
15
+ const sourceBinaries = binaryNames.map(sourceBinaryPath);
15
16
 
16
17
  if (process.env.JSCPD_RS_SKIP_POSTINSTALL === "1") {
17
18
  console.log("jscpd-rs: skipping native build because JSCPD_RS_SKIP_POSTINSTALL=1");
18
19
  process.exit(0);
19
20
  }
20
21
 
21
- if (binaries.every((binary) => fs.existsSync(binary))) {
22
+ if (prebuiltBinaries.every(Boolean)) {
23
+ process.exit(0);
24
+ }
25
+
26
+ if (sourceBinaries.every((binary) => fs.existsSync(binary))) {
22
27
  process.exit(0);
23
28
  }
24
29
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jscpd-rs",
3
- "version": "0.1.0",
4
- "description": "Fast native Rust clone of jscpd for duplicate-code detection",
3
+ "version": "0.1.2",
4
+ "description": "50x+ faster duplicate-code detector for CI/CD; jscpd-compatible CLI, SARIF, JSON, HTML reports",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,11 +18,22 @@
18
18
  "duplication",
19
19
  "duplicate-code",
20
20
  "code-duplication",
21
+ "copy-paste-detection",
21
22
  "clone-detection",
23
+ "code-quality",
22
24
  "ci",
25
+ "cicd",
23
26
  "sarif",
27
+ "code-scanning",
24
28
  "cli"
25
29
  ],
30
+ "optionalDependencies": {
31
+ "jscpd-rs-darwin-arm64": "0.1.2",
32
+ "jscpd-rs-darwin-x64": "0.1.2",
33
+ "jscpd-rs-linux-arm64-gnu": "0.1.2",
34
+ "jscpd-rs-linux-x64-gnu": "0.1.2",
35
+ "jscpd-rs-win": "0.1.2"
36
+ },
26
37
  "bin": {
27
38
  "jscpd-rs": "npm/bin/jscpd-rs.js",
28
39
  "jscpd": "npm/bin/jscpd-rs.js",
package/src/lib.rs CHANGED
@@ -1,9 +1,21 @@
1
- #![doc(html_root_url = "https://docs.rs/jscpd-rs/0.1.0")]
1
+ #![doc(html_root_url = "https://docs.rs/jscpd-rs/0.1.2")]
2
2
 
3
- //! Native Rust API for `jscpd-rs`, a high-performance Rust clone of
4
- //! [`jscpd`](https://github.com/kucherenko/jscpd).
3
+ //! Native Rust API for `jscpd-rs`, a 50x+ faster duplicate-code detector for
4
+ //! local development and CI/CD.
5
5
  //!
6
- //! The crate exposes the same detector core used by the `jscpd` and
6
+ //! `jscpd-rs` scans a codebase, finds copy-paste fragments across files, writes
7
+ //! console, JSON, SARIF, HTML, XML, CSV, Markdown, badge, and Xcode reports,
8
+ //! and can fail a build when duplication crosses a configured threshold.
9
+ //!
10
+ //! It is a native Rust implementation of the common
11
+ //! [`jscpd`](https://github.com/kucherenko/jscpd) command-line workflow:
12
+ //! upstream-style CLI flags, `.jscpd.json` and `package.json#jscpd`
13
+ //! configuration, report formats, exit-code behavior, Git blame, and server
14
+ //! snippet checks. The current public benchmark suite records 50x+ speedups on
15
+ //! pinned React, Next.js, and Prometheus cases while using a coverage-first
16
+ //! compatibility gate against upstream `jscpd`.
17
+ //!
18
+ //! This crate exposes the same detector core used by the `jscpd` and
7
19
  //! `jscpd-server` binaries: option parsing, file discovery, tokenization,
8
20
  //! duplicate detection, statistics, and in-memory source checks.
9
21
  //!