mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
Adds scheduled workflow to bump ComfyUI frontend RC releases every Monday at noon PST. ## Implementation - **Resolver script** (`scripts/cicd/resolve-comfyui-release.ts`): Checks ComfyUI `requirements.txt` and determines next minor version, compares branch commits to latest patch tag - **Workflow** (`.github/workflows/weekly-comfyui-release.yaml`): - Scheduled for Monday 20:00 UTC (noon PST) - Manual dispatch supported for testing/off-cycle runs - Three jobs: resolve version → trigger release if needed → create ComfyUI PR - Reuses existing `release-version-bump.yaml` workflow - Creates draft PR from fork to `comfyanonymous/ComfyUI` with updated `requirements.txt` - Includes error handling and validation for all steps - Force pushes to same branch weekly to maintain single open PR ## Testing - Resolver script tested locally: correctly identified v1.28.8 → v1.29.4 with release needed - yamllint passes - knip passes with `gh` binary added to ignore list ## Follow-up Can test via workflow_dispatch once merged, or in this PR if we enable Actions on branch. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6877-feat-Add-weekly-ComfyUI-release-automation-2b46d73d36508154aa05c783c6942d9a) by [Unito](https://www.unito.io)
255 lines
6.6 KiB
TypeScript
Executable File
255 lines
6.6 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)
|
|
|
|
// Calculate target minor version (next minor)
|
|
const targetMinor = currentMinor + 1
|
|
const targetBranch = `core/1.${targetMinor}`
|
|
|
|
// Check if target branch exists in frontend repo
|
|
exec('git fetch origin', frontendRepoPath)
|
|
const branchExists = exec(
|
|
`git rev-parse --verify origin/${targetBranch}`,
|
|
frontendRepoPath
|
|
)
|
|
|
|
if (!branchExists) {
|
|
console.error(
|
|
`Target branch ${targetBranch} does not exist in frontend repo`
|
|
)
|
|
return null
|
|
}
|
|
|
|
// 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 }
|