From 2527b2fe333abe13a47560c2a052328ac10f8456 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 00:56:07 +0000 Subject: [PATCH 1/4] Initial plan From dd0ae9e2bacab7e0f4f4f6a79d7cc80a27bc8865 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:08:06 +0000 Subject: [PATCH 2/4] feat: Add automated workflow documentation generation and standardization --- .github/workflows/README.md | 272 +++++++++++- .github/workflows/ci-workflow-docs.yaml | 43 ++ .../workflows/i18n-update-custom-nodes.yaml | 1 + .github/workflows/i18n-update-nodes.yaml | 1 + .github/workflows/pr-backport.yaml | 1 + .../pr-update-playwright-expectations.yaml | 1 + .../publish-desktop-ui-on-merge.yaml | 1 + .github/workflows/publish-desktop-ui.yaml | 1 + .github/workflows/release-branch-create.yaml | 1 + .github/workflows/release-draft-create.yaml | 1 + .github/workflows/release-npm-types.yaml | 1 + .github/workflows/release-pypi-dev.yaml | 1 + .../workflows/version-bump-desktop-ui.yaml | 1 + package.json | 2 + pnpm-lock.yaml | 6 + pnpm-workspace.yaml | 7 +- scripts/cicd/generate-workflow-docs.ts | 389 ++++++++++++++++++ 17 files changed, 720 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci-workflow-docs.yaml create mode 100644 scripts/cicd/generate-workflow-docs.ts diff --git a/.github/workflows/README.md b/.github/workflows/README.md index f62e91eda..60a4267c7 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,5 +1,10 @@ # GitHub Workflows +This directory contains GitHub Actions workflow files that automate various aspects of the ComfyUI frontend development and release process. + +> **Note:** This documentation is auto-generated from workflow files. Do not edit manually. +> Run `pnpm workflow:docs` to regenerate. + ## Naming Convention Workflow files follow a consistent naming pattern: `-.yaml` @@ -8,14 +13,267 @@ Workflow files follow a consistent naming pattern: `-. | Prefix | Purpose | Example | | ---------- | ----------------------------------- | ------------------------------------ | -| `ci-` | Testing, linting, validation | `ci-tests-e2e.yaml` | -| `release-` | Version management, publishing | `release-version-bump.yaml` | -| `pr-` | PR automation (triggered by labels) | `pr-claude-review.yaml` | -| `api-` | External Api type generation | `api-update-registry-api-types.yaml` | -| `i18n-` | Internationalization updates | `i18n-update-core.yaml` | +| `ci-` | Testing, linting, validation | `ci-json-validation.yaml` | +| `pr-` | PR automation (triggered by labels) | `pr-backport.yaml` | +| `release-` | Version management, publishing | `release-branch-create.yaml` | +| `api-` | External API type generation | `api-update-electron-api-types.yaml` | +| `i18n-` | Internationalization updates | `i18n-update-core.yaml` | +| `publish-` | Publishing and deployment | `publish-desktop-ui-on-merge.yaml` | +| `version-` | Version management | `version-bump-desktop-ui.yaml` | + + +## Quick Reference + +For label-triggered workflows, add the corresponding label to a PR to trigger the workflow: +- `claude-review` - Trigger AI-powered code review +- `New Browser Test Expectations` - Update Playwright test snapshots + +For manual workflows, use the "Run workflow" button in the Actions tab. + +## Workflow Details + + +### CI + +#### [`ci-json-validation.yaml`](./ci-json-validation.yaml) + +**Name:** CI: JSON Validation + +**Description:** Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq + +**Triggers:** push + +#### [`ci-lint-format.yaml`](./ci-lint-format.yaml) + +**Name:** CI: Lint Format + +**Description:** Linting and code formatting validation for pull requests + +**Triggers:** pull_request + +#### [`ci-python-validation.yaml`](./ci-python-validation.yaml) + +**Name:** CI: Python Validation + +**Description:** Validates Python code in tools/devtools directory + +**Triggers:** pull_request, push + +#### [`ci-tests-e2e-forks.yaml`](./ci-tests-e2e-forks.yaml) + +**Name:** CI: Tests E2E (Deploy for Forks) + +**Description:** Deploys test results from forked PRs (forks can't access deployment secrets) + +#### [`ci-tests-e2e.yaml`](./ci-tests-e2e.yaml) + +**Name:** CI: Tests E2E + +**Description:** End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages + +**Triggers:** pull_request, push + +#### [`ci-tests-storybook-forks.yaml`](./ci-tests-storybook-forks.yaml) + +**Name:** CI: Tests Storybook (Deploy for Forks) + +**Description:** Deploys Storybook previews from forked PRs (forks can't access deployment secrets) + +#### [`ci-tests-storybook.yaml`](./ci-tests-storybook.yaml) + +**Name:** CI: Tests Storybook + +**Description:** Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages + +**Triggers:** workflow_dispatch (manual), pull_request + +#### [`ci-tests-unit.yaml`](./ci-tests-unit.yaml) + +**Name:** CI: Tests Unit + +**Description:** Unit and component testing with Vitest + +**Triggers:** pull_request, push + + +### PR + +#### [`pr-backport.yaml`](./pr-backport.yaml) + +**Name:** PR Backport + +**Description:** Automatically backports merged PRs to release branches when 'needs-backport' label is applied + +**Triggers:** workflow_dispatch (manual), pull_request_target (closed, labeled) + +#### [`pr-claude-review.yaml`](./pr-claude-review.yaml) + +**Name:** PR: Claude Review + +**Description:** AI-powered code review triggered by adding the 'claude-review' label to a PR + +**Triggers:** pull_request (labeled) + +**Label Triggers:** `claude-review` + +#### [`pr-update-playwright-expectations.yaml`](./pr-update-playwright-expectations.yaml) + +**Name:** PR: Update Playwright Expectations + +**Description:** Updates Playwright test snapshots when triggered by label or comment + +**Triggers:** pull_request (labeled), issue_comment (created) + +**Label Triggers:** `New Browser Test Expectations`, `/update-playwright` + + +### RELEASE + +#### [`release-branch-create.yaml`](./release-branch-create.yaml) + +**Name:** Release Branch Create + +**Description:** Creates release branch when version bump PR with 'Release' label is merged + +**Triggers:** pull_request (closed) + +#### [`release-draft-create.yaml`](./release-draft-create.yaml) + +**Name:** Release Draft Create + +**Description:** Creates GitHub release draft when version bump PR with 'Release' label is merged + +**Triggers:** pull_request (closed) + +#### [`release-npm-types.yaml`](./release-npm-types.yaml) + +**Name:** Release NPM Types + +**Description:** Manual workflow to publish TypeScript type definitions to npm + +**Triggers:** workflow_dispatch (manual) + +#### [`release-pypi-dev.yaml`](./release-pypi-dev.yaml) + +**Name:** Release PyPI Dev + +**Description:** Manual workflow to publish development version to PyPI + +**Triggers:** workflow_dispatch (manual) + +#### [`release-version-bump.yaml`](./release-version-bump.yaml) + +**Name:** Release: Version Bump + +**Description:** Manual workflow to increment package version with semantic versioning support + +**Triggers:** workflow_dispatch (manual) + + +### API + +#### [`api-update-electron-api-types.yaml`](./api-update-electron-api-types.yaml) + +**Name:** Api: Update Electron API Types + +**Description:** When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo + +**Triggers:** workflow_dispatch (manual) + +#### [`api-update-manager-api-types.yaml`](./api-update-manager-api-types.yaml) + +**Name:** Api: Update Manager API Types + +**Description:** When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo + +**Triggers:** workflow_dispatch (manual) + +#### [`api-update-registry-api-types.yaml`](./api-update-registry-api-types.yaml) + +**Name:** Api: Update Registry API Types + +**Description:** When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo + +**Triggers:** workflow_dispatch (manual) + + +### I18N + +#### [`i18n-update-core.yaml`](./i18n-update-core.yaml) + +**Name:** i18n: Update Core + +**Description:** Generates and updates translations for core ComfyUI components using OpenAI + +**Triggers:** workflow_dispatch (manual), pull_request (opened, synchronize, reopened) + +#### [`i18n-update-custom-nodes.yaml`](./i18n-update-custom-nodes.yaml) + +**Name:** i18n Update Custom Nodes + +**Description:** Updates translations for custom node repositories using OpenAI + +**Triggers:** workflow_dispatch (manual) + +#### [`i18n-update-nodes.yaml`](./i18n-update-nodes.yaml) + +**Name:** i18n Update Nodes + +**Description:** Updates translations for ComfyUI node definitions + +**Triggers:** workflow_dispatch (manual) + + +### PUBLISH + +#### [`publish-desktop-ui-on-merge.yaml`](./publish-desktop-ui-on-merge.yaml) + +**Name:** Publish Desktop UI on PR Merge + +**Description:** Automatically publishes desktop UI package to npm when version bump PR is merged + +**Triggers:** pull_request (closed) + +#### [`publish-desktop-ui.yaml`](./publish-desktop-ui.yaml) + +**Name:** Publish Desktop UI + +**Description:** Manual workflow to publish desktop UI package to npm with specified version + +**Triggers:** workflow_dispatch (manual) + + +### VERSION + +#### [`version-bump-desktop-ui.yaml`](./version-bump-desktop-ui.yaml) + +**Name:** Version Bump Desktop UI + +**Description:** Manual workflow to increment desktop UI package version with semantic versioning support + +**Triggers:** workflow_dispatch (manual) + + ## Documentation -Each workflow file contains comments explaining its purpose, triggers, and behavior. For specific details about what each workflow does, refer to the comments at the top of each `.yaml` file. +For more information about GitHub Actions, see: +- [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows) +- [Workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) -For GitHub Actions documentation, see [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows). +## Maintaining Workflows + +### Adding a New Workflow + +1. Create a new workflow file following the naming convention +2. Include `name` and `description` fields at the top of the workflow +3. Run `pnpm workflow:docs` to update this README +4. Commit both the workflow file and updated README + +### Best Practices + +1. **Always include a description**: Add a `description` field after the `name` field +2. **Use consistent prefixes**: Follow the established prefix conventions +3. **Label-triggered workflows**: For PR automation, use the `pr-` prefix +4. **Document triggers**: Make trigger conditions clear in the workflow description +5. **Keep docs in sync**: Run `pnpm workflow:docs` after any workflow changes diff --git a/.github/workflows/ci-workflow-docs.yaml b/.github/workflows/ci-workflow-docs.yaml new file mode 100644 index 000000000..e1fd5f011 --- /dev/null +++ b/.github/workflows/ci-workflow-docs.yaml @@ -0,0 +1,43 @@ +name: "CI: Workflow Documentation" +description: "Validates that workflow documentation is up-to-date with workflow files" + +on: + pull_request: + paths: + - '.github/workflows/*.yaml' + - '.github/workflows/*.yml' + - 'scripts/cicd/generate-workflow-docs.ts' + +jobs: + check-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate workflow documentation + run: pnpm workflow:docs + + - name: Check if documentation is up-to-date + run: | + if [ -n "$(git status --porcelain .github/workflows/README.md)" ]; then + echo "::error::Workflow documentation is out of date. Please run 'pnpm workflow:docs' and commit the changes." + git diff .github/workflows/README.md + exit 1 + else + echo "✓ Workflow documentation is up-to-date" + fi diff --git a/.github/workflows/i18n-update-custom-nodes.yaml b/.github/workflows/i18n-update-custom-nodes.yaml index 61076f031..d10c523d6 100644 --- a/.github/workflows/i18n-update-custom-nodes.yaml +++ b/.github/workflows/i18n-update-custom-nodes.yaml @@ -1,4 +1,5 @@ name: i18n Update Custom Nodes +description: "Updates translations for custom node repositories using OpenAI" on: workflow_dispatch: diff --git a/.github/workflows/i18n-update-nodes.yaml b/.github/workflows/i18n-update-nodes.yaml index 0b9f1534d..39107fca8 100644 --- a/.github/workflows/i18n-update-nodes.yaml +++ b/.github/workflows/i18n-update-nodes.yaml @@ -1,4 +1,5 @@ name: i18n Update Nodes +description: "Updates translations for ComfyUI node definitions" on: workflow_dispatch: diff --git a/.github/workflows/pr-backport.yaml b/.github/workflows/pr-backport.yaml index 13e6dd74e..8ebbecafd 100644 --- a/.github/workflows/pr-backport.yaml +++ b/.github/workflows/pr-backport.yaml @@ -1,4 +1,5 @@ name: PR Backport +description: "Automatically backports merged PRs to release branches when 'needs-backport' label is applied" on: pull_request_target: diff --git a/.github/workflows/pr-update-playwright-expectations.yaml b/.github/workflows/pr-update-playwright-expectations.yaml index f688c3250..b0835022d 100644 --- a/.github/workflows/pr-update-playwright-expectations.yaml +++ b/.github/workflows/pr-update-playwright-expectations.yaml @@ -1,5 +1,6 @@ # Setting test expectation screenshots for Playwright name: "PR: Update Playwright Expectations" +description: "Updates Playwright test snapshots when triggered by label or comment" on: pull_request: diff --git a/.github/workflows/publish-desktop-ui-on-merge.yaml b/.github/workflows/publish-desktop-ui-on-merge.yaml index b1adaa0c2..f7574aaf1 100644 --- a/.github/workflows/publish-desktop-ui-on-merge.yaml +++ b/.github/workflows/publish-desktop-ui-on-merge.yaml @@ -1,4 +1,5 @@ name: Publish Desktop UI on PR Merge +description: "Automatically publishes desktop UI package to npm when version bump PR is merged" on: pull_request: diff --git a/.github/workflows/publish-desktop-ui.yaml b/.github/workflows/publish-desktop-ui.yaml index 92324d2f5..421ae698a 100644 --- a/.github/workflows/publish-desktop-ui.yaml +++ b/.github/workflows/publish-desktop-ui.yaml @@ -1,4 +1,5 @@ name: Publish Desktop UI +description: "Manual workflow to publish desktop UI package to npm with specified version" on: workflow_dispatch: diff --git a/.github/workflows/release-branch-create.yaml b/.github/workflows/release-branch-create.yaml index 992e779dd..1de1847ce 100644 --- a/.github/workflows/release-branch-create.yaml +++ b/.github/workflows/release-branch-create.yaml @@ -1,4 +1,5 @@ name: Release Branch Create +description: "Creates release branch when version bump PR with 'Release' label is merged" on: pull_request: diff --git a/.github/workflows/release-draft-create.yaml b/.github/workflows/release-draft-create.yaml index 240a89f1f..a888594c8 100644 --- a/.github/workflows/release-draft-create.yaml +++ b/.github/workflows/release-draft-create.yaml @@ -1,4 +1,5 @@ name: Release Draft Create +description: "Creates GitHub release draft when version bump PR with 'Release' label is merged" on: pull_request: diff --git a/.github/workflows/release-npm-types.yaml b/.github/workflows/release-npm-types.yaml index 23f0cc016..03e107768 100644 --- a/.github/workflows/release-npm-types.yaml +++ b/.github/workflows/release-npm-types.yaml @@ -1,4 +1,5 @@ name: Release NPM Types +description: "Manual workflow to publish TypeScript type definitions to npm" on: workflow_dispatch: diff --git a/.github/workflows/release-pypi-dev.yaml b/.github/workflows/release-pypi-dev.yaml index 88675e82e..62f24ae76 100644 --- a/.github/workflows/release-pypi-dev.yaml +++ b/.github/workflows/release-pypi-dev.yaml @@ -1,4 +1,5 @@ name: Release PyPI Dev +description: "Manual workflow to publish development version to PyPI" on: workflow_dispatch: diff --git a/.github/workflows/version-bump-desktop-ui.yaml b/.github/workflows/version-bump-desktop-ui.yaml index 6d5d01c67..c50dd32f9 100644 --- a/.github/workflows/version-bump-desktop-ui.yaml +++ b/.github/workflows/version-bump-desktop-ui.yaml @@ -1,4 +1,5 @@ name: Version Bump Desktop UI +description: "Manual workflow to increment desktop UI package version with semantic versioning support" on: workflow_dispatch: diff --git a/package.json b/package.json index 9f1aeff9e..9d514d395 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "test:browser": "pnpm exec nx e2e", "test:unit": "nx run test", "typecheck": "vue-tsc --noEmit", + "workflow:docs": "tsx scripts/cicd/generate-workflow-docs.ts", "zipdist": "node scripts/zipdist.js" }, "devDependencies": { @@ -109,6 +110,7 @@ "vue-component-type-helpers": "catalog:", "vue-eslint-parser": "catalog:", "vue-tsc": "catalog:", + "yaml": "catalog:", "zip-dir": "^2.0.0", "zod-to-json-schema": "catalog:" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de05d7d7e..e0d476cb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -270,6 +270,9 @@ catalogs: vuefire: specifier: ^3.2.1 version: 3.2.1 + yaml: + specifier: ^2.8.1 + version: 2.8.1 yjs: specifier: ^13.6.27 version: 13.6.27 @@ -654,6 +657,9 @@ importers: vue-tsc: specifier: 'catalog:' version: 3.0.7(typescript@5.9.2) + yaml: + specifier: 'catalog:' + version: 2.8.1 zip-dir: specifier: ^2.0.0 version: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 10eadc424..469310756 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -91,6 +91,7 @@ catalog: vue-router: ^4.4.3 vue-tsc: ^3.0.7 vuefire: ^3.2.1 + yaml: ^2.8.1 yjs: ^13.6.27 zod: ^3.23.8 zod-to-json-schema: ^3.24.1 @@ -98,9 +99,6 @@ catalog: cleanupUnusedCatalogs: true -overrides: - '@types/eslint': '-' - ignoredBuiltDependencies: - '@firebase/util' - protobufjs @@ -115,3 +113,6 @@ onlyBuiltDependencies: - esbuild - nx - oxc-resolver + +overrides: + '@types/eslint': '-' diff --git a/scripts/cicd/generate-workflow-docs.ts b/scripts/cicd/generate-workflow-docs.ts new file mode 100644 index 000000000..fa0cde23c --- /dev/null +++ b/scripts/cicd/generate-workflow-docs.ts @@ -0,0 +1,389 @@ +#!/usr/bin/env tsx +/** + * Generate workflow documentation from GitHub Actions workflow files + * + * This script: + * 1. Scans all workflow YAML files in .github/workflows + * 2. Extracts metadata (name, description, triggers, labels) + * 3. Updates the workflows README.md with current information + */ +import { readFileSync, readdirSync, writeFileSync } from 'fs' +import { join } from 'path' +import { parse } from 'yaml' + +interface WorkflowMetadata { + filename: string + name: string + description?: string + prefix: string + triggers: string[] + labelTriggers: string[] +} + +interface WorkflowsByPrefix { + [prefix: string]: { + description: string + workflows: WorkflowMetadata[] + } +} + +const WORKFLOWS_DIR = join(process.cwd(), '.github/workflows') +const README_PATH = join(WORKFLOWS_DIR, 'README.md') + +// Category descriptions for workflow prefixes +const PREFIX_DESCRIPTIONS: Record = { + 'ci-': 'Testing, linting, validation', + 'release-': 'Version management, publishing', + 'pr-': 'PR automation (triggered by labels)', + 'api-': 'External API type generation', + 'i18n-': 'Internationalization updates', + 'publish-': 'Publishing and deployment', + 'version-': 'Version management' +} + +/** + * Extract label triggers from workflow content + */ +function extractLabelTriggers(content: string, workflowData: any): string[] { + const labels: string[] = [] + + // Check for label_trigger in anthropics/claude-code-action + const labelTriggerMatch = content.match(/label_trigger:\s*["']([^"']+)["']/i) + if (labelTriggerMatch) { + labels.push(labelTriggerMatch[1]) + } + + // Check for github.event.label.name == 'label-name' pattern + const labelNameMatches = content.matchAll( + /github\.event\.label\.name\s*==\s*['"]([^'"]+)['"]/gi + ) + for (const match of labelNameMatches) { + if (!labels.includes(match[1])) { + labels.push(match[1]) + } + } + + // Check for startsWith or contains patterns with label names + const labelCommentMatches = content.matchAll( + /startsWith\(github\.event\.comment\.body,\s*['"]([^'"]+)['"]\)/gi + ) + for (const match of labelCommentMatches) { + const command = match[1] + if (command && !labels.includes(command)) { + labels.push(command) + } + } + + return labels +} + +/** + * Extract trigger information from workflow + */ +function extractTriggers(workflowData: any): string[] { + const triggers: string[] = [] + const on = workflowData.on + + if (!on) return triggers + + if (typeof on === 'string') { + triggers.push(on) + } else if (Array.isArray(on)) { + triggers.push(...on) + } else if (typeof on === 'object') { + // Handle workflow_dispatch + if (on.workflow_dispatch !== undefined) { + triggers.push('workflow_dispatch (manual)') + } + + // Handle pull_request with types + if (on.pull_request) { + if (typeof on.pull_request === 'object' && on.pull_request.types) { + const types = Array.isArray(on.pull_request.types) + ? on.pull_request.types.join(', ') + : on.pull_request.types + triggers.push(`pull_request (${types})`) + } else { + triggers.push('pull_request') + } + } + + // Handle pull_request_target + if (on.pull_request_target) { + if ( + typeof on.pull_request_target === 'object' && + on.pull_request_target.types + ) { + const types = Array.isArray(on.pull_request_target.types) + ? on.pull_request_target.types.join(', ') + : on.pull_request_target.types + triggers.push(`pull_request_target (${types})`) + } else { + triggers.push('pull_request_target') + } + } + + // Handle push + if (on.push) { + triggers.push('push') + } + + // Handle schedule + if (on.schedule) { + triggers.push('schedule') + } + + // Handle issue_comment + if (on.issue_comment) { + if (typeof on.issue_comment === 'object' && on.issue_comment.types) { + const types = Array.isArray(on.issue_comment.types) + ? on.issue_comment.types.join(', ') + : on.issue_comment.types + triggers.push(`issue_comment (${types})`) + } else { + triggers.push('issue_comment') + } + } + } + + return triggers +} + +/** + * Parse a single workflow file and extract metadata + */ +function parseWorkflowFile(filename: string): WorkflowMetadata | null { + try { + const filepath = join(WORKFLOWS_DIR, filename) + const content = readFileSync(filepath, 'utf-8') + const workflowData = parse(content) + + if (!workflowData || !workflowData.name) { + console.warn(`Skipping ${filename}: no name field`) + return null + } + + // Determine prefix from filename + const prefixMatch = filename.match(/^([a-z0-9]+)-/) + const prefix = prefixMatch ? prefixMatch[1] + '-' : 'other-' + + const metadata: WorkflowMetadata = { + filename, + name: workflowData.name, + description: workflowData.description, + prefix, + triggers: extractTriggers(workflowData), + labelTriggers: extractLabelTriggers(content, workflowData) + } + + return metadata + } catch (error) { + console.error(`Error parsing ${filename}:`, error) + return null + } +} + +/** + * Group workflows by prefix + */ +function groupWorkflowsByPrefix( + workflows: WorkflowMetadata[] +): WorkflowsByPrefix { + const grouped: WorkflowsByPrefix = {} + + for (const workflow of workflows) { + if (!grouped[workflow.prefix]) { + grouped[workflow.prefix] = { + description: PREFIX_DESCRIPTIONS[workflow.prefix] || 'Other workflows', + workflows: [] + } + } + grouped[workflow.prefix].workflows.push(workflow) + } + + // Sort workflows within each group by filename + for (const prefix in grouped) { + grouped[prefix].workflows.sort((a, b) => + a.filename.localeCompare(b.filename) + ) + } + + return grouped +} + +/** + * Generate markdown table for workflow categories + */ +function generateCategoryTable(grouped: WorkflowsByPrefix): string { + const prefixOrder = [ + 'ci-', + 'pr-', + 'release-', + 'api-', + 'i18n-', + 'publish-', + 'version-', + 'other-' + ] + + let table = + '| Prefix | Purpose | Example |\n' + table += + '| ---------- | ----------------------------------- | ------------------------------------ |\n' + + for (const prefix of prefixOrder) { + if (grouped[prefix]) { + const example = grouped[prefix].workflows[0]?.filename || '' + const purpose = grouped[prefix].description + table += `| \`${prefix}\` | ${purpose} | \`${example}\` |\n` + } + } + + return table +} + +/** + * Generate detailed workflow list with descriptions + */ +function generateWorkflowList(grouped: WorkflowsByPrefix): string { + const prefixOrder = [ + 'ci-', + 'pr-', + 'release-', + 'api-', + 'i18n-', + 'publish-', + 'version-', + 'other-' + ] + let markdown = '' + + for (const prefix of prefixOrder) { + if (!grouped[prefix]) continue + + const category = grouped[prefix] + const prefixName = + prefix === 'other-' + ? 'Other Workflows' + : prefix.replace('-', '').toUpperCase() + + markdown += `\n### ${prefixName}\n\n` + + for (const workflow of category.workflows) { + markdown += `#### [\`${workflow.filename}\`](./${workflow.filename})\n\n` + markdown += `**Name:** ${workflow.name}\n\n` + + if (workflow.description) { + markdown += `**Description:** ${workflow.description}\n\n` + } + + if (workflow.triggers.length > 0) { + markdown += `**Triggers:** ${workflow.triggers.join(', ')}\n\n` + } + + if (workflow.labelTriggers.length > 0) { + markdown += `**Label Triggers:** \`${workflow.labelTriggers.join('`, `')}\`\n\n` + } + } + } + + return markdown +} + +/** + * Generate the complete README content + */ +function generateReadme(grouped: WorkflowsByPrefix): string { + const categoryTable = generateCategoryTable(grouped) + const workflowList = generateWorkflowList(grouped) + + return `# GitHub Workflows + +This directory contains GitHub Actions workflow files that automate various aspects of the ComfyUI frontend development and release process. + +> **Note:** This documentation is auto-generated from workflow files. Do not edit manually. +> Run \`pnpm workflow:docs\` to regenerate. + +## Naming Convention + +Workflow files follow a consistent naming pattern: \`-.yaml\` + +### Category Prefixes + +${categoryTable} + +## Quick Reference + +For label-triggered workflows, add the corresponding label to a PR to trigger the workflow: +- \`claude-review\` - Trigger AI-powered code review +- \`New Browser Test Expectations\` - Update Playwright test snapshots + +For manual workflows, use the "Run workflow" button in the Actions tab. + +## Workflow Details + +${workflowList} + +## Documentation + +For more information about GitHub Actions, see: +- [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows) +- [Workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) + +## Maintaining Workflows + +### Adding a New Workflow + +1. Create a new workflow file following the naming convention +2. Include \`name\` and \`description\` fields at the top of the workflow +3. Run \`pnpm workflow:docs\` to update this README +4. Commit both the workflow file and updated README + +### Best Practices + +1. **Always include a description**: Add a \`description\` field after the \`name\` field +2. **Use consistent prefixes**: Follow the established prefix conventions +3. **Label-triggered workflows**: For PR automation, use the \`pr-\` prefix +4. **Document triggers**: Make trigger conditions clear in the workflow description +5. **Keep docs in sync**: Run \`pnpm workflow:docs\` after any workflow changes +` +} + +/** + * Main function + */ +function main() { + // Read all workflow files + const files = readdirSync(WORKFLOWS_DIR).filter( + (f) => f.endsWith('.yaml') || f.endsWith('.yml') + ) + const workflowFiles = files.filter((f) => f !== 'README.md') + + // Parse each workflow + const workflows: WorkflowMetadata[] = [] + for (const filename of workflowFiles) { + const metadata = parseWorkflowFile(filename) + if (metadata) { + workflows.push(metadata) + } + } + + // Group workflows by prefix + const grouped = groupWorkflowsByPrefix(workflows) + + // Generate README + const readme = generateReadme(grouped) + writeFileSync(README_PATH, readme, 'utf-8') + + // Show label-triggered workflows for validation + const labelWorkflows = workflows.filter((w) => w.labelTriggers.length > 0) + if (labelWorkflows.length > 0 && process.env.VERBOSE) { + for (const workflow of labelWorkflows) { + console.warn( + `Label-triggered: ${workflow.name}: ${workflow.labelTriggers.join(', ')}` + ) + } + } +} + +main() From 254a03aba0502476b65b6f45afd1f2a187079855 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:13:05 +0000 Subject: [PATCH 3/4] feat: Improve workflow documentation generation - Add detection for needs-backport and Release labels - Deduplicate label triggers in Quick Reference section - Generate Quick Reference dynamically from workflows - Show all label-triggered workflows with descriptions Co-authored-by: snomiao <7323030+snomiao@users.noreply.github.com> --- .github/workflows/README.md | 23 ++++++++- scripts/cicd/generate-workflow-docs.ts | 68 +++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 60a4267c7..e84b6beeb 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -25,11 +25,14 @@ Workflow files follow a consistent naming pattern: `-. ## Quick Reference For label-triggered workflows, add the corresponding label to a PR to trigger the workflow: -- `claude-review` - Trigger AI-powered code review -- `New Browser Test Expectations` - Update Playwright test snapshots +- `New Browser Test Expectations` - Updates Playwright test snapshots when triggered by label or comment +- `Release` - Triggers 3 workflows +- `claude-review` - AI-powered code review triggered by adding the 'claude-review' label to a PR +- `needs-backport` - Automatically backports merged PRs to release branches when 'needs-backport' label is applied For manual workflows, use the "Run workflow" button in the Actions tab. + ## Workflow Details @@ -95,6 +98,14 @@ For manual workflows, use the "Run workflow" button in the Actions tab. **Triggers:** pull_request, push +#### [`ci-workflow-docs.yaml`](./ci-workflow-docs.yaml) + +**Name:** CI: Workflow Documentation + +**Description:** Validates that workflow documentation is up-to-date with workflow files + +**Triggers:** pull_request + ### PR @@ -106,6 +117,8 @@ For manual workflows, use the "Run workflow" button in the Actions tab. **Triggers:** workflow_dispatch (manual), pull_request_target (closed, labeled) +**Label Triggers:** `needs-backport` + #### [`pr-claude-review.yaml`](./pr-claude-review.yaml) **Name:** PR: Claude Review @@ -137,6 +150,8 @@ For manual workflows, use the "Run workflow" button in the Actions tab. **Triggers:** pull_request (closed) +**Label Triggers:** `Release` + #### [`release-draft-create.yaml`](./release-draft-create.yaml) **Name:** Release Draft Create @@ -145,6 +160,8 @@ For manual workflows, use the "Run workflow" button in the Actions tab. **Triggers:** pull_request (closed) +**Label Triggers:** `Release` + #### [`release-npm-types.yaml`](./release-npm-types.yaml) **Name:** Release NPM Types @@ -234,6 +251,8 @@ For manual workflows, use the "Run workflow" button in the Actions tab. **Triggers:** pull_request (closed) +**Label Triggers:** `Release` + #### [`publish-desktop-ui.yaml`](./publish-desktop-ui.yaml) **Name:** Publish Desktop UI diff --git a/scripts/cicd/generate-workflow-docs.ts b/scripts/cicd/generate-workflow-docs.ts index fa0cde23c..c971b534b 100644 --- a/scripts/cicd/generate-workflow-docs.ts +++ b/scripts/cicd/generate-workflow-docs.ts @@ -63,6 +63,16 @@ function extractLabelTriggers(content: string, workflowData: any): string[] { } } + // Check for contains(github.event.pull_request.labels.*.name, 'label-name') pattern + const containsLabelMatches = content.matchAll( + /contains\(github\.event\.pull_request\.labels\.\*\.name,\s*['"]([^'"]+)['"]\)/gi + ) + for (const match of containsLabelMatches) { + if (!labels.includes(match[1])) { + labels.push(match[1]) + } + } + // Check for startsWith or contains patterns with label names const labelCommentMatches = content.matchAll( /startsWith\(github\.event\.comment\.body,\s*['"]([^'"]+)['"]\)/gi @@ -290,11 +300,61 @@ function generateWorkflowList(grouped: WorkflowsByPrefix): string { return markdown } +/** + * Generate quick reference for label-triggered workflows + */ +function generateQuickReference(grouped: WorkflowsByPrefix): string { + const allWorkflows = Object.values(grouped).flatMap((g) => g.workflows) + const labelWorkflows = allWorkflows.filter((w) => w.labelTriggers.length > 0) + + if (labelWorkflows.length === 0) { + return '' + } + + // Group workflows by label to avoid duplicates + const labelMap = new Map() + for (const workflow of labelWorkflows) { + for (const label of workflow.labelTriggers) { + // Skip comment-based triggers (like /update-playwright) + if (label.startsWith('/')) { + continue + } + const description = workflow.description || workflow.name + if (!labelMap.has(label)) { + labelMap.set(label, []) + } + labelMap.get(label)!.push(description) + } + } + + let markdown = '## Quick Reference\n\n' + markdown += + 'For label-triggered workflows, add the corresponding label to a PR to trigger the workflow:\n' + + // Sort labels alphabetically for consistency + const sortedLabels = Array.from(labelMap.keys()).sort() + for (const label of sortedLabels) { + const descriptions = labelMap.get(label)! + // Use the first description, or combine if multiple + const description = + descriptions.length === 1 + ? descriptions[0] + : `Triggers ${descriptions.length} workflows` + markdown += `- \`${label}\` - ${description}\n` + } + + markdown += + '\nFor manual workflows, use the "Run workflow" button in the Actions tab.\n' + + return markdown +} + /** * Generate the complete README content */ function generateReadme(grouped: WorkflowsByPrefix): string { const categoryTable = generateCategoryTable(grouped) + const quickReference = generateQuickReference(grouped) const workflowList = generateWorkflowList(grouped) return `# GitHub Workflows @@ -312,13 +372,7 @@ Workflow files follow a consistent naming pattern: \`- ${categoryTable} -## Quick Reference - -For label-triggered workflows, add the corresponding label to a PR to trigger the workflow: -- \`claude-review\` - Trigger AI-powered code review -- \`New Browser Test Expectations\` - Update Playwright test snapshots - -For manual workflows, use the "Run workflow" button in the Actions tab. +${quickReference} ## Workflow Details From ae70e458ba914357b0b6ea664b8155b8701af1f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:17:32 +0000 Subject: [PATCH 4/4] refactor: Address code review feedback - Extract duplicate label checking into addUniqueLabel helper - Improve comments explaining trigger filtering logic - Clarify which triggers are shown in Quick Reference vs detailed sections Co-authored-by: snomiao <7323030+snomiao@users.noreply.github.com> --- scripts/cicd/generate-workflow-docs.ts | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/scripts/cicd/generate-workflow-docs.ts b/scripts/cicd/generate-workflow-docs.ts index c971b534b..bf3c5b007 100644 --- a/scripts/cicd/generate-workflow-docs.ts +++ b/scripts/cicd/generate-workflow-docs.ts @@ -41,6 +41,15 @@ const PREFIX_DESCRIPTIONS: Record = { 'version-': 'Version management' } +/** + * Add a label to the list if it's not already present + */ +function addUniqueLabel(labels: string[], label: string): void { + if (!labels.includes(label)) { + labels.push(label) + } +} + /** * Extract label triggers from workflow content */ @@ -58,9 +67,7 @@ function extractLabelTriggers(content: string, workflowData: any): string[] { /github\.event\.label\.name\s*==\s*['"]([^'"]+)['"]/gi ) for (const match of labelNameMatches) { - if (!labels.includes(match[1])) { - labels.push(match[1]) - } + addUniqueLabel(labels, match[1]) } // Check for contains(github.event.pull_request.labels.*.name, 'label-name') pattern @@ -68,19 +75,18 @@ function extractLabelTriggers(content: string, workflowData: any): string[] { /contains\(github\.event\.pull_request\.labels\.\*\.name,\s*['"]([^'"]+)['"]\)/gi ) for (const match of containsLabelMatches) { - if (!labels.includes(match[1])) { - labels.push(match[1]) - } + addUniqueLabel(labels, match[1]) } - // Check for startsWith or contains patterns with label names + // Check for startsWith patterns with comment commands (e.g., /update-playwright) + // These are included as they can trigger workflows through PR comments const labelCommentMatches = content.matchAll( /startsWith\(github\.event\.comment\.body,\s*['"]([^'"]+)['"]\)/gi ) for (const match of labelCommentMatches) { const command = match[1] - if (command && !labels.includes(command)) { - labels.push(command) + if (command) { + addUniqueLabel(labels, command) } } @@ -315,7 +321,8 @@ function generateQuickReference(grouped: WorkflowsByPrefix): string { const labelMap = new Map() for (const workflow of labelWorkflows) { for (const label of workflow.labelTriggers) { - // Skip comment-based triggers (like /update-playwright) + // Filter out comment-based triggers (commands starting with /) from Quick Reference + // These are still shown in detailed workflow sections with full context if (label.startsWith('/')) { continue } @@ -335,7 +342,7 @@ function generateQuickReference(grouped: WorkflowsByPrefix): string { const sortedLabels = Array.from(labelMap.keys()).sort() for (const label of sortedLabels) { const descriptions = labelMap.get(label)! - // Use the first description, or combine if multiple + // Use the first description, or note if multiple workflows share the same label const description = descriptions.length === 1 ? descriptions[0]