UNPKG

verz

Version:

[![NPM Version](https://img.shields.io/npm/v/verz)](https://www.npmjs.com/package/verz)

312 lines (303 loc) 11 kB
#!/usr/bin/env node var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts import { readFileSync as readFileSync2, writeFileSync } from "fs"; import { join as join2 } from "path"; import { Command } from "commander"; import semver from "semver"; // src/lib/logger.ts import { inspect } from "util"; var COLORS = { reset: "\x1B[0m", red: "\x1B[31m", yellow: "\x1B[33m", blue: "\x1B[34m", green: "\x1B[32m", magenta: "\x1B[35m", gray: "\x1B[90m", cyan: "\x1B[36m" }; var Logger = class { constructor(_level = "info") { this._level = _level; this.COLORS = COLORS; } debug(...args) { if (this.shouldLog("debug")) console.log( `${COLORS.gray}[DEBUG] ${COLORS.reset}`, ...args.map((arg) => typeof arg === "object" ? inspect(arg) : arg), COLORS.reset ); } info(...args) { if (this.shouldLog("info")) console.log( `${COLORS.blue}[INFO] ${COLORS.reset}`, ...args.map((arg) => typeof arg === "object" ? inspect(arg) : arg), COLORS.reset ); } warn(...args) { if (this.shouldLog("warn")) console.warn( `${COLORS.yellow}[WARN] ${COLORS.reset}`, ...args.map((arg) => typeof arg === "object" ? inspect(arg) : arg), COLORS.reset ); } error(...args) { if (this.shouldLog("error")) console.error( `${COLORS.red}[ERROR] ${COLORS.reset}`, ...args.map((arg) => typeof arg === "object" ? inspect(arg) : arg), COLORS.reset ); } level(level) { this._level = level; } shouldLog(level) { const levels = ["error", "warn", "info", "debug"]; return levels.indexOf(this._level) >= levels.indexOf(level); } }; var logger_default = new Logger(); // src/lib/exec.ts import { spawnSync } from "child_process"; // src/lib/config.ts import { join } from "path"; import { existsSync, readFileSync } from "fs"; // src/lib/utils.ts var deepMerge = (target, source) => { if (typeof source !== "object" || source === null) return source; const output = __spreadValues({}, target); for (const key in source) { if (source[key] === void 0 || source[key] === null) continue; if (typeof source[key] === "object" && !Array.isArray(source[key])) { output[key] = deepMerge(output[key] || {}, source[key]); } else { output[key] = source[key]; } } return output; }; // src/lib/config.ts var defaultConfig = { commit: { message: "chore: version %v" }, tag: { name: "%v" }, dryRun: false }; var Config = class { constructor() { this.config = defaultConfig; } fileLoad() { return __async(this, null, function* () { const configFiles = ["verz.config.json", "verz.config.js", "verz.config.mjs"]; for (const file of configFiles) { const configPath = join(process.cwd(), file); if (existsSync(configPath)) { logger_default.debug(`Loading config from ${file}`); try { if (file.endsWith(".json")) { return JSON.parse(readFileSync(configPath, "utf-8")); } else if (file.endsWith(".mjs")) { const module = yield import(configPath); return module.default; } else { return __require(configPath); } } catch (error) { logger_default.warn(`Failed to load config from ${file}:`, error); } } } return {}; }); } load() { return __async(this, arguments, function* (override = {}) { const fileConfig = yield this.fileLoad(); this.config = deepMerge(deepMerge(this.config, fileConfig), override); return this.config; }); } get() { return this.config; } }; var config_default = new Config(); // src/lib/exec.ts var exec = (command, args, options) => { var _a, _b; const config = config_default.get(); logger_default.debug(`Running command${logger_default.COLORS.gray}`, command, args); if (config.dryRun) return ""; const p = spawnSync(command, args.map(String), { stdio: "pipe" }); if (!(options == null ? void 0 : options.ignoreReturnCode)) { if (p.error) { throw p.error; } if (p.status !== 0) { throw new Error(`Command failed with exit code ${p.status}`); } } const stdout = (_b = (_a = p.stdout) == null ? void 0 : _a.toString()) != null ? _b : ""; if (stdout) { } return stdout; }; var exec_default = exec; // src/index.ts function main() { return __async(this, null, function* () { const program = new Command(); program.name("verz").description("Version bumping tool").option("--patch", "bump patch version").option("--minor", "bump minor version").option("--major", "bump major version").option("--prerelease [preid]", "bump to prerelease version (optionally specify preid: alpha, beta, rc, etc.)").option("--version <version>", "set exact version (e.g., 1.2.3)").option("--commit.message <message>", "custom commit message").option("-v, --verbose", "enable verbose logging").option("--dry-run", "dry run").addHelpText( "after", "\nAt least one of --patch, --minor, --major, --prerelease, or --version must be specified." ).action((options) => __async(null, null, function* () { var _a; if (options.verbose) { logger_default.level("debug"); } try { let type; let preid; let newVersion; const versionBumpOptions = [ options.patch ? "patch" : null, options.minor ? "minor" : null, options.major ? "major" : null, options.prerelease !== void 0 ? "prerelease" : null, options.version ? "version" : null ].filter(Boolean); if (versionBumpOptions.length === 0) throw new Error("You must specify one of: --patch, --minor, --major, --prerelease, or --version"); if (versionBumpOptions.length > 1) throw new Error( `Only one version bump option can be specified. Found: ${versionBumpOptions.join(", ")}` ); if (options.version) { if (!semver.valid(options.version)) throw new Error( `Invalid version specified: ${options.version}. Must be a valid semver version.` ); newVersion = options.version; } else if (options.prerelease !== void 0) { type = "prerelease"; preid = typeof options.prerelease === "string" ? options.prerelease : "rc"; } else if (options.major) { type = "major"; } else if (options.minor) { type = "minor"; } else if (options.patch) { type = "patch"; } const cliConfig = { commit: { message: options["commit.message"] }, dryRun: options["dryRun"] }; yield config_default.load(cliConfig); const config = config_default.get(); const packagePath = join2(process.cwd(), "package.json"); const packageContent = readFileSync2(packagePath, "utf-8"); const packageJson = JSON.parse(packageContent); logger_default.info(`Current version${logger_default.COLORS.magenta}`, packageJson.version); if (!newVersion) { if (!type) throw new Error("No version bump type specified"); const calculatedVersion = semver.inc(packageJson.version, type, false, preid || "rc"); if (!calculatedVersion) throw new Error(`Failed to bump version: ${packageJson.version}`); newVersion = calculatedVersion; } const packageJsonStatus = exec_default("git", ["status", "--porcelain", "package.json"], { ignoreReturnCode: true }); if (packageJsonStatus.length > 0) { throw new Error("package.json has uncommitted changes. Please commit or stash them first."); } const hasChanges = exec_default("git", ["diff", "HEAD"], { ignoreReturnCode: true }).length > 0; if (hasChanges) { exec_default("git", ["stash", "push", "--keep-index"]); } packageJson.version = newVersion; logger_default.debug(`Updating ${logger_default.COLORS.gray}package.json`); const match = packageContent.match(/^(?:( +)|\t+)/m); const indent = (_a = match == null ? void 0 : match[0]) != null ? _a : " "; if (!config.dryRun) writeFileSync(packagePath, JSON.stringify(packageJson, null, indent) + "\n"); try { exec_default("git", ["add", "package.json"]); const commitMessage = config.commit.message.replace("%v", newVersion); logger_default.info(`Committing changes with message${logger_default.COLORS.magenta}`, commitMessage); exec_default("git", ["commit", "-m", commitMessage]); const tagName = config.tag.name.replace("%v", newVersion); logger_default.info(`Creating tag${logger_default.COLORS.magenta}`, tagName); exec_default("git", ["tag", tagName]); } catch (e) { logger_default.error("Failed to commit and tag:", e); } finally { if (hasChanges) { exec_default("git", ["stash", "pop"]); } } logger_default.info(`Version bumped to${logger_default.COLORS.magenta}`, newVersion); } catch (error) { logger_default.error(error); process.exit(1); } })); program.parse(); }); } main();