basta now has a proper release: github.com/kucherenko/basta got its
own signed GitHub release with binaries, checksums, provenance and
sigstore signatures (mirroring what this repo's own release does),
built on the release surface its repo was already designed for (a
submodule pin to this workspace + its own v*-tag-triggered workflow)
but had never actually used.
That makes this repo's basta-release.yml redundant — it duplicated
that same publish logic and never created a release with artifacts
anywhere. Remove it; jscpd's own crates-publish.yml is unaffected,
since its basta-backfill logic (publishing basta as a dependency when
a fresh jscpd release needs it on crates.io first) is self-contained
and doesn't depend on basta-release.yml existing.
basta is a workspace member but releases on its own basta-v* tag; a
version bump to it alone still made the crates_needs_publish loop
over crates/*/Cargo.toml return true, which made create-rust-github-
release try to recreate the still-current jscpd release and fail with
"Release.tag_name already exists" (seen pushing the basta 0.1.2 bump
to master before tagging it).
Track a narrower cpd_needs_publish signal (true only when the jscpd
crate itself is unpublished) and gate the GitHub release on that
instead of the broad crates_needs_publish, which stays as-is for
gating the crates.io publish job — that one is supposed to backfill
basta too.
Closes#1037.
Adds rust/scripts/build-pypi-wheels.py, which repacks the eight
jscpd-<platform>.tar.gz release tarballs into platform wheels: the same
binary under <pkg>.data/scripts/ as both `jscpd` and `cpd`, one wheel
per target, stdlib only, no maturin and no recompilation. glibc wheels
take their manylinux floor from the GLIBC_x.y symbols in the binary
(manylinux_2_34 today), musl builds are static so they tag as
musllinux_1_2, macOS and Windows use the Rust minimums. Semver
pre-releases map to PEP 440 (5.3.0-beta.1 -> 5.3.0b1); other labels
fail rather than guess.
release.yml gains a rust-publish-pypi job after the npm publish: it
takes the binary artifacts from this run, refills any missing target
from the published npm platform package (resumed releases, or a version
already on npm), builds the wheels, pip-installs the matching one and
checks `jscpd --version` and `cpd --version`, then uploads with PyPI
trusted publishing (environment `pypi`, no token, skip-existing). The
detect job treats a version missing from PyPI as pending, so the wheels
for the current release publish on the next push to master without a
new release commit. The GitHub release lists the PyPI package and waits
on the upload the same way it waits on crates.io and npm.
Docs: PyPI in the README install table and packages table, rust/README
and docs/rust.md install sections, and a `language: python` pre-commit
option in docs/ci-and-hooks.md so the hook no longer needs Node.js.
Claude-Session: https://claude.ai/code/session_01WztdFKZAwW9b51iJe9zhtV
Everything belonging to the v4 TypeScript engine moves to the master-v4
branch: apps/, packages/, build-utils/, changesets, pnpm workspace, turbo,
the Node.js CI workflow and the ts-* release jobs. What remains is the
Rust workspace under rust/, the npm wrapper and platform packages, the
GitHub Action, Docker image, flake, skills, benchmark and fixtures/.
- rust.yml gains a smoke job that runs the release binary against
fixtures/ (json, sarif, html) and asserts clones and reporters
- release.yml keeps only the Rust jobs; pnpm removed from every workflow;
audit.yml keeps cargo audit + cargo deny; dependabot drops the root npm entry
- root package.json becomes a thin private package depending on the exact
jscpd@5 version so the pre-commit hook keeps installing the binary;
sync-version.mjs keeps it in step
- .pre-commit-hooks.yaml used v4-only flags that the v5 CLI rejects; fixed
- FORMATS.md is now generated from the Rust tokenizer (224 formats)
- README, docs, CONTRIBUTING, SECURITY, package and crate descriptions,
benchmark and skills rewritten for v5 on its own terms: no v5-vs-v4 speed
multipliers anywhere, a single "Looking for v4?" pointer to master-v4
Rust / crates.io
- report the invoked binary name in --version/--help (jscpd vs cpd)
- add repository, documentation, keywords, categories to all crates
- exclude tests/ from the jscpd crate package; add cargo-binstall metadata
- expand the crates.io README and render it on docs.rs
npm
- add funding field to jscpd, cpd and jscpd@4 packages
- add jscpd-linux-arm64-musl platform package and clearer unsupported-platform error
- enable provenance for the v4 changeset publish
Pipelines
- rust.yml: windows-latest in the matrix, drop dead schedule guard
- release.yml: cosign-sign release assets, floating v5 tag, LICENSE in tarballs,
invoke the new docker.yml (GHCR multi-arch image) after each release
- audit.yml: nightly cargo audit, cargo deny and pnpm audit
- Dockerfile: multi-stage image from release binaries (distroless), mcp target for Glama
Docs / community
- README: shields badges, single primary install per OS, GitHub Action snippet,
link to jscpd.dev, v5.1 features, 15 reporters, 224 formats, 8 platforms
- docs: @v5 action tag, Docker usage, baseline section, current crate versions
- YAML issue forms with engine selector, config.yml, CODEOWNERS
- remove stale .github/CONTRIBUTING.md stub, custom.md, TODO.md; untrack rust/jscpd-report.json
- examples: mark as v4 API, add a Rust cpd-finder example
The release-notes package check ran a single 'npm view' per package
immediately after publishing. npm reads are eventually consistent, so a
package published seconds earlier can still be invisible — v5.1.0 shipped
with a stale 'Not Yet Published: jscpd@5.1.0 (published: 5.0.16)' section
even though the publish had succeeded.
Re-check only the packages still missing, against one shared deadline
(5 min) so a genuinely unpublished package can't stretch the wait, and
report the results in stable package order. Same fix in the TypeScript
release job, which had the identical single-shot check.
Pinning rust-toolchain.toml to 1.97.1 made rustup auto-install a bare
1.97.1 toolchain in CI instead of using the dtolnay-installed 1.97 one
that carried clippy and rustfmt, failing the Clippy step. Declare the
components in the toolchain file so any auto-install includes them, and
align the workflow toolchain pins to 1.97.1 so CI installs the exact
toolchain cargo resolves.
Claude-Session: https://claude.ai/code/session_01VvWzWfEK6oV4HTe6EoGkff
Improves the OpenSSF Scorecard results (currently 7.2):
- Signed-Releases (0/10): release jobs now generate a sha256
checksums.txt and signed SLSA build provenance for the binary
tarballs via actions/attest-build-provenance (keyless, OIDC), and
upload the Sigstore bundle as a multiple.intoto.jsonl release asset.
Users can verify assets with
`gh attestation verify <asset> --repo kucherenko/jscpd`.
- Pinned-Dependencies (8/10): the five `curl | node -e` pipelines that
parse the crates.io API response are `curl | jq` now — jq consumes
the download as data, so nothing downloaded is piped into an
interpreter. The remaining npm warning is the post-publish smoke
test installing the just-published exact version from the live
registry, which is the point of that step.
- README: add Socket.dev and OpenSSF Scorecard badges.
release-rust.yml is currently uncalled (release.yml creates the
releases) but gets the same signing steps so it stays correct if
revived.
- pin every GitHub Action to a full commit SHA (with version comments);
add github-actions ecosystem to Dependabot to keep the pins fresh
- follow least privilege for workflow tokens: top-level contents:read
everywhere, checks/id-token/contents write grants scoped to the jobs
that need them (test-reporter, npm/crates trusted publishing,
gh release create)
- add SECURITY.md with private disclosure channels and supported versions
Also enabled outside the repo: master branch ruleset (no force-push or
deletion) and GitHub private vulnerability reporting.
- release.yml: mark the Rust v5 release --latest, the legacy TS v4 and
cpd releases --latest=false so v4 tags never steal the Latest badge
- rust/jscpd/README.md: use absolute GitHub URLs so links work on npmjs.com
- rust/jscpd/package.json: richer description and search keywords
The jscpd package ships from two release lines: 4.x (TypeScript) and
5.x (Rust wrapper). changeset publish always passes --tag latest (it
ignores publishConfig.tag), so the v4.3.0 maintenance release moved
'latest' from 5.0.15 back to 4.3.0 and 'npx jscpd' started installing
v4 again.
Add a guard step after the TS publish that re-points 'latest' at the
newest 5.x and keeps a 'v4' dist-tag on the newest 4.x, plus a
manually-dispatchable npm-dist-tags workflow with the same logic for
one-off repairs.
The security overrides lived in package.json's "pnpm.overrides", which
pnpm 10 silently stopped reading (settings moved to pnpm-workspace.yaml)
— so none of them applied and transitive-dependency alerts piled up.
- Move all overrides to pnpm-workspace.yaml and update them to the
patched minimums for every open alert: fast-uri >=3.1.5, hono
>=4.12.34, @hono/node-server >=1.19.15, js-yaml >=3.15.1/4.3.1,
brace-expansion >=1.1.16/5.0.9, nanoid >=3.3.17, postcss >=8.5.23,
ip-address >=10.3.1, body-parser >=2.3.0 (each annotated with its
GHSA id).
- Regenerate pnpm-lock.yaml; pnpm dedupe cleared a stale postcss peer
resolution. All affected packages now resolve to patched versions and
--frozen-lockfile passes.
- Drop the now-ignored pnpm block from package.json so dead config
can't mask itself as protection again.
- CI: install with --frozen-lockfile in nodejs.yml and release.yml so
lockfile corruption or drift fails loudly instead of silently
re-resolving dependencies from the registry (follow-up from #901).
Verified: pnpm build (10/10 tasks), pnpm test (5/5 suites), and
jscpd-server tests (43/43) all pass on the updated resolutions.
Renames the compiled binary from cpd(.exe) to jscpd(.exe) and the
platform-specific optional-dependency npm packages from cpd-* to
jscpd-* to avoid collision with an antivirus-flagged executable name.
Fixes#826
Fixes release note step failing with "cpd-core@0.1.5: command not found" because markdown backticks in the published/failed package lists were interpreted as command substitution when assigned inside double quotes.
- Rust releases now list crates.io and npm packages with publish status
- TypeScript releases now list all workspace npm packages with publish status
- Fix crates.io User-Agent from kucheren/jscpd to kucherenko/jscpd
actions/setup-node@v5 enables package-manager-cache by default, which tries
to save the pnpm store in post-cleanup. When pnpm install is never run
(e.g. idempotent publish skip), the store path doesn't exist, causing
'Path Validation Error: Path(s) specified for caching do not exist'.
Also added cache-on-failure: true to all Swatinem/rust-cache steps to
ensure Cargo cache is saved even on failed builds.
When a reusable workflow call is skipped, GitHub Actions returns an
empty string for needs.*.result instead of 'skipped'. This caused the
GitHub release job to be skipped even when crates were successfully
published, because the npm publish workflow was skipped (already
published) and its result was '' which matched neither 'success' nor
'skipped'.
Switch from whitelisting result values to blacklisting 'failure' and
'cancelled', which correctly handles empty string results from skipped
reusable workflows.
When rust-publish-npm is skipped (npm already published), GitHub skips
all dependent jobs by default. Add always() && !cancelled() so the if
condition is actually evaluated, allowing the release to be created.
The crates.io API returns 403 without a User-Agent header, causing curl -sf
to silently fail and return empty string. This made the skip check (comparing
max_version) always fail, so already-published crates were not skipped.
Also wrap cargo publish in if/then to continue past failures instead of
aborting the entire loop with set -e.
- Both rust_changed and ts_changed can be true simultaneously
- Replace fragile echo|node -p /dev/stdin pattern with node -e stdin reader
that handles pipefail and empty input gracefully
- Add actions/setup-node to rust-version and ts-version jobs (needed for
node CLI after merged pipeline)
- Rewrite release.yml as single entry point on push to master
- Detect changed paths (rust/** vs non-rust) to select branch
- Rust branch: clippy → test → build, then version bump detection
- TypeScript branch: lint → test → build, then version bump detection
- Publish crates.io in topological order via cargo metadata (no hardcoding)
- Publish npm packages via existing changeset flow
- Create GitHub releases with CHANGELOG-extracted notes (v5.x / v4.x tags)
- Remove push triggers from helper workflows (crates-publish, npm-rust-publish, release-rust)
- release-rust.yml now workflow_call only with CHANGELOG extraction
- Add rust/CHANGELOG.md for v5 release notes
- add cargo-nextest test steps to all Rust pipelines (rust.yml,
crates-publish.yml, npm-rust-publish.yml) with JUnit report upload
via dorny/test-reporter
- add Vitest JUnit reporter to Node.js pipelines (nodejs.yml,
release.yml) with test report display
- add rust/.config/nextest.toml with ci profile for JUnit output
- move platform-published check before build in npm-rust-publish.yml
so already-published targets skip the entire build chain
- make each cargo publish step idempotent (skip if version exists)
- add master to rust.yml push/pull_request branch triggers
- publish-packages: remove changeset version (run separately)