Merge branch 'main' into feat/targeted-screenshot-tests

This commit is contained in:
Johnpaul Chiwetelu
2026-04-14 15:10:27 +01:00
committed by GitHub
6 changed files with 134 additions and 33 deletions

View File

@@ -1,14 +1,23 @@
# Automated bi-weekly workflow to bump ComfyUI frontend RC releases
name: 'Release: Bi-weekly ComfyUI'
# Release workflow for ComfyUI frontend: version bump → PyPI publish → ComfyUI PR.
# Runs on a bi-weekly schedule for minor releases, or manually for patch/hotfix releases.
name: 'Release: ComfyUI'
on:
# Schedule for Monday at 12:00 PM PST (20:00 UTC)
# Bi-weekly schedule: Monday at 20:00 UTC
schedule:
- cron: '0 20 * * 1'
# Allow manual triggering (bypasses bi-weekly check)
# Manual trigger for both on-demand minor and patch/hotfix releases
workflow_dispatch:
inputs:
release_type:
description: 'minor = next minor version (bi-weekly cadence), patch = hotfix for current production version'
required: true
default: 'minor'
type: choice
options:
- minor
- patch
comfyui_fork:
description: 'ComfyUI fork to use for PR (e.g., Comfy-Org/ComfyUI)'
required: false
@@ -41,10 +50,11 @@ jobs:
- name: Summary
run: |
echo "## Bi-weekly Check" >> $GITHUB_STEP_SUMMARY
echo "## Release Check" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Is release week: ${{ steps.check.outputs.is_release_week }}" >> $GITHUB_STEP_SUMMARY
echo "- Manual trigger: ${{ github.event_name == 'workflow_dispatch' }}" >> $GITHUB_STEP_SUMMARY
echo "- Release type: ${{ inputs.release_type || 'minor (scheduled)' }}" >> $GITHUB_STEP_SUMMARY
resolve-version:
needs: check-release-week
@@ -76,6 +86,8 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
with:
package_json_file: frontend/package.json
- name: Setup Node.js
uses: actions/setup-node@v6
@@ -89,6 +101,8 @@ jobs:
- name: Resolve release information
id: resolve
working-directory: frontend
env:
RELEASE_TYPE: ${{ inputs.release_type || 'minor' }}
run: |
set -euo pipefail

View File

@@ -137,35 +137,71 @@ function resolveRelease(
// 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}`
// Determine target branch based on release type:
// 'patch' → target current minor (hotfix for production version)
// 'minor' → try next minor, fall back to current minor (bi-weekly cadence)
const releaseTypeInput =
process.env.RELEASE_TYPE?.trim().toLowerCase() || 'minor'
if (releaseTypeInput !== 'minor' && releaseTypeInput !== 'patch') {
console.error(
`Invalid RELEASE_TYPE: "${releaseTypeInput}". Expected "minor" or "patch"`
)
return null
}
const releaseType: 'minor' | 'patch' = releaseTypeInput
let targetMinor: number
let targetBranch: string
const nextMinorExists = exec(
`git rev-parse --verify origin/${targetBranch}`,
frontendRepoPath
)
if (!nextMinorExists) {
// Fall back to current minor for patch releases
if (releaseType === 'patch') {
targetMinor = currentMinor
targetBranch = `core/1.${targetMinor}`
const currentMinorExists = exec(
const branchExists = exec(
`git rev-parse --verify origin/${targetBranch}`,
frontendRepoPath
)
if (!currentMinorExists) {
if (!branchExists) {
console.error(
`Neither core/1.${currentMinor + 1} nor core/1.${currentMinor} branches exist in frontend repo`
`Patch release requested but branch ${targetBranch} does not exist`
)
return null
}
console.error(
`Next minor branch core/1.${currentMinor + 1} not found, falling back to core/1.${currentMinor} for patch release`
`Patch release: targeting current production branch ${targetBranch}`
)
} else {
// Try next minor first, fall back to current minor if not available
targetMinor = currentMinor + 1
targetBranch = `core/1.${targetMinor}`
const nextMinorExists = exec(
`git rev-parse --verify origin/${targetBranch}`,
frontendRepoPath
)
if (!nextMinorExists) {
// Fall back to current minor for minor release
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 minor release`
)
}
}
// Get latest patch tag for target minor
@@ -264,7 +300,7 @@ if (!releaseInfo) {
}
// Output as JSON for GitHub Actions
// oxlint-disable-next-line no-console -- stdout is captured by the workflow
console.log(JSON.stringify(releaseInfo, null, 2))
export { resolveRelease }

View File

@@ -4,7 +4,6 @@ import Popover from 'primevue/popover'
import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
import { useToastStore } from '@/platform/updates/common/toastStore'
import type {
@@ -51,7 +50,6 @@ interface Props {
}
const { t } = useI18n()
const overlayProps = useTransformCompatOverlayProps()
const {
placeholder,
@@ -211,7 +209,6 @@ function handleSelection(item: FormDropdownItem, index: number) {
ref="popoverRef"
:dismissable="true"
:close-on-escape="true"
:append-to="overlayProps.appendTo"
unstyled
:pt="{
root: {

View File

@@ -4,7 +4,6 @@ import { ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
import type {
FilterOption,
OwnershipFilterOption,
@@ -16,7 +15,6 @@ import FormSearchInput from '../FormSearchInput.vue'
import type { LayoutMode, SortOption } from './types'
const { t } = useI18n()
const overlayProps = useTransformCompatOverlayProps()
defineProps<{
sortOptions: SortOption[]
@@ -135,7 +133,6 @@ function toggleBaseModelSelection(item: FilterOption) {
ref="sortPopoverRef"
:dismissable="true"
:close-on-escape="true"
:append-to="overlayProps.appendTo"
unstyled
:pt="{
root: {
@@ -198,7 +195,6 @@ function toggleBaseModelSelection(item: FilterOption) {
ref="ownershipPopoverRef"
:dismissable="true"
:close-on-escape="true"
:append-to="overlayProps.appendTo"
unstyled
:pt="{
root: {
@@ -261,7 +257,6 @@ function toggleBaseModelSelection(item: FilterOption) {
ref="baseModelPopoverRef"
:dismissable="true"
:close-on-escape="true"
:append-to="overlayProps.appendTo"
unstyled
:pt="{
root: {

View File

@@ -7,12 +7,21 @@ import { useMissingNodesErrorStore } from '@/platform/nodeReplacement/missingNod
import { executionIdToNodeLocatorId } from '@/utils/graphTraversalUtil'
// Create mock functions that will be shared
const mockNodeExecutionIdToNodeLocatorId = vi.fn()
const mockNodeIdToNodeLocatorId = vi.fn()
const mockNodeLocatorIdToNodeExecutionId = vi.fn()
const {
mockNodeExecutionIdToNodeLocatorId,
mockNodeIdToNodeLocatorId,
mockNodeLocatorIdToNodeExecutionId,
mockShowTextPreview
} = vi.hoisted(() => ({
mockNodeExecutionIdToNodeLocatorId: vi.fn(),
mockNodeIdToNodeLocatorId: vi.fn(),
mockNodeLocatorIdToNodeExecutionId: vi.fn(),
mockShowTextPreview: vi.fn()
}))
import type * as WorkflowStoreModule from '@/platform/workflow/management/stores/workflowStore'
import type { NodeProgressState } from '@/schemas/apiSchema'
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
import { createTestingPinia } from '@pinia/testing'
@@ -38,7 +47,7 @@ declare global {
vi.mock('@/composables/node/useNodeProgressText', () => ({
useNodeProgressText: () => ({
showTextPreview: vi.fn()
showTextPreview: mockShowTextPreview
})
}))
@@ -431,6 +440,56 @@ describe('useExecutionStore - reconcileInitializingJobs', () => {
})
})
describe('useExecutionStore - progress_text startup guard', () => {
let store: ReturnType<typeof useExecutionStore>
function fireProgressText(detail: {
nodeId: string
text: string
prompt_id?: string
}) {
const handler = apiEventHandlers.get('progress_text')
if (!handler) throw new Error('progress_text handler not bound')
handler(new CustomEvent('progress_text', { detail }))
}
beforeEach(() => {
vi.clearAllMocks()
apiEventHandlers.clear()
setActivePinia(createTestingPinia({ stubActions: false }))
store = useExecutionStore()
store.bindExecutionEvents()
})
it('should ignore progress_text before the canvas is initialized', async () => {
const { useCanvasStore } =
await import('@/renderer/core/canvas/canvasStore')
useCanvasStore().canvas = null
expect(() =>
fireProgressText({
nodeId: '1',
text: 'warming up'
})
).not.toThrow()
expect(mockShowTextPreview).not.toHaveBeenCalled()
})
it('should call showTextPreview when canvas is available', async () => {
const mockNode = createMockLGraphNode({ id: 1 })
const { useCanvasStore } =
await import('@/renderer/core/canvas/canvasStore')
useCanvasStore().canvas = {
graph: { getNodeById: vi.fn(() => mockNode) }
} as unknown as LGraphCanvas
fireProgressText({ nodeId: '1', text: 'warming up' })
expect(mockShowTextPreview).toHaveBeenCalledWith(mockNode, 'warming up')
})
})
describe('useExecutionErrorStore - Node Error Lookups', () => {
let store: ReturnType<typeof useExecutionErrorStore>

View File

@@ -527,7 +527,7 @@ export const useExecutionStore = defineStore('execution', () => {
// Handle execution node IDs for subgraphs
const currentId = getNodeIdIfExecuting(nodeId)
if (!currentId) return
const node = canvasStore.getCanvas().graph?.getNodeById(currentId)
const node = canvasStore.canvas?.graph?.getNodeById(currentId)
if (!node) return
useNodeProgressText().showTextPreview(node, text)