mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 22:37:32 +00:00
Merge branch 'copilot/standardize-triggering-and-docs' of github.com:Comfy-Org/ComfyUI_frontend into copilot/standardize-triggering-and-docs
This commit is contained in:
291
.github/workflows/README.md
vendored
291
.github/workflows/README.md
vendored
@@ -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: `<prefix>-<descriptive-name>.yaml`
|
||||
@@ -8,14 +13,286 @@ Workflow files follow a consistent naming pattern: `<prefix>-<descriptive-name>.
|
||||
|
||||
| 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:
|
||||
- `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
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
#### [`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
|
||||
|
||||
#### [`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)
|
||||
|
||||
**Label Triggers:** `needs-backport`
|
||||
|
||||
#### [`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)
|
||||
|
||||
**Label Triggers:** `Release`
|
||||
|
||||
#### [`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)
|
||||
|
||||
**Label Triggers:** `Release`
|
||||
|
||||
#### [`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)
|
||||
|
||||
**Label Triggers:** `Release`
|
||||
|
||||
#### [`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
|
||||
|
||||
43
.github/workflows/ci-workflow-docs.yaml
vendored
Normal file
43
.github/workflows/ci-workflow-docs.yaml
vendored
Normal file
@@ -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
|
||||
@@ -1,4 +1,5 @@
|
||||
name: i18n Update Custom Nodes
|
||||
description: "Updates translations for custom node repositories using OpenAI"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
1
.github/workflows/i18n-update-nodes.yaml
vendored
1
.github/workflows/i18n-update-nodes.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: i18n Update Nodes
|
||||
description: "Updates translations for ComfyUI node definitions"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
1
.github/workflows/pr-backport.yaml
vendored
1
.github/workflows/pr-backport.yaml
vendored
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
1
.github/workflows/publish-desktop-ui.yaml
vendored
1
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Publish Desktop UI
|
||||
description: "Manual workflow to publish desktop UI package to npm with specified version"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
1
.github/workflows/release-branch-create.yaml
vendored
1
.github/workflows/release-branch-create.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Release Branch Create
|
||||
description: "Creates release branch when version bump PR with 'Release' label is merged"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
1
.github/workflows/release-draft-create.yaml
vendored
1
.github/workflows/release-draft-create.yaml
vendored
@@ -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:
|
||||
|
||||
1
.github/workflows/release-npm-types.yaml
vendored
1
.github/workflows/release-npm-types.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Release NPM Types
|
||||
description: "Manual workflow to publish TypeScript type definitions to npm"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
1
.github/workflows/release-pypi-dev.yaml
vendored
1
.github/workflows/release-pypi-dev.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Release PyPI Dev
|
||||
description: "Manual workflow to publish development version to PyPI"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:"
|
||||
},
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
450
scripts/cicd/generate-workflow-docs.ts
Normal file
450
scripts/cicd/generate-workflow-docs.ts
Normal file
@@ -0,0 +1,450 @@
|
||||
#!/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<string, string> = {
|
||||
'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'
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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) {
|
||||
addUniqueLabel(labels, match[1])
|
||||
}
|
||||
|
||||
// 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) {
|
||||
addUniqueLabel(labels, match[1])
|
||||
}
|
||||
|
||||
// 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) {
|
||||
addUniqueLabel(labels, 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 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<string, string[]>()
|
||||
for (const workflow of labelWorkflows) {
|
||||
for (const label of workflow.labelTriggers) {
|
||||
// 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
|
||||
}
|
||||
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 note if multiple workflows share the same label
|
||||
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
|
||||
|
||||
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: \`<prefix>-<descriptive-name>.yaml\`
|
||||
|
||||
### Category Prefixes
|
||||
|
||||
${categoryTable}
|
||||
|
||||
${quickReference}
|
||||
|
||||
## 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()
|
||||
Reference in New Issue
Block a user