// DrFed: A web-based platform for developing and debugging ActivityPub apps
// Copyright (C) 2026 DrFed team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// oxlint-disable no-console no-magic-numbers
import { readFile, readdir, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { setComposeImageTags } from "./images/compose-tags.mts";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const packagesDir = join(root, "packages");
const version = process.argv[2];
if (version == null) {
console.error("Usage: node scripts/bump-versions.mts ");
console.error("Example: node scripts/bump-versions.mts 1.2.0");
process.exit(1);
}
async function findPackageJsonPaths(): Promise {
const entries = await readdir(packagesDir, { withFileTypes: true });
const paths: string[] = [];
for (const entry of entries) {
if (!entry.isDirectory()) {
continue;
}
paths.push(join(packagesDir, entry.name, "package.json"));
}
return paths;
}
function isSemver(verStr: string): boolean {
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.test(
verStr,
);
}
if (!isSemver(version)) {
console.error(`Invalid semver version: ${version}`);
process.exit(1);
}
const packageJsonPaths = await findPackageJsonPaths();
if (packageJsonPaths.length === 0) {
console.error("No packages found under packages/.");
process.exit(1);
}
// First, so that a Compose example it cannot change leaves every file as is.
await setComposeImageTags(version);
console.log(`Compose examples: ghcr.io/fedify-dev/drfed:${version}`);
await Promise.all(
packageJsonPaths.map(async (path) => {
const content = await readFile(path, "utf8");
const data = JSON.parse(content) as { name: string; version: string };
const oldVersion = data.version;
data.version = version;
const updated = `${JSON.stringify(data, null, 2)}\n`;
await writeFile(path, updated, "utf8");
console.log(`${data.name}: ${oldVersion} -> ${version}`);
}),
);
const count = packageJsonPaths.length;
console.log(
`\nBumped ${count} package${count === 1 ? "" : "s"} to ${version}.`,
);