Files
apk eec83f853e refactor: fold accidental code clones in the Rust workspace
`jscpd rust/` reported 48 clones. This folds every one that was repetition
rather than a deliberate copy, leaving 34 across `rust/` and 14 in
`rust/crates`, all of the kinds the #1053 pass kept on purpose: fixture
corpora, `use` blocks, the TypeScript file and its JavaScript twin,
`impl Reporter` headers and the `ts_strip` visitor pair.

- basta's JSONC reader copied a string-skipping loop into both `strip_jsonc`
  and `drop_trailing_commas`. It is now `strip_comments` followed by a comma
  pass over `lang::outside_strings`, the quote-aware scanning the bundler
  config readers already share.
- The JavaScript and Python analyzer tests each built their own module
  index, import lookup, binding-reference lookup and member-read list. They
  live once in `test_scan` as `index_of`, `import`, `binding_reference` and
  `member_reads`.
- `test_scan::TempTree` writes a test's files and removes them when dropped,
  so a failing assertion no longer leaves its directory behind; the script
  and `extends` tests that repeated the setup use it.
- Four tsconfig `paths` tests with one shape are one table-driven test.
- The CLI integration tests share `duplicate_shapes`, `history_stdout` and
  `assert_only_the_real_file_was_scanned`.
- `scripts/publish-npm.sh` repeated a 43-line "publish the platform package,
  then the main packages under --force-main" tail in two branches, and the
  "publish unless already live, or show the dry run" block five times. They
  are `publish_platform_target` and `publish_package`. Run against the old
  script with the build, the registry check and `npm` stubbed, all eleven
  combinations of --target, --all, --force-main, --dry-run, --provenance, a
  failed build and missing platforms print the same lines and exit the same.

Behaviour is unchanged: the fixtures corpus reports in every mode, the
summary, the console output and the dead-code demo are byte-identical.
2026-09-17 15:15:11 +02:00

328 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# publish-npm.sh — build, package, and publish all cpd npm packages
#
# Usage:
# scripts/publish-npm.sh [--dry-run] [--target <target>] [--all] [--provenance]
#
# Modes:
# --all Build and publish all 8 platform packages, then publish main
# --target <key> Build and publish a single platform package
# (default) Build and publish for the current platform only
#
# Targets: linux-x64-gnu, linux-arm64-gnu, linux-x64-musl, linux-arm64-musl,
# darwin-arm64, darwin-x64, windows-x64-msvc, windows-arm64-msvc
#
# With --all, builds that fail (e.g. missing cross-compiler) are skipped.
# Already-published platform packages are also skipped.
# The main cpd package is only published if all 8 are on npm.
#
# Environment:
# NPM_TOKEN — npm auth token (or already logged in via npm login)
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
RUST_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
DRY_RUN=""
TARGET_FLAG=""
ALL_TARGETS=""
PROVENANCE=""
FORCE_MAIN=""
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run)
DRY_RUN=1
shift
;;
--target)
TARGET_FLAG="$2"
shift 2
;;
--all)
ALL_TARGETS=1
shift
;;
--provenance)
PROVENANCE=1
shift
;;
--force-main)
FORCE_MAIN=1
shift
;;
--help|-h)
echo "Usage: scripts/publish-npm.sh [--dry-run] [--target <target>] [--all] [--provenance] [--force-main]"
echo ""
echo "Build and publish cpd npm packages."
echo ""
echo "Options:"
echo " --all Build and publish all 8 platform packages"
echo " --target <target> Build and publish a single platform package"
echo " --force-main Publish main cpd package even if some platform packages are missing"
echo " --dry-run Show what would be published without actually publishing"
echo " --provenance Add npm provenance (requires GitHub Actions OIDC)"
echo ""
echo "Available targets: linux-x64-gnu, linux-arm64-gnu, linux-x64-musl, linux-arm64-musl,"
echo " darwin-arm64, darwin-x64, windows-x64-msvc, windows-arm64-msvc"
echo ""
echo "Note: --all will skip targets that fail to build (missing cross-compiler)."
echo " Already-published packages are also skipped."
echo " By default the main cpd package requires all 8 platforms on npm."
echo " Use --force-main to publish with only available platform packages."
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
cd "$RUST_DIR"
VERSION=$(node -p "require('./package.json').version")
ALL_TARGET_KEYS="linux-x64-gnu linux-arm64-gnu linux-x64-musl linux-arm64-musl darwin-arm64 darwin-x64 windows-x64-msvc windows-arm64-msvc"
PROVENANCE_FLAG=""
if [ -n "$PROVENANCE" ]; then
PROVENANCE_FLAG="--provenance"
fi
log() {
echo ":: publish-npm :: $*"
}
npm_exists() {
local package="$1" ver="$2"
local status
status=$(curl -so /dev/null -w '%{http_code}' \
-H 'Accept: application/json' \
-H 'User-Agent: cpd-publish-script' \
"https://registry.npmjs.org/${package}/${ver}" 2>/dev/null)
[ "$status" = "200" ]
}
detect_current_target() {
node -e "
const { getPlatformKey } = require('./platform-map.js');
const key = getPlatformKey();
if (!key) { process.exit(1); }
process.stdout.write(key);
"
}
build_and_publish() {
local TARGET_KEY="$1"
local RUST_TARGET PACKAGE_NAME BINARY_NAME
RUST_TARGET="$(node -p "require('./npm/prebuilt-targets.json')['${TARGET_KEY}'].rustTarget")"
PACKAGE_NAME="$(node -p "require('./npm/prebuilt-targets.json')['${TARGET_KEY}'].packageName")"
BINARY_NAME="cpd"
if [ "$(node -p "require('./npm/prebuilt-targets.json')['${TARGET_KEY}'].os")" = "win32" ]; then
BINARY_NAME="cpd.exe"
fi
if npm_exists "$PACKAGE_NAME" "$VERSION"; then
log " $PACKAGE_NAME@$VERSION already published, skipping"
return 0
fi
log "Building $PACKAGE_NAME ($RUST_TARGET)"
if [ -z "$DRY_RUN" ]; then
if ! rustup target list --installed 2>/dev/null | grep -q "$RUST_TARGET"; then
log " Installing rust target $RUST_TARGET..."
rustup target add "$RUST_TARGET"
fi
if ! cargo build --release --locked --target "$RUST_TARGET" -p jscpd 2>&1; then
log " BUILD FAILED: $PACKAGE_NAME — skipping (missing cross-compiler?)"
return 1
fi
if [ ! -f "target/$RUST_TARGET/release/$BINARY_NAME" ]; then
log " BUILD FAILED: binary not found at target/$RUST_TARGET/release/$BINARY_NAME"
return 1
fi
else
log " [dry-run] Would run: cargo build --release --locked --target $RUST_TARGET -p jscpd"
fi
local PACKAGE_DIR=""
if [ -z "$DRY_RUN" ]; then
PACKAGE_DIR="$(node scripts/npm-prebuilt-package.mjs \
--target "$TARGET_KEY" \
--bin-dir "target/$RUST_TARGET/release" \
--out-dir "target/npm-prebuilt")"
if [ -z "$PACKAGE_DIR" ]; then
log " PACKAGE FAILED: npm-prebuilt-package.mjs returned empty path"
return 1
fi
log " Package created at $PACKAGE_DIR"
else
log " [dry-run] Would run: node scripts/npm-prebuilt-package.mjs --target $TARGET_KEY --bin-dir target/$RUST_TARGET/release --out-dir target/npm-prebuilt"
fi
log "Publishing $PACKAGE_NAME@$VERSION"
if [ -z "$DRY_RUN" ]; then
npm publish "$PACKAGE_DIR" --access public $PROVENANCE_FLAG
log " Published $PACKAGE_NAME@$VERSION"
else
log " [dry-run] Would run: npm publish $PACKAGE_DIR --access public $PROVENANCE_FLAG"
fi
return 0
}
# Publish one npm package unless this version is already live. An empty
# directory means the package in the current directory (the main cpd package).
publish_package() {
local name="$1" dir="$2"
if [ -n "$DRY_RUN" ]; then
log " [dry-run] Would run: npm publish ${dir:+$dir }--access public $PROVENANCE_FLAG"
elif npm_exists "$name" "$VERSION"; then
log " $name@$VERSION already published, skipping"
else
npm publish ${dir:+"$dir"} --access public $PROVENANCE_FLAG
log " Published $name@$VERSION"
fi
}
# Build and publish one platform package, then the main cpd and jscpd
# packages when --force-main asks for them.
publish_platform_target() {
local key="$1" label="$2"
PACKAGE_NAME="$(node -p "require('./npm/prebuilt-targets.json')['${key}'].packageName")"
log "$label: $PACKAGE_NAME@$VERSION"
if ! build_and_publish "$key"; then
log "FAILED: $PACKAGE_NAME"
exit 1
fi
echo ""
log "Done! $PACKAGE_NAME@$VERSION published."
if [ -n "$FORCE_MAIN" ]; then
echo ""
log "Publishing main cpd package (--force-main)..."
publish_package cpd ""
echo ""
log "Publishing jscpd@$VERSION (wrapper package)..."
JSCPD_DIR="$RUST_DIR/jscpd"
if [ -d "$JSCPD_DIR" ]; then
publish_package jscpd "$JSCPD_DIR"
else
log " WARNING: jscpd package directory not found at $JSCPD_DIR"
fi
else
echo ""
log "To publish all platforms: scripts/publish-npm.sh --all"
log "To publish main package now: scripts/publish-npm.sh --force-main"
log "Otherwise, all 8 platform packages must be live first."
fi
}
if [ -n "$ALL_TARGETS" ]; then
log "Publishing all platform packages for v$VERSION"
FAILED_TARGETS=""
SUCCEEDED_TARGETS=""
for TARGET_KEY in $ALL_TARGET_KEYS; do
PACKAGE_NAME="$(node -p "require('./npm/prebuilt-targets.json')['${TARGET_KEY}'].packageName")"
log "--- $PACKAGE_NAME ---"
if build_and_publish "$TARGET_KEY"; then
SUCCEEDED_TARGETS="$SUCCEEDED_TARGETS $PACKAGE_NAME"
else
FAILED_TARGETS="$FAILED_TARGETS $PACKAGE_NAME"
fi
done
echo ""
log "Checking all platform packages are live on npm..."
MISSING=0
OK=0
for TARGET_KEY in $ALL_TARGET_KEYS; do
PACKAGE_NAME="$(node -p "require('./npm/prebuilt-targets.json')['${TARGET_KEY}'].packageName")"
if npm_exists "$PACKAGE_NAME" "$VERSION"; then
log " $PACKAGE_NAME@$VERSION OK"
OK=$((OK + 1))
else
log " $PACKAGE_NAME@$VERSION MISSING"
MISSING=$((MISSING + 1))
fi
done
echo ""
if [ "$MISSING" -gt 0 ]; then
if [ -n "$FORCE_MAIN" ]; then
log "WARNING: $MISSING platform package(s) missing from npm"
log " Published:$SUCCEEDED_TARGETS"
if [ -n "$FAILED_TARGETS" ]; then
log " Failed builds:$FAILED_TARGETS"
fi
log " --force-main: publishing main cpd package anyway"
else
log "WARNING: $MISSING platform package(s) missing from npm (needed for main package)"
log " Published:$SUCCEEDED_TARGETS"
if [ -n "$FAILED_TARGETS" ]; then
log " Failed builds:$FAILED_TARGETS"
fi
log " Publish the missing packages manually with: scripts/publish-npm.sh --target <target>"
log " Or use CI: git push origin HEAD:release/rust"
log " Or use --force-main to publish with only available platforms"
log ""
log "Skipping main cpd package publish ($MISSING missing)."
exit 1
fi
else
log "All $OK platform packages verified."
fi
publish_package cpd ""
log "Done! cpd@$VERSION published with all platform packages."
echo ""
log "Publishing jscpd@$VERSION (wrapper package)..."
JSCPD_DIR="$RUST_DIR/jscpd"
if [ ! -d "$JSCPD_DIR" ]; then
log "ERROR: jscpd package directory not found at $JSCPD_DIR"
exit 1
fi
echo ""
log "Running fixture check with cpd before publishing jscpd..."
FIXTURES_DIR="$RUST_DIR/../fixtures"
if [ ! -d "$FIXTURES_DIR" ]; then
log "ERROR: fixtures directory not found at $FIXTURES_DIR"
exit 1
fi
CPD_BIN=$(which cpd 2>/dev/null || true)
if [ -n "$CPD_BIN" ]; then
log "Running: cpd $FIXTURES_DIR --silent --reporters json"
if $CPD_BIN "$FIXTURES_DIR" --silent --reporters json; then
log "Fixture check passed"
else
log "WARNING: cpd fixture check exited with non-zero (may be --exit-code)"
fi
else
log "WARNING: cpd not found in PATH, skipping fixture smoke test"
fi
publish_package jscpd "$JSCPD_DIR"
log "Done! jscpd@$VERSION published."
elif [ -n "$TARGET_FLAG" ]; then
publish_platform_target "$TARGET_FLAG" "Publishing single target"
else
TARGET_KEY="$(detect_current_target)"
if [ -z "$TARGET_KEY" ]; then
log "ERROR: Could not detect current platform"
exit 1
fi
publish_platform_target "$TARGET_KEY" "Publishing current platform"
fi