Files
ComfyUI_frontend/scripts/cicd/resolve-comfyui-release.ts
Christian Byrne 77e453db36 [fix] Fall back to current minor when next minor branch doesn't exist (#7286)
## Summary
- When the next minor branch (e.g., `core/1.35`) doesn't exist yet, fall
back to current minor (`core/1.34`) for patch releases instead of
failing
- Fixes the weekly release workflow failure when ComfyUI is on version
1.33.x and `core/1.34` doesn't exist yet

## Test plan
- [x] Tested locally with version where next minor exists (1.33.5 →
finds core/1.34)
- [x] Tested fallback when next minor doesn't exist (1.34.7 → falls back
to core/1.34)
- [x] Tested error case when neither branch exists

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7286-fix-Fall-back-to-current-minor-when-next-minor-branch-doesn-t-exist-2c46d73d365081009762c732954e148d)
by [Unito](https://www.unito.io)
2025-12-09 02:45:56 -07:00

271 lines
7.1 KiB
TypeScript
Executable File

#!/usr/bin/env tsx
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'
interface ReleaseInfo {
current_version: string
target_minor: number
target_version: string
target_branch: string
needs_release: boolean
latest_patch_tag: string | null
branch_head_sha: string | null
tag_commit_sha: string | null
diff_url: string
release_pr_url: string | null
}
/**
* Execute a command and return stdout
*/
function exec(command: string, cwd?: string): string {
try {
return execSync(command, {
cwd,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe']
}).trim()
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
const cwdInfo = cwd ? ` in directory: ${cwd}` : ''
console.error(
`Command failed: ${command}${cwdInfo}\nError: ${errorMessage}`
)
return ''
}
}
/**
* Parse version from requirements.txt
* Handles formats: comfyui-frontend-package==1.2.3, comfyui-frontend-package>=1.2.3, etc.
*/
function parseRequirementsVersion(requirementsPath: string): string | null {
if (!fs.existsSync(requirementsPath)) {
console.error(`Requirements file not found: ${requirementsPath}`)
return null
}
const content = fs.readFileSync(requirementsPath, 'utf-8')
const match = content.match(
/comfyui-frontend-package\s*(?:==|>=|<=|~=|>|<)\s*([0-9]+\.[0-9]+\.[0-9]+)/
)
if (!match) {
console.error(
'Could not find comfyui-frontend-package version in requirements.txt'
)
return null
}
return match[1]
}
/**
* Validate semantic version string
*/
function isValidSemver(version: string): boolean {
if (!version || typeof version !== 'string') {
return false
}
const parts = version.split('.')
if (parts.length !== 3) {
return false
}
return parts.every((part) => {
const num = Number(part)
return Number.isFinite(num) && num >= 0 && String(num) === part
})
}
/**
* Get the latest patch tag for a given minor version
*/
function getLatestPatchTag(repoPath: string, minor: number): string | null {
// Fetch all tags
exec('git fetch --tags', repoPath)
// Use git's native version sorting to get the latest tag
const latestTag = exec(
`git tag -l 'v1.${minor}.*' --sort=-version:refname | head -n 1`,
repoPath
)
if (!latestTag) {
return null
}
// Validate the tag is a valid semver (vX.Y.Z format)
const validTagRegex = /^v\d+\.\d+\.\d+$/
if (!validTagRegex.test(latestTag)) {
console.error(
`Latest tag for minor version ${minor} is not valid semver: ${latestTag}`
)
return null
}
return latestTag
}
/**
* Resolve the ComfyUI release information
*/
function resolveRelease(
comfyuiRepoPath: string,
frontendRepoPath: string
): ReleaseInfo | null {
// Parse current version from ComfyUI requirements.txt
const requirementsPath = path.join(comfyuiRepoPath, 'requirements.txt')
const currentVersion = parseRequirementsVersion(requirementsPath)
if (!currentVersion) {
return null
}
// Validate version format
if (!isValidSemver(currentVersion)) {
console.error(
`Invalid semantic version format: ${currentVersion}. Expected format: X.Y.Z`
)
return null
}
const [major, currentMinor, patch] = currentVersion.split('.').map(Number)
// Fetch all branches
exec('git fetch origin', frontendRepoPath)
// Try next minor first, fall back to current minor if not available
let targetMinor = currentMinor + 1
let targetBranch = `core/1.${targetMinor}`
const nextMinorExists = exec(
`git rev-parse --verify origin/${targetBranch}`,
frontendRepoPath
)
if (!nextMinorExists) {
// Fall back to current minor for patch releases
targetMinor = currentMinor
targetBranch = `core/1.${targetMinor}`
const currentMinorExists = exec(
`git rev-parse --verify origin/${targetBranch}`,
frontendRepoPath
)
if (!currentMinorExists) {
console.error(
`Neither core/1.${currentMinor + 1} nor core/1.${currentMinor} branches exist in frontend repo`
)
return null
}
console.error(
`Next minor branch core/1.${currentMinor + 1} not found, falling back to core/1.${currentMinor} for patch release`
)
}
// Get latest patch tag for target minor
const latestPatchTag = getLatestPatchTag(frontendRepoPath, targetMinor)
let needsRelease = false
let branchHeadSha: string | null = null
let tagCommitSha: string | null = null
let targetVersion = currentVersion
if (latestPatchTag) {
// Get commit SHA for the tag
tagCommitSha = exec(`git rev-list -n 1 ${latestPatchTag}`, frontendRepoPath)
// Get commit SHA for branch head
branchHeadSha = exec(
`git rev-parse origin/${targetBranch}`,
frontendRepoPath
)
// Check if there are commits between tag and branch head
const commitsBetween = exec(
`git rev-list ${latestPatchTag}..origin/${targetBranch} --count`,
frontendRepoPath
)
const commitCount = parseInt(commitsBetween, 10)
needsRelease = !isNaN(commitCount) && commitCount > 0
// Parse existing patch number and increment if needed
const tagVersion = latestPatchTag.replace('v', '')
// Validate tag version format
if (!isValidSemver(tagVersion)) {
console.error(
`Invalid tag version format: ${tagVersion}. Expected format: X.Y.Z`
)
return null
}
const [, , existingPatch] = tagVersion.split('.').map(Number)
// Validate existingPatch is a valid number
if (!Number.isFinite(existingPatch) || existingPatch < 0) {
console.error(`Invalid patch number in tag: ${existingPatch}`)
return null
}
if (needsRelease) {
targetVersion = `1.${targetMinor}.${existingPatch + 1}`
} else {
targetVersion = tagVersion
}
} else {
// No tags exist for this minor version, need to create v1.{targetMinor}.0
needsRelease = true
targetVersion = `1.${targetMinor}.0`
branchHeadSha = exec(
`git rev-parse origin/${targetBranch}`,
frontendRepoPath
)
}
const diffUrl = `https://github.com/Comfy-Org/ComfyUI_frontend/compare/v${currentVersion}...v${targetVersion}`
return {
current_version: currentVersion,
target_minor: targetMinor,
target_version: targetVersion,
target_branch: targetBranch,
needs_release: needsRelease,
latest_patch_tag: latestPatchTag,
branch_head_sha: branchHeadSha,
tag_commit_sha: tagCommitSha,
diff_url: diffUrl,
release_pr_url: null // Will be populated by workflow if release is triggered
}
}
// Main execution
const comfyuiRepoPath = process.argv[2]
const frontendRepoPath = process.argv[3] || process.cwd()
if (!comfyuiRepoPath) {
console.error(
'Usage: resolve-comfyui-release.ts <comfyui-repo-path> [frontend-repo-path]'
)
process.exit(1)
}
const releaseInfo = resolveRelease(comfyuiRepoPath, frontendRepoPath)
if (!releaseInfo) {
console.error('Failed to resolve release information')
process.exit(1)
}
// Output as JSON for GitHub Actions
// eslint-disable-next-line no-console
console.log(JSON.stringify(releaseInfo, null, 2))
export { resolveRelease }