mirror of
https://github.com/kucherenko/jscpd.git
synced 2026-09-19 08:11:03 +08:00
de66dff9a9
Single-file component support (Vue/Svelte/Astro), bundler alias resolution (vite.config, svelte.config, tsconfig paths) and monorepo workspace package-name resolution landed on master after basta 0.1.1 was tagged (#1062); the published crate and npm packages don't have them yet.
278 lines
10 KiB
JavaScript
278 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const packageJson = JSON.parse(
|
|
fs.readFileSync(path.join(root, "package.json"), "utf8"),
|
|
);
|
|
|
|
const npmVersion = packageJson.version;
|
|
|
|
const subCrates = [
|
|
{ dir: "crates/cpd-core", version: "0.1.15" },
|
|
{ dir: "crates/cpd-tokenizer", version: "0.1.17" },
|
|
{ dir: "crates/cpd-finder", version: "0.1.17" },
|
|
{ dir: "crates/cpd-reporter", version: "0.1.16" },
|
|
{ dir: "crates/basta", version: "0.1.2" },
|
|
];
|
|
|
|
const mainCrate = { dir: "crates/cpd", version: npmVersion };
|
|
|
|
// basta ships on its own cadence: its crate version is the single source of
|
|
// truth for the npm wrapper and its platform packages alike, so a jscpd
|
|
// release never drags basta's version along and vice versa.
|
|
const bastaVersion = subCrates.find((c) => c.dir === "crates/basta").version;
|
|
|
|
const subCrateVersions = {};
|
|
for (const { dir, version } of subCrates) {
|
|
subCrateVersions[dir.split("/")[1]] = version;
|
|
}
|
|
|
|
function updateCargoToml(filePath, updates) {
|
|
let content = fs.readFileSync(filePath, "utf8");
|
|
let changed = false;
|
|
|
|
for (const [pattern, replacement] of updates) {
|
|
const newContent = content.replace(pattern, replacement);
|
|
if (newContent !== content) {
|
|
content = newContent;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
fs.writeFileSync(filePath, content);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
// Update sub-crate versions and their dependency references
|
|
for (const { dir, version } of subCrates) {
|
|
const filePath = path.join(root, dir, "Cargo.toml");
|
|
const crateName = dir.split("/")[1];
|
|
const updates = [
|
|
[/^version = ".*"$/m, `version = "${version}"`],
|
|
];
|
|
|
|
// Update dependency versions for other sub-crates this crate depends on
|
|
for (const [depName, depVersion] of Object.entries(subCrateVersions)) {
|
|
if (crateName !== depName) {
|
|
updates.push([
|
|
new RegExp(`^(${depName} = \\{ version = )"([^"]+)"(, path = "([^"]+)"\\s*)\\}`, "m"),
|
|
`$1"${depVersion}"$3}`,
|
|
]);
|
|
}
|
|
}
|
|
|
|
const changed = updateCargoToml(filePath, updates);
|
|
console.log(`${changed ? "Updated" : "No change"} ${dir}/Cargo.toml version to ${version}`);
|
|
}
|
|
|
|
// Update main crate (jscpd) version and its dependency references
|
|
{
|
|
const filePath = path.join(root, mainCrate.dir, "Cargo.toml");
|
|
const updates = [
|
|
[/^version = ".*"$/m, `version = "${mainCrate.version}"`],
|
|
];
|
|
|
|
for (const [depName, depVersion] of Object.entries(subCrateVersions)) {
|
|
updates.push([
|
|
new RegExp(`^(${depName} = \\{ version = )"([^"]+)"(, path = "([^"]+)"\\s*)\\}`, "m"),
|
|
`$1"${depVersion}"$3}`,
|
|
]);
|
|
}
|
|
|
|
const changed = updateCargoToml(filePath, updates);
|
|
console.log(`${changed ? "Updated" : "No change"} ${mainCrate.dir}/Cargo.toml version to ${mainCrate.version}`);
|
|
}
|
|
|
|
console.log(`Version sync complete: npm=${npmVersion}, sub-crates=${JSON.stringify(subCrateVersions)}`);
|
|
|
|
// Sync cpd package optionalDependencies (platform binary packages)
|
|
{
|
|
const cpdPkgPath = path.join(root, "package.json");
|
|
const cpdPkg = JSON.parse(fs.readFileSync(cpdPkgPath, "utf8"));
|
|
let changed = false;
|
|
for (const [dep, version] of Object.entries(cpdPkg.optionalDependencies || {})) {
|
|
if (version !== npmVersion) {
|
|
cpdPkg.optionalDependencies[dep] = npmVersion;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) {
|
|
fs.writeFileSync(cpdPkgPath, `${JSON.stringify(cpdPkg, null, 2)}\n`);
|
|
console.log(`Updated package.json optionalDependencies to ${npmVersion}`);
|
|
} else {
|
|
console.log(`No change package.json optionalDependencies (${npmVersion})`);
|
|
}
|
|
}
|
|
|
|
// Sync jscpd wrapper package version and its platform pins.
|
|
//
|
|
// Version and optionalDependencies are updated independently on purpose. An
|
|
// earlier revision skipped the whole block when the version already matched,
|
|
// which meant anything that set `version` before this script ran (a manual
|
|
// edit, a partial release) left the platform pins stale — and they ship that
|
|
// way, so the wrapper resolves an older engine than it claims to be. That is
|
|
// how jscpd@5.1.0 went out pinned to the 5.0.16 binaries.
|
|
{
|
|
const jscpdPkgPath = path.join(root, "jscpd", "package.json");
|
|
const jscpdPkg = JSON.parse(fs.readFileSync(jscpdPkgPath, "utf8"));
|
|
let changed = false;
|
|
|
|
if (jscpdPkg.version !== npmVersion) {
|
|
jscpdPkg.version = npmVersion;
|
|
changed = true;
|
|
}
|
|
|
|
for (const [dep, version] of Object.entries(jscpdPkg.optionalDependencies || {})) {
|
|
if (version !== npmVersion) {
|
|
jscpdPkg.optionalDependencies[dep] = npmVersion;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
fs.writeFileSync(jscpdPkgPath, `${JSON.stringify(jscpdPkg, null, 2)}\n`);
|
|
console.log(`Updated jscpd/package.json to ${npmVersion}`);
|
|
} else {
|
|
console.log(`No change jscpd/package.json (${npmVersion})`);
|
|
}
|
|
}
|
|
|
|
// Sync the basta wrapper package version and its platform pins, against the
|
|
// basta crate version rather than the jscpd release version. Version and pins
|
|
// are set independently for the same reason the jscpd block does it: anything
|
|
// that sets one without the other ships a wrapper resolving the wrong engine.
|
|
{
|
|
const bastaPkgPath = path.join(root, "basta", "package.json");
|
|
const bastaPkg = JSON.parse(fs.readFileSync(bastaPkgPath, "utf8"));
|
|
let changed = false;
|
|
|
|
if (bastaPkg.version !== bastaVersion) {
|
|
bastaPkg.version = bastaVersion;
|
|
changed = true;
|
|
}
|
|
|
|
for (const [dep, version] of Object.entries(bastaPkg.optionalDependencies || {})) {
|
|
if (version !== bastaVersion) {
|
|
bastaPkg.optionalDependencies[dep] = bastaVersion;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
fs.writeFileSync(bastaPkgPath, `${JSON.stringify(bastaPkg, null, 2)}\n`);
|
|
console.log(`Updated basta/package.json to ${bastaVersion}`);
|
|
} else {
|
|
console.log(`No change basta/package.json (${bastaVersion})`);
|
|
}
|
|
}
|
|
|
|
// PyPI needs PEP 440 versions: 5.3.0-beta.1 -> 5.3.0b1 (alpha/beta/rc only).
|
|
// Mirrors `pep440()` in build-pypi-wheels.py; keep the two in step. Anything
|
|
// else throws rather than guessing, since a wrong mapping would pin the
|
|
// pre-commit hook to a version that never reaches PyPI.
|
|
function toPep440(version) {
|
|
const m = /^(\d+\.\d+\.\d+)(?:-(alpha|beta|rc)\.?(\d+))?$/.exec(version);
|
|
if (!m) throw new Error(`cannot map version ${version} to PEP 440`);
|
|
const [, base, kind, num] = m;
|
|
return kind ? `${base}${{ alpha: "a", beta: "b", rc: "rc" }[kind]}${num}` : base;
|
|
}
|
|
const pypiVersion = toPep440(npmVersion);
|
|
|
|
// Sync CITATION.cff: `version` follows the engine version, and `date-released`
|
|
// is set to today's UTC date whenever the version changes, so a release commit
|
|
// carries the right citation metadata without a manual edit.
|
|
{
|
|
const citationPath = path.join(root, "..", "CITATION.cff");
|
|
const citation = fs.readFileSync(citationPath, "utf8");
|
|
const current = /^version: (.*)$/m.exec(citation)?.[1];
|
|
if (current !== npmVersion) {
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
const updated = citation
|
|
.replace(/^version: .*$/m, `version: ${npmVersion}`)
|
|
.replace(/^date-released: .*$/m, `date-released: ${today}`);
|
|
fs.writeFileSync(citationPath, updated);
|
|
console.log(`Updated ../CITATION.cff to ${npmVersion} (released ${today})`);
|
|
} else {
|
|
console.log(`No change ../CITATION.cff (${npmVersion})`);
|
|
}
|
|
}
|
|
|
|
// Sync the repository-root pyproject.toml. It is a private project whose only
|
|
// job is to make `pre-commit` (language: python, see .pre-commit-hooks.yaml)
|
|
// install the `jscpd` binary from PyPI when a project points its hook at this
|
|
// repo, so its `version` and its exact `jscpd` pin follow the engine version.
|
|
{
|
|
const pyprojectPath = path.join(root, "..", "pyproject.toml");
|
|
const changed = updateCargoToml(pyprojectPath, [
|
|
[/^version = ".*"$/m, `version = "${pypiVersion}"`],
|
|
[/^dependencies = \["jscpd==[^"]*"\]$/m, `dependencies = ["jscpd==${pypiVersion}"]`],
|
|
]);
|
|
console.log(`${changed ? "Updated" : "No change"} ../pyproject.toml to ${pypiVersion}`);
|
|
}
|
|
|
|
// Fail loudly rather than shipping a wrapper that resolves the wrong engine.
|
|
{
|
|
const problems = [];
|
|
for (const rel of ["package.json", "jscpd/package.json"]) {
|
|
const pkg = JSON.parse(fs.readFileSync(path.join(root, rel), "utf8"));
|
|
if (pkg.version !== npmVersion) {
|
|
problems.push(`${rel}: version is ${pkg.version}, expected ${npmVersion}`);
|
|
}
|
|
for (const [dep, version] of Object.entries(pkg.optionalDependencies || {})) {
|
|
if (version !== npmVersion) {
|
|
problems.push(`${rel}: ${dep} pinned to ${version}, expected ${npmVersion}`);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
const citation = fs.readFileSync(path.join(root, "..", "CITATION.cff"), "utf8");
|
|
const version = /^version: (.*)$/m.exec(citation)?.[1];
|
|
if (version !== npmVersion) {
|
|
problems.push(`../CITATION.cff: version is ${version}, expected ${npmVersion}`);
|
|
}
|
|
}
|
|
{
|
|
const pyproject = fs.readFileSync(path.join(root, "..", "pyproject.toml"), "utf8");
|
|
const version = /^version = "([^"]*)"$/m.exec(pyproject)?.[1];
|
|
if (version !== pypiVersion) {
|
|
problems.push(`../pyproject.toml: version is ${version}, expected ${pypiVersion}`);
|
|
}
|
|
const pin = /^dependencies = \["jscpd==([^"]*)"\]$/m.exec(pyproject)?.[1];
|
|
if (pin !== pypiVersion) {
|
|
problems.push(`../pyproject.toml: jscpd pinned to ${pin}, expected ${pypiVersion}`);
|
|
}
|
|
}
|
|
{
|
|
const pkg = JSON.parse(
|
|
fs.readFileSync(path.join(root, "basta", "package.json"), "utf8"),
|
|
);
|
|
if (pkg.version !== bastaVersion) {
|
|
problems.push(`basta/package.json: version is ${pkg.version}, expected ${bastaVersion}`);
|
|
}
|
|
for (const [dep, version] of Object.entries(pkg.optionalDependencies || {})) {
|
|
if (version !== bastaVersion) {
|
|
problems.push(`basta/package.json: ${dep} pinned to ${version}, expected ${bastaVersion}`);
|
|
}
|
|
}
|
|
const targets = JSON.parse(
|
|
fs.readFileSync(path.join(root, "npm", "prebuilt-targets.json"), "utf8"),
|
|
);
|
|
for (const key of Object.keys(targets)) {
|
|
if (!(`basta-${key}` in (pkg.optionalDependencies || {}))) {
|
|
problems.push(`basta/package.json: no optional dependency for platform ${key}`);
|
|
}
|
|
}
|
|
}
|
|
if (problems.length > 0) {
|
|
console.error("Version sync verification FAILED:");
|
|
for (const p of problems) console.error(` - ${p}`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`Verified npm package versions and platform pins are all ${npmVersion}`);
|
|
console.log(`Verified basta wrapper and platform pins are all ${bastaVersion}`);
|
|
} |