| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- #!/usr/bin/env bun
- /**
- * Version detection utilities for upstream merge automation
- */
- import { $ } from "bun"
- import { getUpstreamTags, getCommitMessage, getTagsForCommit } from "./git"
- export interface VersionInfo {
- version: string
- tag: string
- commit: string
- }
- /**
- * Parse version from a tag string (e.g., "v1.1.49" -> "1.1.49")
- * Only matches stable versions (not dev/preview tags like v0.0.0-202507310417)
- */
- export function parseVersion(tag: string, includePrerelease = false): string | null {
- // Match stable versions like v1.1.49 or 1.1.49
- const stableMatch = tag.match(/^v?(\d+\.\d+\.\d+)$/)
- if (stableMatch) return stableMatch[1] ?? null
- // Optionally match prerelease versions
- if (includePrerelease) {
- const prereleaseMatch = tag.match(/^v?(\d+\.\d+\.\d+-.+)$/)
- if (prereleaseMatch) return prereleaseMatch[1] ?? null
- }
- return null
- }
- /**
- * Compare two semver versions
- * Returns: -1 if a < b, 0 if a == b, 1 if a > b
- */
- export function compareVersions(a: string, b: string): number {
- const partsA = a.split(".").map((x) => parseInt(x, 10) || 0)
- const partsB = b.split(".").map((x) => parseInt(x, 10) || 0)
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
- const numA = partsA[i] || 0
- const numB = partsB[i] || 0
- if (numA < numB) return -1
- if (numA > numB) return 1
- }
- return 0
- }
- /**
- * Get the latest upstream version from tags
- */
- export async function getLatestUpstreamVersion(): Promise<VersionInfo | null> {
- const versions = await getAvailableUpstreamVersions()
- if (versions.length === 0) return null
- return versions[0] ?? null
- }
- /**
- * Get version info for a specific commit
- */
- export async function getVersionForCommit(commit: string): Promise<VersionInfo | null> {
- const tags = await getTagsForCommit(commit)
- for (const tag of tags) {
- const version = parseVersion(tag)
- if (version) {
- return { version, tag, commit }
- }
- }
- // Try to extract from commit message
- const message = await getCommitMessage(commit)
- const match = message.match(/v?(\d+\.\d+\.\d+)/)
- if (match && match[1]) {
- return {
- version: match[1],
- tag: `v${match[1]}`,
- commit,
- }
- }
- return null
- }
- /**
- * Get available upstream versions (sorted newest first)
- */
- export async function getAvailableUpstreamVersions(): Promise<VersionInfo[]> {
- // Get tags with their commits directly from ls-remote
- const result = await $`git ls-remote --tags upstream`.quiet().nothrow()
- if (result.exitCode !== 0) {
- throw new Error(`Failed to list upstream tags: ${result.stderr.toString()}`)
- }
- const output = result.stdout.toString()
- const versions: VersionInfo[] = []
- for (const line of output.trim().split("\n")) {
- // Match lines like: abc123... refs/tags/v1.1.49
- // Skip annotated tag references (those ending with ^{})
- const match = line.match(/^([a-f0-9]+)\s+refs\/tags\/([^\^]+)$/)
- if (!match) continue
- const commit = match[1]
- const tag = match[2]
- if (!commit || !tag) continue
- const version = parseVersion(tag)
- if (version) {
- versions.push({ version, tag, commit })
- }
- }
- // Sort by version descending
- versions.sort((a, b) => compareVersions(b.version, a.version))
- return versions
- }
- /**
- * Get current Kilo version from package.json
- */
- export async function getCurrentKiloVersion(): Promise<string> {
- // Resolve path relative to repo root (script is in script/upstream/)
- const path = new URL("../../../packages/opencode/package.json", import.meta.url).pathname
- const pkg = await Bun.file(path).json()
- return pkg.version
- }
|