Compare commits
20 Commits
coderabbit
...
api-change
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
842515c2be | ||
|
|
e36e25ebd6 | ||
|
|
f844d3e95b | ||
|
|
98ed124b02 | ||
|
|
e933b5c357 | ||
|
|
cb32562f04 | ||
|
|
9c94a4818f | ||
|
|
779f539b0e | ||
|
|
95c815f17c | ||
|
|
30dcf8c133 | ||
|
|
f1193a2f86 | ||
|
|
75daf2e4d2 | ||
|
|
11922709a9 | ||
|
|
3742a76cfb | ||
|
|
c84144581d | ||
|
|
8b88f8ccae | ||
|
|
0aab7cba4b | ||
|
|
608874a312 | ||
|
|
5cf6ac07ac | ||
|
|
ff60bdf1bc |
6
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@@ -36,9 +36,9 @@ body:
|
||||
3. Click Queue Prompt
|
||||
4. See error
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ runs:
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
if [[ -z "$VERSION_FILE" ]]; then
|
||||
echo "::error::version_file input is required" >&2
|
||||
echo '::error::version_file input is required' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -55,13 +55,11 @@ runs:
|
||||
|
||||
case "$VERSION_FILE" in
|
||||
package.json)
|
||||
LINKS_VALUE=$(printf '%s\n%s' \
|
||||
'PyPI|https://pypi.org/project/comfyui-frontend-package/{{version}}/' \
|
||||
'npm types|https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/{{version}}')
|
||||
LINKS_VALUE=$'PyPI|https://pypi.org/project/comfyui-frontend-package/{{version}}/\n''npm types|https://npm.im/@comfyorg/comfyui-frontend-types@{{version}}'
|
||||
;;
|
||||
apps/desktop-ui/package.json)
|
||||
MARKER='desktop-release-summary'
|
||||
LINKS_VALUE='npm desktop UI|https://www.npmjs.com/package/@comfyorg/desktop-ui/v/{{version}}'
|
||||
LINKS_VALUE='npm desktop UI|https://npm.im/@comfyorg/desktop-ui@{{version}}'
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -71,13 +69,12 @@ runs:
|
||||
COMMENT_FILE=$(mktemp)
|
||||
|
||||
{
|
||||
echo "<!--$MARKER:$DIFF_PREFIX$NEW_VERSION-->"
|
||||
echo "$MESSAGE"
|
||||
echo ""
|
||||
echo "- $DIFF_LABEL: [\`$DIFF_PREFIX$PREV_VERSION...$DIFF_PREFIX$NEW_VERSION\`]($DIFF_URL)"
|
||||
printf '<!--%s:%s%s-->\n' "$MARKER" "$DIFF_PREFIX" "$NEW_VERSION"
|
||||
printf '%s\n\n' "$MESSAGE"
|
||||
printf -- '- %s: [%s%s...%s%s](%s)\n' "$DIFF_LABEL" "$DIFF_PREFIX" "$PREV_VERSION" "$DIFF_PREFIX" "$NEW_VERSION" "$DIFF_URL"
|
||||
|
||||
while IFS= read -r RAW_LINE; do
|
||||
LINE=$(echo "$RAW_LINE" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
LINE=$(printf '%s' "$RAW_LINE" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
[[ -z "$LINE" ]] && continue
|
||||
if [[ "$LINE" != *"|"* ]]; then
|
||||
echo "::warning::Skipping malformed link entry: $LINE" >&2
|
||||
@@ -87,16 +84,16 @@ runs:
|
||||
URL_TEMPLATE=${LINE#*|}
|
||||
URL=${URL_TEMPLATE//\{\{version\}\}/$NEW_VERSION}
|
||||
URL=${URL//\{\{prev_version\}\}/$PREV_VERSION}
|
||||
echo "- $LABEL: [\`$NEW_VERSION\`]($URL)"
|
||||
printf -- '- %s: %s\n' "$LABEL" "$URL"
|
||||
done <<< "$LINKS_VALUE"
|
||||
|
||||
echo ""
|
||||
printf '\n'
|
||||
} > "$COMMENT_FILE"
|
||||
|
||||
{
|
||||
echo "body<<COMMENT_BODY_END_MARKER"
|
||||
echo "body<<'EOF'"
|
||||
cat "$COMMENT_FILE"
|
||||
echo "COMMENT_BODY_END_MARKER"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "prev_version=$PREV_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "marker_search=<!--$MARKER:" >> "$GITHUB_OUTPUT"
|
||||
|
||||
234
.github/workflows/README-manual-api-changelog.md
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
# Manual API Changelog Generation
|
||||
|
||||
This workflow allows you to generate API changelogs by comparing any two versions of the ComfyUI Frontend package.
|
||||
|
||||
## Usage
|
||||
|
||||
### Via GitHub Actions UI
|
||||
|
||||
1. Go to **Actions** tab in the repository
|
||||
2. Select **Manual API Changelog Generation** from the workflows list
|
||||
3. Click **Run workflow** button
|
||||
4. Fill in the inputs:
|
||||
- **Previous version**: The earlier version (e.g., `1.29.0` or `v1.29.0`)
|
||||
- **Current version**: The later version (e.g., `1.30.2` or `v1.30.2`)
|
||||
- **Create PR**: Check this to automatically create a pull request with the changelog
|
||||
|
||||
### Via GitHub CLI
|
||||
|
||||
```bash
|
||||
# Basic usage - just generate changelog
|
||||
gh workflow run manual-api-changelog.yaml \
|
||||
-f from_version=1.29.0 \
|
||||
-f to_version=1.30.2 \
|
||||
-f create_pr=false
|
||||
|
||||
# Generate changelog and create PR
|
||||
gh workflow run manual-api-changelog.yaml \
|
||||
-f from_version=1.29.0 \
|
||||
-f to_version=1.30.2 \
|
||||
-f create_pr=true
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
||||
1. **Validates Inputs**: Checks that version formats are valid (X.Y.Z) and tags exist
|
||||
2. **Builds Both Versions**: Checks out each version tag, installs dependencies, and builds TypeScript types
|
||||
3. **Generates Snapshots**: Creates structured JSON snapshots of the public API surface for each version
|
||||
4. **Compares APIs**: Analyzes differences and categorizes as:
|
||||
- ⚠️ **Breaking changes** (removals, signature changes)
|
||||
- ✨ **Additions** (new interfaces, methods, properties)
|
||||
- 🔄 **Modifications** (non-breaking changes)
|
||||
5. **Uploads Artifact**: Saves the changelog and snapshots as a workflow artifact (90-day retention)
|
||||
6. **Creates PR** (optional): Generates a draft PR to update `docs/API-CHANGELOG.md`
|
||||
|
||||
## Output
|
||||
|
||||
### Workflow Artifacts
|
||||
|
||||
Every run produces an artifact containing:
|
||||
- `CHANGELOG-{from}-to-{to}.md` - Human-readable changelog
|
||||
- `from.json` - API snapshot of the earlier version
|
||||
- `to.json` - API snapshot of the later version
|
||||
|
||||
**Retention**: 90 days
|
||||
|
||||
### Pull Request (Optional)
|
||||
|
||||
If `create_pr` is enabled and changes are detected:
|
||||
- Creates a draft PR with title: `[docs] API Changelog: v{from} → v{to}`
|
||||
- Updates `docs/API-CHANGELOG.md` with the new changelog entry
|
||||
- Includes detailed metadata and review instructions
|
||||
- Labeled with `documentation`
|
||||
|
||||
## Example Changelog Output
|
||||
|
||||
```markdown
|
||||
## v1.30.2 (2025-11-04)
|
||||
|
||||
Comparing v1.29.0 → v1.30.2. This changelog documents changes to the public API surface.
|
||||
|
||||
### ✨ Additions
|
||||
|
||||
**Type Aliases**
|
||||
- `WorkflowId`
|
||||
|
||||
**Interfaces**
|
||||
- `ExtensionMetadata`
|
||||
- Members: `id`, `name`, `version`, `description`
|
||||
|
||||
### 🔄 Modifications
|
||||
|
||||
> **Note**: Some modifications may be breaking changes.
|
||||
|
||||
**Interfaces**
|
||||
- `ComfyApi`
|
||||
- ✨ Added member: `queuePromptAsync`
|
||||
- ✨ Added member: `cancelPrompt`
|
||||
- ⚠️ **Breaking**: Removed member: `queuePrompt`
|
||||
|
||||
**Enums**
|
||||
- `NodeStatus`
|
||||
- ✨ Added enum value: `ERROR`
|
||||
- ✨ Added enum value: `COMPLETED`
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Generate Changelog for Missed Releases
|
||||
|
||||
If the automatic workflow failed or was skipped for a release:
|
||||
|
||||
```bash
|
||||
gh workflow run manual-api-changelog.yaml \
|
||||
-f from_version=1.28.0 \
|
||||
-f to_version=1.29.0 \
|
||||
-f create_pr=true
|
||||
```
|
||||
|
||||
### 2. Compare Non-Adjacent Versions
|
||||
|
||||
To see cumulative changes across multiple releases:
|
||||
|
||||
```bash
|
||||
gh workflow run manual-api-changelog.yaml \
|
||||
-f from_version=1.25.0 \
|
||||
-f to_version=1.30.2 \
|
||||
-f create_pr=false
|
||||
```
|
||||
|
||||
### 3. Test Upcoming Changes
|
||||
|
||||
Compare current `main` branch against the latest release (requires creating a temporary tag):
|
||||
|
||||
```bash
|
||||
# Create temporary tag for current main
|
||||
git tag v1.31.0-preview
|
||||
git push origin v1.31.0-preview
|
||||
|
||||
# Run comparison
|
||||
gh workflow run manual-api-changelog.yaml \
|
||||
-f from_version=1.30.2 \
|
||||
-f to_version=1.31.0-preview \
|
||||
-f create_pr=false
|
||||
|
||||
# Clean up temporary tag
|
||||
git tag -d v1.31.0-preview
|
||||
git push origin :refs/tags/v1.31.0-preview
|
||||
```
|
||||
|
||||
### 4. Audit Historical Changes
|
||||
|
||||
Generate changelogs for documentation purposes:
|
||||
|
||||
```bash
|
||||
# Compare multiple version pairs
|
||||
for from in 1.26.0 1.27.0 1.28.0 1.29.0; do
|
||||
to=$(echo "$from" | awk -F. '{print $1"."$2+1".0"}')
|
||||
gh workflow run manual-api-changelog.yaml \
|
||||
-f from_version=$from \
|
||||
-f to_version=$to \
|
||||
-f create_pr=false
|
||||
done
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
The workflow validates:
|
||||
- ✅ Version format matches semantic versioning (X.Y.Z)
|
||||
- ✅ Both version tags exist in the repository
|
||||
- ✅ Tags reference valid commits with buildable code
|
||||
|
||||
If validation fails, the workflow exits early with a clear error message.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Tag requirement**: Both versions must have corresponding `vX.Y.Z` git tags
|
||||
- **Build requirement**: Both versions must have functional build processes
|
||||
- **Type files**: Requires `dist/index.d.ts` to exist after building
|
||||
- **Scripts**: Requires `scripts/snapshot-api.js` and `scripts/compare-api-snapshots.js` to be present
|
||||
|
||||
## Related Workflows
|
||||
|
||||
- **[Release API Changelogs](.github/workflows/release-api-changelogs.yaml)**: Automatic changelog generation triggered by NPM releases
|
||||
- **[Release NPM Types](.github/workflows/release-npm-types.yaml)**: Publishes type definitions and triggers automatic changelog
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Tag does not exist" error
|
||||
|
||||
Ensure the version exists as a git tag:
|
||||
|
||||
```bash
|
||||
git tag -l 'v*' | grep 1.29.0
|
||||
```
|
||||
|
||||
If missing, the version may not have been released yet.
|
||||
|
||||
### "Build failed" error
|
||||
|
||||
Check that the version can be built successfully:
|
||||
|
||||
```bash
|
||||
git checkout v1.29.0
|
||||
pnpm install
|
||||
pnpm build:types
|
||||
```
|
||||
|
||||
### No changes detected
|
||||
|
||||
If the workflow reports no changes but you expect some:
|
||||
1. Check the artifact snapshots to verify they're different
|
||||
2. Ensure you're comparing the correct versions
|
||||
3. Review the comparison script logic in `scripts/compare-api-snapshots.js`
|
||||
|
||||
### PR not created
|
||||
|
||||
PR creation requires:
|
||||
- `create_pr` input set to `true`
|
||||
- Significant changes detected (more than just headers)
|
||||
- `PR_GH_TOKEN` secret configured with appropriate permissions
|
||||
|
||||
## Security
|
||||
|
||||
- **Permissions**: Workflow requires `contents: write` and `pull-requests: write`
|
||||
- **Token**: Uses `secrets.PR_GH_TOKEN` for PR creation
|
||||
- **Isolation**: Each workflow run uses a unique concurrency group
|
||||
- **Artifacts**: Retained for 90 days, accessible to repository collaborators
|
||||
|
||||
## Monitoring
|
||||
|
||||
View workflow runs:
|
||||
```bash
|
||||
gh run list --workflow=manual-api-changelog.yaml
|
||||
```
|
||||
|
||||
View specific run details:
|
||||
```bash
|
||||
gh run view <run-id>
|
||||
```
|
||||
|
||||
Download artifacts:
|
||||
```bash
|
||||
gh run download <run-id>
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
# Description: When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||
name: 'Api: Update Electron API Types'
|
||||
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Description: When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||
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'
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
@@ -105,4 +105,4 @@ jobs:
|
||||
labels: Manager
|
||||
delete-branch: true
|
||||
add-paths: |
|
||||
src/types/generatedManagerTypes.ts
|
||||
src/types/generatedManagerTypes.ts
|
||||
@@ -1,5 +1,5 @@
|
||||
# Description: When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||
name: 'Api: Update Registry API Types'
|
||||
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
|
||||
4
.github/workflows/ci-json-validation.yaml
vendored
@@ -1,13 +1,11 @@
|
||||
# Description: Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq
|
||||
name: "CI: JSON Validation"
|
||||
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.json'
|
||||
|
||||
jobs:
|
||||
json-lint:
|
||||
|
||||
4
.github/workflows/ci-lint-format.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Linting and code formatting validation for pull requests
|
||||
name: "CI: Lint Format"
|
||||
description: "Linting and code formatting validation for pull requests"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit changes
|
||||
|
||||
4
.github/workflows/ci-python-validation.yaml
vendored
@@ -1,12 +1,12 @@
|
||||
# Description: Validates Python code in tools/devtools directory
|
||||
name: "CI: Python Validation"
|
||||
description: "Validates Python code in tools/devtools directory"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'tools/devtools/**'
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'tools/devtools/**'
|
||||
|
||||
|
||||
16
.github/workflows/ci-tests-e2e-forks.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Deploys test results from forked PRs (forks can't access deployment secrets)
|
||||
name: "CI: Tests E2E (Deploy for Forks)"
|
||||
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
deploy-and-comment-forked-pr:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.head_repository != null &&
|
||||
github.event.workflow_run.repository != null &&
|
||||
@@ -43,14 +43,14 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
});
|
||||
|
||||
|
||||
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
|
||||
|
||||
|
||||
if (!pr) {
|
||||
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
|
||||
return pr.number;
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
pattern: playwright-report-*
|
||||
path: reports
|
||||
|
||||
|
||||
- name: Handle Test Completion
|
||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||
env:
|
||||
@@ -85,9 +85,9 @@ jobs:
|
||||
# Rename merged report if exists
|
||||
[ -d "reports/playwright-report-chromium-merged" ] && \
|
||||
mv reports/playwright-report-chromium-merged reports/playwright-report-chromium
|
||||
|
||||
|
||||
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
||||
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||
"${{ steps.pr.outputs.result }}" \
|
||||
"${{ github.event.workflow_run.head_branch }}" \
|
||||
"completed"
|
||||
"completed"
|
||||
4
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages
|
||||
name: "CI: Tests E2E"
|
||||
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
with:
|
||||
include_build_step: true
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers
|
||||
uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers
|
||||
|
||||
# Save the entire workspace as cache for later test jobs to restore
|
||||
- name: Generate cache key
|
||||
|
||||
14
.github/workflows/ci-tests-storybook-forks.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Deploys Storybook previews from forked PRs (forks can't access deployment secrets)
|
||||
name: "CI: Tests Storybook (Deploy for Forks)"
|
||||
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
deploy-and-comment-forked-pr:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.head_repository != null &&
|
||||
github.event.workflow_run.repository != null &&
|
||||
@@ -43,14 +43,14 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
});
|
||||
|
||||
|
||||
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
|
||||
|
||||
|
||||
if (!pr) {
|
||||
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
|
||||
return pr.number;
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
name: storybook-static
|
||||
path: storybook-static
|
||||
|
||||
|
||||
- name: Handle Storybook Completion
|
||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||
env:
|
||||
@@ -88,4 +88,4 @@ jobs:
|
||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||
"${{ steps.pr.outputs.result }}" \
|
||||
"${{ github.event.workflow_run.head_branch }}" \
|
||||
"completed"
|
||||
"completed"
|
||||
31
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -1,9 +1,10 @@
|
||||
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
|
||||
name: "CI: Tests Storybook"
|
||||
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
# Post starting comment for non-forked PRs
|
||||
@@ -15,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
|
||||
- name: Post starting comment
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
@@ -88,7 +89,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Required for Chromatic baseline
|
||||
fetch-depth: 0 # Required for Chromatic baseline
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -110,9 +111,9 @@ jobs:
|
||||
with:
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
buildScriptName: build-storybook
|
||||
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
||||
exitOnceUploaded: true # Don't wait for UI tests to complete
|
||||
onlyChanged: true # Only capture changed stories
|
||||
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
||||
exitOnceUploaded: true # Don't wait for UI tests to complete
|
||||
onlyChanged: true # Only capture changed stories
|
||||
|
||||
- name: Set job status
|
||||
id: job-status
|
||||
@@ -137,17 +138,17 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
|
||||
- name: Download Storybook build
|
||||
if: needs.storybook-build.outputs.conclusion == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static
|
||||
|
||||
|
||||
- name: Make deployment script executable
|
||||
run: chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
||||
|
||||
|
||||
- name: Deploy Storybook and comment on PR
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
@@ -175,25 +176,25 @@ jobs:
|
||||
script: |
|
||||
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
||||
const storybookUrl = '${{ needs.chromatic-deployment.outputs.chromatic-storybook-url }}';
|
||||
|
||||
|
||||
// Find the existing Storybook comment
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: ${{ github.event.pull_request.number }}
|
||||
});
|
||||
|
||||
const storybookComment = comments.find(comment =>
|
||||
|
||||
const storybookComment = comments.find(comment =>
|
||||
comment.body.includes('<!-- STORYBOOK_BUILD_STATUS -->')
|
||||
);
|
||||
|
||||
|
||||
if (storybookComment && buildUrl && storybookUrl) {
|
||||
// Append Chromatic info to existing comment
|
||||
const updatedBody = storybookComment.body.replace(
|
||||
/---\n(.*)$/s,
|
||||
`---\n### 🎨 Chromatic Visual Tests\n- 📊 [View Chromatic Build](${buildUrl})\n- 📚 [View Chromatic Storybook](${storybookUrl})\n\n$1`
|
||||
);
|
||||
|
||||
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
|
||||
2
.github/workflows/ci-tests-unit.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Unit and component testing with Vitest
|
||||
name: "CI: Tests Unit"
|
||||
description: "Unit and component testing with Vitest"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
33
.github/workflows/ci-yaml-validation.yaml
vendored
@@ -1,33 +0,0 @@
|
||||
# Description: Validates YAML syntax and style using yamllint with relaxed rules
|
||||
name: "CI: YAML Validation"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**/*.yml'
|
||||
- '**/*.yaml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.yml'
|
||||
- '**/*.yaml'
|
||||
|
||||
jobs:
|
||||
yaml-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install yamllint
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install yamllint
|
||||
|
||||
- name: Validate YAML syntax and style
|
||||
run: ./scripts/cicd/check-yaml.sh
|
||||
69
.github/workflows/cloud-backport-tag.yaml
vendored
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: Cloud Backport Tag
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ['closed']
|
||||
branches: [cloud/*]
|
||||
|
||||
jobs:
|
||||
create-tag:
|
||||
if: >
|
||||
github.event.pull_request.merged == true &&
|
||||
contains(github.event.pull_request.labels.*.name, 'backport')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Create tag for cloud backport
|
||||
id: tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
BRANCH="${{ github.event.pull_request.base.ref }}"
|
||||
if [[ ! "$BRANCH" =~ ^cloud/([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "::error::Base branch '$BRANCH' is not a cloud/x.y branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAJOR="${BASH_REMATCH[1]}"
|
||||
MINOR="${BASH_REMATCH[2]}"
|
||||
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
if [[ "$VERSION" =~ ^${MAJOR}\.${MINOR}\.([0-9]+)(-.+)?$ ]]; then
|
||||
PATCH="${BASH_REMATCH[1]}"
|
||||
SUFFIX="${BASH_REMATCH[2]:-}"
|
||||
else
|
||||
echo "::error::Version '${VERSION}' does not match cloud branch '${BRANCH}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG="cloud/v${VERSION}"
|
||||
|
||||
if git ls-remote --tags origin "${TAG}" | grep -Fq "refs/tags/${TAG}"; then
|
||||
echo "::notice::Tag ${TAG} already exists; skipping"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git tag "${TAG}" "${{ github.event.pull_request.merge_commit_sha }}"
|
||||
git push origin "${TAG}"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "Created tag: ${TAG}"
|
||||
echo "Branch: ${BRANCH}"
|
||||
echo "Version: ${VERSION}"
|
||||
echo "Commit: ${{ github.event.pull_request.merge_commit_sha }}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
84
.github/workflows/i18n-update-core.yaml
vendored
@@ -1,12 +1,12 @@
|
||||
# Description: Generates and updates translations for core ComfyUI components using OpenAI
|
||||
name: "i18n: Update Core"
|
||||
description: "Generates and updates translations for core ComfyUI components using OpenAI"
|
||||
|
||||
on:
|
||||
# Manual dispatch for urgent translation updates
|
||||
# Manual dispatch for urgent translation updates
|
||||
workflow_dispatch:
|
||||
# Only trigger on PRs to main/master - additional branch filtering in job condition
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches: [ main ]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
@@ -15,45 +15,45 @@ jobs:
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: true
|
||||
- name: Setup ComfyUI Server
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
with:
|
||||
launch_server: true
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: true
|
||||
- name: Setup ComfyUI Server
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
with:
|
||||
launch_server: true
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: pnpm dev:electron &
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: pnpm dev:electron &
|
||||
|
||||
# Update locales, collect new strings and update translations using OpenAI, then commit changes
|
||||
- name: Update en.json
|
||||
run: pnpm collect-i18n
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
- name: Update translations
|
||||
run: pnpm locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Commit updated locales
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
git fetch origin ${{ github.head_ref }}
|
||||
# Stash any local changes before checkout
|
||||
git stash -u
|
||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||
# Apply the stashed changes if any
|
||||
git stash pop || true
|
||||
git add src/locales/
|
||||
git diff --staged --quiet || git commit -m "Update locales"
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
# Update locales, collect new strings and update translations using OpenAI, then commit changes
|
||||
- name: Update en.json
|
||||
run: pnpm collect-i18n
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
- name: Update translations
|
||||
run: pnpm locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Commit updated locales
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
git fetch origin ${{ github.head_ref }}
|
||||
# Stash any local changes before checkout
|
||||
git stash -u
|
||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||
# Apply the stashed changes if any
|
||||
git stash pop || true
|
||||
git add src/locales/
|
||||
git diff --staged --quiet || git commit -m "Update locales"
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
|
||||
204
.github/workflows/i18n-update-custom-nodes.yaml
vendored
@@ -21,116 +21,116 @@ jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Setup playwright environment with custom node repository
|
||||
- name: Setup ComfyUI Server (without launching)
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: 'true'
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
# Setup playwright environment with custom node repository
|
||||
- name: Setup ComfyUI Server (without launching)
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: 'true'
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
# Install the custom node repository
|
||||
- name: Checkout custom node repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
||||
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
||||
- name: Install custom node Python requirements
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
if [ -f "requirements.txt" ]; then
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
# Install the custom node repository
|
||||
- name: Checkout custom node repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
||||
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
||||
- name: Install custom node Python requirements
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
if [ -f "requirements.txt" ]; then
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
|
||||
# Start ComfyUI Server
|
||||
- name: Start ComfyUI Server
|
||||
shell: bash
|
||||
working-directory: ComfyUI
|
||||
run: |
|
||||
python main.py --cpu --multi-user --front-end-root ../dist --custom-node-path ../ComfyUI/custom_nodes/${{ inputs.repository }} &
|
||||
wait-for-it --service
|
||||
# Start ComfyUI Server
|
||||
- name: Start ComfyUI Server
|
||||
shell: bash
|
||||
working-directory: ComfyUI
|
||||
run: |
|
||||
python main.py --cpu --multi-user --front-end-root ../dist --custom-node-path ../ComfyUI/custom_nodes/${{ inputs.repository }} &
|
||||
wait-for-it --service
|
||||
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: pnpm dev:electron &
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: pnpm dev:electron &
|
||||
|
||||
- name: Capture base i18n
|
||||
run: pnpm exec tsx scripts/diff-i18n capture
|
||||
- name: Update en.json
|
||||
run: pnpm collect-i18n
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
- name: Update translations
|
||||
run: pnpm locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Diff base vs updated i18n
|
||||
run: pnpm exec tsx scripts/diff-i18n diff
|
||||
- name: Update i18n in custom node repository
|
||||
run: |
|
||||
LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/
|
||||
install -d "$LOCALE_DIR"
|
||||
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
|
||||
|
||||
# Git ops for pushing changes and creating PR
|
||||
- name: Check and create fork of custom node repository
|
||||
run: |
|
||||
# Try to fork the repository
|
||||
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
|
||||
echo "Fork failed - repository might already be forked"
|
||||
# Exit 0 to prevent the workflow from failing
|
||||
exit 0
|
||||
}
|
||||
|
||||
- name: Capture base i18n
|
||||
run: pnpm exec tsx scripts/diff-i18n capture
|
||||
- name: Update en.json
|
||||
run: pnpm collect-i18n
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
- name: Update translations
|
||||
run: pnpm locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Diff base vs updated i18n
|
||||
run: pnpm exec tsx scripts/diff-i18n diff
|
||||
- name: Update i18n in custom node repository
|
||||
run: |
|
||||
LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/
|
||||
install -d "$LOCALE_DIR"
|
||||
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
|
||||
# Enable workflows on the forked repository
|
||||
gh api \
|
||||
--method PUT \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
|
||||
-F can_approve_pull_request_reviews=true \
|
||||
-F default_workflow_permissions="write" \
|
||||
-F enabled=true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
# Git ops for pushing changes and creating PR
|
||||
- name: Check and create fork of custom node repository
|
||||
run: |
|
||||
# Try to fork the repository
|
||||
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
|
||||
echo "Fork failed - repository might already be forked"
|
||||
# Exit 0 to prevent the workflow from failing
|
||||
exit 0
|
||||
}
|
||||
- name: Commit changes
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
|
||||
# Enable workflows on the forked repository
|
||||
gh api \
|
||||
--method PUT \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
|
||||
-F can_approve_pull_request_reviews=true \
|
||||
-F default_workflow_permissions="write" \
|
||||
-F enabled=true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
# Create and switch to new branch
|
||||
git checkout -b update-locales
|
||||
|
||||
- name: Commit changes
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
# Stage and commit changes
|
||||
git add -A
|
||||
git commit -m "Update locales"
|
||||
|
||||
# Create and switch to new branch
|
||||
git checkout -b update-locales
|
||||
- name: Install SSH key For PUSH
|
||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
|
||||
with:
|
||||
# PR private key from action server
|
||||
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
||||
# github public key to confirm it's github server
|
||||
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||
|
||||
# Stage and commit changes
|
||||
git add -A
|
||||
git commit -m "Update locales"
|
||||
- name: Push changes
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
# Force push to create the branch
|
||||
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
|
||||
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
|
||||
|
||||
- name: Install SSH key For PUSH
|
||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
|
||||
with:
|
||||
# PR private key from action server
|
||||
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
||||
# github public key to confirm it's github server
|
||||
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||
|
||||
- name: Push changes
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
# Force push to create the branch
|
||||
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
|
||||
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
|
||||
|
||||
- name: Create PR
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
# Create PR using gh cli
|
||||
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
- name: Create PR
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
# Create PR using gh cli
|
||||
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
74
.github/workflows/i18n-update-nodes.yaml
vendored
@@ -13,42 +13,42 @@ jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Server (and start)
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
with:
|
||||
launch_server: true
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: true
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Server (and start)
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
with:
|
||||
launch_server: true
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
include_build_step: true
|
||||
- name: Setup Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: pnpm dev:electron &
|
||||
- name: Update en.json
|
||||
run: pnpm collect-i18n -- scripts/collect-i18n-node-defs.ts
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
- name: Update translations
|
||||
run: pnpm locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: "Update locales for node definitions"
|
||||
title: "Update locales for node definitions"
|
||||
body: |
|
||||
Automated PR to update locales for node definitions
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: pnpm dev:electron &
|
||||
- name: Update en.json
|
||||
run: pnpm collect-i18n -- scripts/collect-i18n-node-defs.ts
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
- name: Update translations
|
||||
run: pnpm locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: "Update locales for node definitions"
|
||||
title: "Update locales for node definitions"
|
||||
body: |
|
||||
Automated PR to update locales for node definitions
|
||||
|
||||
This PR was created automatically by the frontend update workflow.
|
||||
branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }}
|
||||
base: main
|
||||
labels: dependencies
|
||||
This PR was created automatically by the frontend update workflow.
|
||||
branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }}
|
||||
base: main
|
||||
labels: dependencies
|
||||
|
||||
255
.github/workflows/manual-api-changelog.yaml
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
name: Manual API Changelog Generation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
from_version:
|
||||
description: 'Previous version (e.g., 1.29.0 or v1.29.0)'
|
||||
required: true
|
||||
type: string
|
||||
to_version:
|
||||
description: 'Current version (e.g., 1.30.2 or v1.30.2)'
|
||||
required: true
|
||||
type: string
|
||||
create_pr:
|
||||
description: 'Create a pull request with the changelog'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: manual-api-changelog-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
generate_changelog:
|
||||
name: Generate API Changelog
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for comparing versions
|
||||
|
||||
- name: Validate version inputs
|
||||
id: validate_versions
|
||||
run: |
|
||||
# Normalize version strings (remove 'v' prefix if present)
|
||||
FROM_VERSION="${{ github.event.inputs.from_version }}"
|
||||
TO_VERSION="${{ github.event.inputs.to_version }}"
|
||||
|
||||
FROM_VERSION=${FROM_VERSION#v}
|
||||
TO_VERSION=${TO_VERSION#v}
|
||||
|
||||
# Validate version format (semantic versioning)
|
||||
if ! [[ "$FROM_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Invalid from_version format: $FROM_VERSION"
|
||||
echo "Expected format: X.Y.Z (e.g., 1.29.0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$TO_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Invalid to_version format: $TO_VERSION"
|
||||
echo "Expected format: X.Y.Z (e.g., 1.30.2)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tags exist
|
||||
if ! git rev-parse "v$FROM_VERSION" >/dev/null 2>&1; then
|
||||
echo "Error: Tag v$FROM_VERSION does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git rev-parse "v$TO_VERSION" >/dev/null 2>&1; then
|
||||
echo "Error: Tag v$TO_VERSION does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "from_version=$FROM_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "to_version=$TO_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "from_tag=v$FROM_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "to_tag=v$TO_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ Validated versions: v$FROM_VERSION → v$TO_VERSION"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
|
||||
- name: Create snapshots directory
|
||||
run: mkdir -p .api-snapshots
|
||||
|
||||
- name: Preserve scripts
|
||||
run: |
|
||||
# Copy scripts to temporary location
|
||||
mkdir -p /tmp/api-changelog-scripts
|
||||
cp scripts/snapshot-api.js scripts/compare-api-snapshots.js /tmp/api-changelog-scripts/
|
||||
|
||||
- name: Build and snapshot TO version
|
||||
run: |
|
||||
echo "Building types for v${{ steps.validate_versions.outputs.to_version }}"
|
||||
git checkout ${{ steps.validate_versions.outputs.to_tag }}
|
||||
|
||||
# Restore scripts
|
||||
mkdir -p scripts
|
||||
cp /tmp/api-changelog-scripts/*.js scripts/
|
||||
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build:types
|
||||
|
||||
# Generate snapshot
|
||||
node scripts/snapshot-api.js dist/index.d.ts > /tmp/api-snapshots-to.json
|
||||
|
||||
echo "✅ Created snapshot for v${{ steps.validate_versions.outputs.to_version }}"
|
||||
|
||||
- name: Build and snapshot FROM version
|
||||
run: |
|
||||
echo "Building types for v${{ steps.validate_versions.outputs.from_version }}"
|
||||
git checkout ${{ steps.validate_versions.outputs.from_tag }}
|
||||
|
||||
# Restore scripts
|
||||
mkdir -p scripts
|
||||
cp /tmp/api-changelog-scripts/*.js scripts/
|
||||
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build:types
|
||||
|
||||
# Generate snapshot
|
||||
node scripts/snapshot-api.js dist/index.d.ts > /tmp/api-snapshots-from.json
|
||||
|
||||
echo "✅ Created snapshot for v${{ steps.validate_versions.outputs.from_version }}"
|
||||
|
||||
- name: Return to original branch
|
||||
run: |
|
||||
git checkout ${{ github.ref_name }}
|
||||
|
||||
# Restore scripts
|
||||
mkdir -p scripts
|
||||
cp /tmp/api-changelog-scripts/*.js scripts/
|
||||
|
||||
# Copy snapshots to working directory
|
||||
cp /tmp/api-snapshots-from.json .api-snapshots/from.json
|
||||
cp /tmp/api-snapshots-to.json .api-snapshots/to.json
|
||||
|
||||
- name: Compare API snapshots and generate changelog
|
||||
id: generate_changelog
|
||||
run: |
|
||||
# Get git ref for TO version
|
||||
GIT_REF=$(git rev-parse ${{ steps.validate_versions.outputs.to_tag }})
|
||||
|
||||
# Run the comparison script
|
||||
CHANGELOG_OUTPUT=$(node scripts/compare-api-snapshots.js \
|
||||
.api-snapshots/from.json \
|
||||
.api-snapshots/to.json \
|
||||
${{ steps.validate_versions.outputs.from_version }} \
|
||||
${{ steps.validate_versions.outputs.to_version }} \
|
||||
Comfy-Org \
|
||||
ComfyUI_frontend \
|
||||
"$GIT_REF")
|
||||
|
||||
# Save changelog to file for artifact
|
||||
echo "$CHANGELOG_OUTPUT" > .api-snapshots/CHANGELOG-${{ steps.validate_versions.outputs.from_version }}-to-${{ steps.validate_versions.outputs.to_version }}.md
|
||||
|
||||
# Also output to step summary
|
||||
echo "## 📊 Generated API Changelog" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CHANGELOG_OUTPUT" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check if changelog is empty or just header
|
||||
if [ $(echo "$CHANGELOG_OUTPUT" | wc -l) -lt 5 ]; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ No significant API changes detected" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ API changes detected and documented" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "✅ Changelog generated successfully"
|
||||
|
||||
- name: Upload changelog artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: api-changelog-v${{ steps.validate_versions.outputs.from_version }}-to-v${{ steps.validate_versions.outputs.to_version }}
|
||||
path: |
|
||||
.api-snapshots/CHANGELOG-*.md
|
||||
.api-snapshots/from.json
|
||||
.api-snapshots/to.json
|
||||
retention-days: 90
|
||||
|
||||
- name: Create Pull Request
|
||||
if: github.event.inputs.create_pr == 'true' && steps.generate_changelog.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[docs] Update API changelog for v${{ steps.validate_versions.outputs.from_version }} → v${{ steps.validate_versions.outputs.to_version }}'
|
||||
title: '[docs] API Changelog: v${{ steps.validate_versions.outputs.from_version }} → v${{ steps.validate_versions.outputs.to_version }}'
|
||||
body: |
|
||||
## API Changelog Update (Manual)
|
||||
|
||||
This PR documents public API changes between v${{ steps.validate_versions.outputs.from_version }} and v${{ steps.validate_versions.outputs.to_version }}.
|
||||
|
||||
The changelog has been manually generated by comparing TypeScript type definitions between versions.
|
||||
|
||||
### Version Comparison
|
||||
- **From:** v${{ steps.validate_versions.outputs.from_version }}
|
||||
- **To:** v${{ steps.validate_versions.outputs.to_version }}
|
||||
- **Requested by:** @${{ github.actor }}
|
||||
- **Workflow run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
### Review Instructions
|
||||
- Review the changes in `docs/API-CHANGELOG.md`
|
||||
- Verify accuracy of breaking changes
|
||||
- Add any additional context or migration notes if needed
|
||||
- Merge when ready to publish changelog
|
||||
|
||||
### Artifacts
|
||||
The full changelog and snapshots are available as workflow artifacts for 90 days.
|
||||
|
||||
---
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
branch: api-changelog-manual-${{ steps.validate_versions.outputs.from_version }}-to-${{ steps.validate_versions.outputs.to_version }}
|
||||
base: ${{ github.ref_name }}
|
||||
labels: documentation
|
||||
delete-branch: true
|
||||
draft: true
|
||||
add-paths: |
|
||||
docs/API-CHANGELOG.md
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## 🎉 Workflow Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **From version:** v${{ steps.validate_versions.outputs.from_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **To version:** v${{ steps.validate_versions.outputs.to_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Changes detected:** ${{ steps.generate_changelog.outputs.has_changes }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Create PR:** ${{ github.event.inputs.create_pr }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### 📦 Artifact" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The generated changelog and API snapshots have been uploaded as artifacts." >> $GITHUB_STEP_SUMMARY
|
||||
echo "Retention: 90 days" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ github.event.inputs.create_pr }}" == "true" ] && [ "${{ steps.generate_changelog.outputs.has_changes }}" == "true" ]; then
|
||||
echo "### 🔀 Pull Request" >> $GITHUB_STEP_SUMMARY
|
||||
echo "A draft pull request has been created with the changelog updates." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ github.event.inputs.create_pr }}" == "true" ]; then
|
||||
echo "### ℹ️ No PR Created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No significant changes were detected, so no PR was created." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "### ℹ️ PR Creation Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Pull request creation was not requested. Enable 'Create PR' option to automatically create a PR." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
102
.github/workflows/pr-backport.yaml
vendored
@@ -19,8 +19,8 @@ on:
|
||||
jobs:
|
||||
backport:
|
||||
if: >
|
||||
(github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
(github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
contains(github.event.pull_request.labels.*.name, 'needs-backport')) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
@@ -38,19 +38,19 @@ jobs:
|
||||
echo "::error::Invalid PR number format. Must be a positive integer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Validate PR exists and is merged
|
||||
if ! gh pr view "${{ inputs.pr_number }}" --json merged >/dev/null 2>&1; then
|
||||
echo "::error::PR #${{ inputs.pr_number }} not found or inaccessible."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
MERGED=$(gh pr view "${{ inputs.pr_number }}" --json merged --jq '.merged')
|
||||
if [ "$MERGED" != "true" ]; then
|
||||
echo "::error::PR #${{ inputs.pr_number }} is not merged. Only merged PRs can be backported."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Validate PR has needs-backport label
|
||||
if ! gh pr view "${{ inputs.pr_number }}" --json labels --jq '.labels[].name' | grep -q "needs-backport"; then
|
||||
echo "::error::PR #${{ inputs.pr_number }} does not have 'needs-backport' label."
|
||||
@@ -78,7 +78,8 @@ jobs:
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
||||
else
|
||||
LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
||||
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
||||
fi
|
||||
|
||||
add_target() {
|
||||
@@ -163,7 +164,6 @@ jobs:
|
||||
|
||||
PENDING=()
|
||||
SKIPPED=()
|
||||
REUSED=()
|
||||
|
||||
for target in $REQUESTED_TARGETS; do
|
||||
SAFE_TARGET=$(echo "$target" | tr '/' '-')
|
||||
@@ -176,22 +176,10 @@ jobs:
|
||||
|
||||
if printf '%s\n' "${EXISTING_BRANCHES[@]:-}" |
|
||||
grep -Fq "refs/heads/${BACKPORT_BRANCH}"; then
|
||||
OPEN_PR=$(
|
||||
gh pr list \
|
||||
--state open \
|
||||
--head "${BACKPORT_BRANCH}" \
|
||||
--json number \
|
||||
--jq 'if length > 0 then .[0].number else "" end'
|
||||
)
|
||||
if [ -n "$OPEN_PR" ]; then
|
||||
SKIPPED+=("${target} (PR #${OPEN_PR})")
|
||||
continue
|
||||
fi
|
||||
|
||||
REUSED+=("$BACKPORT_BRANCH")
|
||||
SKIPPED+=("$target")
|
||||
else
|
||||
PENDING+=("$target")
|
||||
fi
|
||||
|
||||
PENDING+=("$target")
|
||||
done
|
||||
|
||||
SKIPPED_JOINED="${SKIPPED[*]:-}"
|
||||
@@ -199,20 +187,16 @@ jobs:
|
||||
|
||||
echo "already-exists=${SKIPPED_JOINED}" >> $GITHUB_OUTPUT
|
||||
echo "pending-targets=${PENDING_JOINED}" >> $GITHUB_OUTPUT
|
||||
echo "reused-branches=${REUSED[*]:-}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -z "$PENDING_JOINED" ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
if [ -n "$SKIPPED_JOINED" ]; then
|
||||
echo "::warning::Backport branches exist: ${SKIPPED_JOINED}"
|
||||
echo "::warning::Backport branches already exist for: ${SKIPPED_JOINED}"
|
||||
fi
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
if [ -n "$SKIPPED_JOINED" ]; then
|
||||
echo "::notice::Skipping backport targets: ${SKIPPED_JOINED}"
|
||||
fi
|
||||
if [ "${#REUSED[@]}" -gt 0 ]; then
|
||||
echo "::notice::Reusing backport branches: ${REUSED[*]}"
|
||||
echo "::notice::Skipping already backported targets: ${SKIPPED_JOINED}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -224,32 +208,21 @@ jobs:
|
||||
run: |
|
||||
FAILED=""
|
||||
SUCCESS=""
|
||||
|
||||
CREATED_BRANCHES_FILE="$(
|
||||
mktemp "$RUNNER_TEMP/backport-branches-XXXXXX"
|
||||
)"
|
||||
echo "CREATED_BRANCHES_FILE=$CREATED_BRANCHES_FILE" >> "$GITHUB_ENV"
|
||||
|
||||
|
||||
# Get PR data for manual triggers
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit)
|
||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
||||
else
|
||||
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
|
||||
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
fi
|
||||
|
||||
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
|
||||
TARGET_BRANCH="${target}"
|
||||
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
|
||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
||||
REMOTE_BACKPORT_EXISTS=false
|
||||
|
||||
if git ls-remote --exit-code origin "${BACKPORT_BRANCH}" >/dev/null 2>&1; then
|
||||
REMOTE_BACKPORT_EXISTS=true
|
||||
echo "::notice::Updating existing branch ${BACKPORT_BRANCH}"
|
||||
fi
|
||||
|
||||
echo "::group::Backporting to ${TARGET_BRANCH}"
|
||||
|
||||
@@ -274,12 +247,7 @@ jobs:
|
||||
|
||||
# Try cherry-pick
|
||||
if git cherry-pick "${MERGE_COMMIT}"; then
|
||||
if [ "$REMOTE_BACKPORT_EXISTS" = true ]; then
|
||||
git push --force-with-lease origin "${BACKPORT_BRANCH}"
|
||||
else
|
||||
git push origin "${BACKPORT_BRANCH}"
|
||||
fi
|
||||
echo "${BACKPORT_BRANCH}" >> "$CREATED_BRANCHES_FILE"
|
||||
git push origin "${BACKPORT_BRANCH}"
|
||||
SUCCESS="${SUCCESS}${TARGET_BRANCH}:${BACKPORT_BRANCH} "
|
||||
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
|
||||
# Return to main (keep the branch, we need it for PR)
|
||||
@@ -303,13 +271,6 @@ jobs:
|
||||
echo "success=${SUCCESS}" >> $GITHUB_OUTPUT
|
||||
echo "failed=${FAILED}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -s "$CREATED_BRANCHES_FILE" ]; then
|
||||
CREATED_LIST=$(paste -sd' ' "$CREATED_BRANCHES_FILE")
|
||||
echo "created-branches=${CREATED_LIST}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "created-branches=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [ -n "${FAILED}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -326,10 +287,10 @@ jobs:
|
||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
||||
else
|
||||
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
|
||||
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
||||
fi
|
||||
|
||||
|
||||
for backport in ${{ steps.backport.outputs.success }}; do
|
||||
IFS=':' read -r target branch <<< "${backport}"
|
||||
|
||||
@@ -364,9 +325,9 @@ jobs:
|
||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
||||
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
||||
else
|
||||
PR_NUMBER=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")
|
||||
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
|
||||
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
fi
|
||||
|
||||
for failure in ${{ steps.backport.outputs.failed }}; do
|
||||
@@ -387,25 +348,6 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Cleanup stranded backport branches
|
||||
if: steps.filter-targets.outputs.skip != 'true' && failure()
|
||||
run: |
|
||||
FILE="${CREATED_BRANCHES_FILE:-}"
|
||||
|
||||
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
|
||||
echo "No backport branches recorded for cleanup"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while IFS= read -r branch; do
|
||||
[ -z "$branch" ] && continue
|
||||
printf 'Deleting branch %s\n' "${branch}"
|
||||
if ! git push origin --delete "$branch"; then
|
||||
echo "::warning::Failed to delete ${branch}"
|
||||
fi
|
||||
done < "$FILE"
|
||||
|
||||
|
||||
- name: Remove needs-backport label
|
||||
if: steps.filter-targets.outputs.skip != 'true' && success()
|
||||
run: gh pr edit ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} --remove-label "needs-backport"
|
||||
|
||||
2
.github/workflows/pr-claude-review.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: AI-powered code review triggered by adding the 'claude-review' label to a PR
|
||||
name: "PR: Claude Review"
|
||||
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -127,26 +127,26 @@ jobs:
|
||||
echo "=========================================="
|
||||
echo "STAGING CHANGED SNAPSHOTS (Shard ${{ matrix.shardIndex }})"
|
||||
echo "=========================================="
|
||||
|
||||
|
||||
# Get list of changed snapshot files
|
||||
changed_files=$(git diff --name-only browser_tests/ 2>/dev/null | grep -E '\-snapshots/' || echo "")
|
||||
|
||||
|
||||
if [ -z "$changed_files" ]; then
|
||||
echo "No snapshot changes in this shard"
|
||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
echo "✓ Found changed files:"
|
||||
echo "$changed_files"
|
||||
file_count=$(echo "$changed_files" | wc -l)
|
||||
echo "Count: $file_count"
|
||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
||||
echo ""
|
||||
|
||||
|
||||
# Create staging directory
|
||||
mkdir -p /tmp/changed_snapshots_shard
|
||||
|
||||
|
||||
# Copy only changed files, preserving directory structure
|
||||
# Strip 'browser_tests/' prefix to avoid double nesting
|
||||
echo "Copying changed files to staging directory..."
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
cp "$file" "/tmp/changed_snapshots_shard/$file_without_prefix"
|
||||
echo " → $file_without_prefix"
|
||||
done <<< "$changed_files"
|
||||
|
||||
|
||||
echo ""
|
||||
echo "Staged files for upload:"
|
||||
find /tmp/changed_snapshots_shard -type f
|
||||
@@ -233,18 +233,18 @@ jobs:
|
||||
|
||||
shard_name=$(basename "$shard_dir")
|
||||
file_count=$(find "$shard_dir" -type f | wc -l)
|
||||
|
||||
|
||||
if [ "$file_count" -eq 0 ]; then
|
||||
echo " $shard_name: no files"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Processing $shard_name ($file_count file(s))..."
|
||||
|
||||
|
||||
# Copy files directly, preserving directory structure
|
||||
# Since files are already in correct structure (no browser_tests/ prefix), just copy them all
|
||||
cp -v -r "$shard_dir"* browser_tests/ 2>&1 | sed 's/^/ /'
|
||||
|
||||
|
||||
merged_count=$((merged_count + 1))
|
||||
echo " ✓ Merged"
|
||||
echo ""
|
||||
@@ -272,25 +272,25 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
|
||||
|
||||
if git diff --quiet browser_tests/; then
|
||||
echo "No changes to commit"
|
||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
echo "=========================================="
|
||||
echo "COMMITTING CHANGES"
|
||||
echo "=========================================="
|
||||
|
||||
|
||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
git add browser_tests/
|
||||
git commit -m "[automated] Update test expectations"
|
||||
|
||||
|
||||
echo "Pushing to ${{ needs.setup.outputs.branch }}..."
|
||||
git push origin ${{ needs.setup.outputs.branch }}
|
||||
|
||||
|
||||
echo "✓ Commit and push successful"
|
||||
|
||||
- name: Add Done Reaction
|
||||
@@ -306,4 +306,4 @@ jobs:
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
run: gh pr edit ${{ needs.setup.outputs.pr-number }} --remove-label "New Browser Test Expectations"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
203
.github/workflows/release-api-changelogs.yaml
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
name: Release API Changelogs
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Release NPM Types']
|
||||
types:
|
||||
- completed
|
||||
push:
|
||||
branches:
|
||||
- sno-api-changelog
|
||||
|
||||
concurrency:
|
||||
group: release-api-changelogs-${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
generate_changelog:
|
||||
name: Generate API Changelog
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on successful completion of the Release NPM Types workflow or on push to sno-api-changelog
|
||||
if: ${{ github.event_name == 'push' || github.event.workflow_run.conclusion == 'success' }}
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for comparing versions
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
|
||||
- name: Get current version
|
||||
id: current_version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Current version: $VERSION"
|
||||
|
||||
- name: Get previous version
|
||||
id: previous_version
|
||||
run: |
|
||||
# Get the two most recent version tags sorted
|
||||
CURRENT_VERSION="${{ steps.current_version.outputs.version }}"
|
||||
TAGS=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -2)
|
||||
|
||||
# Find the previous version tag (skip current if it exists)
|
||||
PREVIOUS_TAG=""
|
||||
for tag in $TAGS; do
|
||||
TAG_VERSION=${tag#v}
|
||||
if [ "$TAG_VERSION" != "$CURRENT_VERSION" ]; then
|
||||
PREVIOUS_TAG=$tag
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$PREVIOUS_TAG" ]; then
|
||||
echo "No previous version found, this may be the first release"
|
||||
echo "version=" >> $GITHUB_OUTPUT
|
||||
echo "tag=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "version=${PREVIOUS_TAG#v}" >> $GITHUB_OUTPUT
|
||||
echo "tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
|
||||
echo "Previous version: ${PREVIOUS_TAG#v}"
|
||||
fi
|
||||
|
||||
- name: Build current types
|
||||
run: pnpm build:types
|
||||
|
||||
- name: Snapshot current API
|
||||
id: current_snapshot
|
||||
run: |
|
||||
# Create snapshots directory
|
||||
mkdir -p .api-snapshots
|
||||
|
||||
# Generate snapshot of current types
|
||||
node scripts/snapshot-api.js dist/index.d.ts > .api-snapshots/current.json
|
||||
|
||||
echo "Current API snapshot created"
|
||||
|
||||
- name: Preserve scripts for previous version
|
||||
if: steps.previous_version.outputs.tag != ''
|
||||
run: |
|
||||
# Copy scripts to temporary location to use with previous version
|
||||
mkdir -p /tmp/api-changelog-scripts
|
||||
cp scripts/snapshot-api.js scripts/compare-api-snapshots.js /tmp/api-changelog-scripts/
|
||||
|
||||
- name: Checkout previous version
|
||||
if: steps.previous_version.outputs.tag != ''
|
||||
run: |
|
||||
# Stash current changes
|
||||
git stash
|
||||
|
||||
# Checkout previous version
|
||||
git checkout ${{ steps.previous_version.outputs.tag }}
|
||||
|
||||
# Restore scripts
|
||||
mkdir -p scripts
|
||||
cp /tmp/api-changelog-scripts/*.js scripts/
|
||||
|
||||
- name: Build previous types
|
||||
if: steps.previous_version.outputs.tag != ''
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build:types
|
||||
|
||||
- name: Snapshot previous API
|
||||
if: steps.previous_version.outputs.tag != ''
|
||||
run: |
|
||||
# Generate snapshot of previous types
|
||||
node scripts/snapshot-api.js dist/index.d.ts > .api-snapshots/previous.json
|
||||
|
||||
echo "Previous API snapshot created"
|
||||
|
||||
- name: Return to current version
|
||||
if: steps.previous_version.outputs.tag != ''
|
||||
run: |
|
||||
# Remove copied scripts to avoid conflicts
|
||||
rm -f scripts/snapshot-api.js scripts/compare-api-snapshots.js
|
||||
|
||||
git checkout -
|
||||
git stash pop || true
|
||||
|
||||
- name: Compare API snapshots and generate changelog
|
||||
id: generate_changelog
|
||||
run: |
|
||||
# Create docs directory if it doesn't exist
|
||||
mkdir -p docs
|
||||
|
||||
# Get current git ref (commit SHA)
|
||||
GIT_REF=$(git rev-parse HEAD)
|
||||
|
||||
# Run the comparison script
|
||||
if [ -f .api-snapshots/previous.json ]; then
|
||||
node scripts/compare-api-snapshots.js \
|
||||
.api-snapshots/previous.json \
|
||||
.api-snapshots/current.json \
|
||||
${{ steps.previous_version.outputs.version }} \
|
||||
${{ steps.current_version.outputs.version }} \
|
||||
Comfy-Org \
|
||||
ComfyUI_frontend \
|
||||
"$GIT_REF" \
|
||||
>> docs/API-CHANGELOG.md
|
||||
else
|
||||
# First release - just document the initial API surface
|
||||
echo "## v${{ steps.current_version.outputs.version }} ($(date +%Y-%m-%d))" >> docs/API-CHANGELOG.md
|
||||
echo "" >> docs/API-CHANGELOG.md
|
||||
echo "Initial API release." >> docs/API-CHANGELOG.md
|
||||
echo "" >> docs/API-CHANGELOG.md
|
||||
fi
|
||||
|
||||
# Check if there are any changes
|
||||
if git diff --quiet docs/API-CHANGELOG.md; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "No API changes detected"
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "API changes detected"
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.generate_changelog.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[docs] Update API changelog for v${{ steps.current_version.outputs.version }}'
|
||||
title: '[docs] API Changelog for v${{ steps.current_version.outputs.version }}'
|
||||
body: |
|
||||
## API Changelog Update
|
||||
|
||||
This PR documents public API changes between v${{ steps.previous_version.outputs.version }} and v${{ steps.current_version.outputs.version }}.
|
||||
|
||||
The changelog has been automatically generated by comparing TypeScript type definitions between versions.
|
||||
|
||||
### Review Instructions
|
||||
- Review the changes in `docs/API-CHANGELOG.md`
|
||||
- Verify accuracy of breaking changes
|
||||
- Add any additional context or migration notes if needed
|
||||
- Merge when ready to publish changelog
|
||||
|
||||
---
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
branch: api-changelog-v${{ steps.current_version.outputs.version }}
|
||||
base: ${{ github.event_name == 'push' && github.ref_name || 'main' }}
|
||||
labels: documentation
|
||||
delete-branch: true
|
||||
draft: true
|
||||
add-paths: |
|
||||
docs/API-CHANGELOG.md
|
||||
76
.github/workflows/release-branch-create.yaml
vendored
@@ -148,83 +148,13 @@ jobs:
|
||||
done
|
||||
|
||||
{
|
||||
echo "results<<EOF"
|
||||
echo "results<<'EOF'"
|
||||
cat "$RESULTS_FILE"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Ensure release labels
|
||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
BRANCH_BASE="${{ steps.check_version.outputs.branch_base }}"
|
||||
|
||||
if [[ -z "$BRANCH_BASE" ]]; then
|
||||
echo "::error::Branch base not set; unable to manage labels"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
declare -A COLORS=(
|
||||
[core]="4361ee"
|
||||
[cloud]="4f6ef5"
|
||||
)
|
||||
|
||||
for PREFIX in core cloud; do
|
||||
LABEL="${PREFIX}/${BRANCH_BASE}"
|
||||
COLOR="${COLORS[$PREFIX]}"
|
||||
DESCRIPTION="Backport PRs for ${PREFIX} ${BRANCH_BASE}"
|
||||
|
||||
if gh label view "$LABEL" >/dev/null 2>&1; then
|
||||
gh label edit "$LABEL" \
|
||||
--color "$COLOR" \
|
||||
--description "$DESCRIPTION"
|
||||
echo "🔄 Updated label $LABEL"
|
||||
else
|
||||
gh label create "$LABEL" \
|
||||
--color "$COLOR" \
|
||||
--description "$DESCRIPTION"
|
||||
echo "✨ Created label $LABEL"
|
||||
fi
|
||||
done
|
||||
|
||||
MIN_LABELS_TO_KEEP=3
|
||||
MAX_LABELS_TO_FETCH=200
|
||||
|
||||
for PREFIX in core cloud; do
|
||||
mapfile -t LABELS < <(
|
||||
gh label list \
|
||||
--json name \
|
||||
--limit "$MAX_LABELS_TO_FETCH" \
|
||||
--jq '.[].name' |
|
||||
grep -E "^${PREFIX}/[0-9]+\.[0-9]+$" |
|
||||
sort -t/ -k2,2V
|
||||
)
|
||||
|
||||
TOTAL=${#LABELS[@]}
|
||||
|
||||
if (( TOTAL <= MIN_LABELS_TO_KEEP )); then
|
||||
echo "ℹ️ Nothing to prune for $PREFIX labels"
|
||||
continue
|
||||
fi
|
||||
|
||||
REMOVE_COUNT=$((TOTAL - MIN_LABELS_TO_KEEP))
|
||||
|
||||
if (( REMOVE_COUNT > 1 )); then
|
||||
REMOVE_COUNT=1
|
||||
fi
|
||||
|
||||
for ((i=0; i<REMOVE_COUNT; i++)); do
|
||||
OLD_LABEL="${LABELS[$i]}"
|
||||
gh label delete "$OLD_LABEL" --yes
|
||||
echo "🗑️ Removed old label $OLD_LABEL"
|
||||
done
|
||||
done
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post summary
|
||||
if: always() && steps.check_version.outputs.is_minor_bump == 'true'
|
||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||
run: |
|
||||
CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}"
|
||||
RESULTS="${{ steps.create_branches.outputs.results }}"
|
||||
|
||||
2
.github/workflows/release-version-bump.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Manual workflow to increment package version with semantic versioning support
|
||||
name: "Release: Version Bump"
|
||||
description: "Manual workflow to increment package version with semantic versioning support"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
292
.github/workflows/release-weekly-comfyui.yaml
vendored
@@ -1,292 +0,0 @@
|
||||
# Automated weekly workflow to bump ComfyUI frontend RC releases
|
||||
name: "Release: Weekly ComfyUI"
|
||||
|
||||
on:
|
||||
# Schedule for Monday at 12:00 PM PST (20:00 UTC)
|
||||
schedule:
|
||||
- cron: '0 20 * * 1'
|
||||
|
||||
# Allow manual triggering
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
comfyui_fork:
|
||||
description: 'ComfyUI fork to use for PR (e.g., Comfy-Org/ComfyUI)'
|
||||
required: false
|
||||
default: 'Comfy-Org/ComfyUI'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
resolve-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
current_version: ${{ steps.resolve.outputs.current_version }}
|
||||
target_version: ${{ steps.resolve.outputs.target_version }}
|
||||
target_minor: ${{ steps.resolve.outputs.target_minor }}
|
||||
target_branch: ${{ steps.resolve.outputs.target_branch }}
|
||||
needs_release: ${{ steps.resolve.outputs.needs_release }}
|
||||
diff_url: ${{ steps.resolve.outputs.diff_url }}
|
||||
latest_patch_tag: ${{ steps.resolve.outputs.latest_patch_tag }}
|
||||
|
||||
steps:
|
||||
- name: Checkout ComfyUI_frontend
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
path: frontend
|
||||
|
||||
- name: Checkout ComfyUI (sparse)
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: comfyanonymous/ComfyUI
|
||||
sparse-checkout: |
|
||||
requirements.txt
|
||||
path: comfyui
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: frontend
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Resolve release information
|
||||
id: resolve
|
||||
working-directory: frontend
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Run the resolver script
|
||||
if ! RESULT=$(tsx scripts/cicd/resolve-comfyui-release.ts ../comfyui .); then
|
||||
echo "Failed to resolve release information"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Resolver output:"
|
||||
echo "$RESULT"
|
||||
|
||||
# Validate JSON output
|
||||
if ! echo "$RESULT" | jq empty 2>/dev/null; then
|
||||
echo "Invalid JSON output from resolver"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse JSON output and set outputs
|
||||
echo "current_version=$(echo "$RESULT" | jq -r '.current_version')" >> $GITHUB_OUTPUT
|
||||
echo "target_version=$(echo "$RESULT" | jq -r '.target_version')" >> $GITHUB_OUTPUT
|
||||
echo "target_minor=$(echo "$RESULT" | jq -r '.target_minor')" >> $GITHUB_OUTPUT
|
||||
echo "target_branch=$(echo "$RESULT" | jq -r '.target_branch')" >> $GITHUB_OUTPUT
|
||||
echo "needs_release=$(echo "$RESULT" | jq -r '.needs_release')" >> $GITHUB_OUTPUT
|
||||
echo "diff_url=$(echo "$RESULT" | jq -r '.diff_url')" >> $GITHUB_OUTPUT
|
||||
echo "latest_patch_tag=$(echo "$RESULT" | jq -r '.latest_patch_tag')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## Release Information" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Current version: ${{ steps.resolve.outputs.current_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Target version: ${{ steps.resolve.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Target branch: ${{ steps.resolve.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Needs release: ${{ steps.resolve.outputs.needs_release }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Diff: [${{ steps.resolve.outputs.current_version }}...${{ steps.resolve.outputs.target_version }}](${{ steps.resolve.outputs.diff_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
trigger-release-if-needed:
|
||||
needs: resolve-version
|
||||
if: needs.resolve-version.outputs.needs_release == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Trigger release workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "Triggering release workflow for branch ${{ needs.resolve-version.outputs.target_branch }}"
|
||||
|
||||
# Trigger the release-version-bump workflow
|
||||
if ! gh workflow run release-version-bump.yaml \
|
||||
--repo Comfy-Org/ComfyUI_frontend \
|
||||
--ref main \
|
||||
--field version_type=patch \
|
||||
--field branch=${{ needs.resolve-version.outputs.target_branch }}; then
|
||||
echo "Failed to trigger release workflow"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Release workflow triggered successfully for ${{ needs.resolve-version.outputs.target_branch }}"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## Release Workflow Triggered" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Branch: ${{ needs.resolve-version.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Target version: ${{ needs.resolve-version.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [View workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
create-comfyui-pr:
|
||||
needs: [resolve-version, trigger-release-if-needed]
|
||||
if: always() && needs.resolve-version.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout ComfyUI fork
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
path: comfyui
|
||||
|
||||
- name: Sync with upstream
|
||||
working-directory: comfyui
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Fetch latest upstream to base our branch on fresh code
|
||||
# Note: This only affects the local checkout, NOT the fork's master branch
|
||||
# We only push the automation branch, leaving the fork's master untouched
|
||||
echo "Fetching upstream master..."
|
||||
if ! git fetch https://github.com/comfyanonymous/ComfyUI.git master; then
|
||||
echo "Failed to fetch upstream master"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking out upstream master..."
|
||||
if ! git checkout FETCH_HEAD; then
|
||||
echo "Failed to checkout FETCH_HEAD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully synced with upstream master"
|
||||
|
||||
- name: Update requirements.txt
|
||||
working-directory: comfyui
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TARGET_VERSION="${{ needs.resolve-version.outputs.target_version }}"
|
||||
echo "Updating comfyui-frontend-package to ${TARGET_VERSION}"
|
||||
|
||||
# Update the comfyui-frontend-package version (POSIX-compatible)
|
||||
sed -i.bak "s/comfyui-frontend-package==[0-9.][0-9.]*/comfyui-frontend-package==${TARGET_VERSION}/" requirements.txt
|
||||
rm requirements.txt.bak
|
||||
|
||||
# Verify the change was made
|
||||
if ! grep -q "comfyui-frontend-package==${TARGET_VERSION}" requirements.txt; then
|
||||
echo "Failed to update requirements.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Updated requirements.txt:"
|
||||
grep comfyui-frontend-package requirements.txt
|
||||
|
||||
- name: Build PR description
|
||||
id: pr-body
|
||||
run: |
|
||||
BODY=$(cat <<'EOF'
|
||||
Bumps frontend to ${{ needs.resolve-version.outputs.target_version }}
|
||||
|
||||
Test quickly:
|
||||
|
||||
```bash
|
||||
python main.py --front-end-version Comfy-Org/ComfyUI_frontend@${{ needs.resolve-version.outputs.target_version }}
|
||||
```
|
||||
|
||||
- Diff: [v${{ needs.resolve-version.outputs.current_version }}...v${{ needs.resolve-version.outputs.target_version }}](${{ needs.resolve-version.outputs.diff_url }})
|
||||
- PyPI: https://pypi.org/project/comfyui-frontend-package/${{ needs.resolve-version.outputs.target_version }}/
|
||||
- npm: https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/${{ needs.resolve-version.outputs.target_version }}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Add release PR note if release was triggered
|
||||
if [ "${{ needs.resolve-version.outputs.needs_release }}" = "true" ]; then
|
||||
RELEASE_NOTE="⚠️ **Release PR must be merged first** - check [release workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)"
|
||||
BODY=$''"${RELEASE_NOTE}"$'\n\n'"${BODY}"
|
||||
fi
|
||||
|
||||
# Save to file for later use
|
||||
printf '%s\n' "$BODY" > pr-body.txt
|
||||
cat pr-body.txt
|
||||
|
||||
- name: Create PR to ComfyUI
|
||||
working-directory: comfyui
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
COMFYUI_FORK: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Extract fork owner from repository name
|
||||
FORK_OWNER=$(echo "$COMFYUI_FORK" | cut -d'/' -f1)
|
||||
|
||||
echo "Creating PR from ${COMFYUI_FORK} to comfyanonymous/ComfyUI"
|
||||
|
||||
# Configure git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Create/update branch (reuse same branch name each week)
|
||||
BRANCH="automation/comfyui-frontend-bump"
|
||||
git checkout -B "$BRANCH"
|
||||
git add requirements.txt
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}"
|
||||
else
|
||||
echo "No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Force push to fork (overwrites previous week's branch)
|
||||
# Note: This intentionally destroys branch history to maintain a single PR
|
||||
# Any review comments or manual commits will need to be re-applied
|
||||
if ! git push -f origin "$BRANCH"; then
|
||||
echo "Failed to push branch to fork"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create draft PR from fork to upstream
|
||||
PR_BODY=$(cat ../pr-body.txt)
|
||||
|
||||
# Try to create PR, ignore error if it already exists
|
||||
if ! gh pr create \
|
||||
--repo comfyanonymous/ComfyUI \
|
||||
--head "${FORK_OWNER}:${BRANCH}" \
|
||||
--base master \
|
||||
--title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \
|
||||
--body "$PR_BODY" \
|
||||
--draft 2>&1; then
|
||||
|
||||
# Check if PR already exists
|
||||
set +e
|
||||
EXISTING_PR=$(gh pr list --repo comfyanonymous/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1)
|
||||
PR_LIST_EXIT=$?
|
||||
set -e
|
||||
|
||||
if [ $PR_LIST_EXIT -ne 0 ]; then
|
||||
echo "Failed to check for existing PR: $EXISTING_PR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
|
||||
echo "PR already exists (#${EXISTING_PR}), updating branch will update the PR"
|
||||
else
|
||||
echo "Failed to create PR and no existing PR found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## ComfyUI PR Created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Draft PR created in comfyanonymous/ComfyUI" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### PR Body:" >> $GITHUB_STEP_SUMMARY
|
||||
cat pr-body.txt >> $GITHUB_STEP_SUMMARY
|
||||
@@ -92,3 +92,4 @@ jobs:
|
||||
base: ${{ github.event.inputs.branch }}
|
||||
labels: |
|
||||
Release
|
||||
|
||||
|
||||
2
.github/workflows/weekly-docs-check.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
# Description: Automated weekly documentation accuracy check and update via Claude
|
||||
name: "Weekly Documentation Check"
|
||||
description: "Automated weekly documentation accuracy check and update via Claude"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -9,7 +9,7 @@ module.exports = defineConfig({
|
||||
entry: 'src/locales/en',
|
||||
entryLocale: 'en',
|
||||
output: 'src/locales',
|
||||
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR'],
|
||||
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
|
||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
||||
'latent' is the short form of 'latent space'.
|
||||
'mask' is in the context of image processing.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"ignorePatterns": [
|
||||
".i18nrc.cjs",
|
||||
"components.d.ts",
|
||||
"lint-staged.config.js",
|
||||
"vitest.setup.ts",
|
||||
"**/vite.config.*.timestamp*",
|
||||
"**/vitest.config.*.timestamp*",
|
||||
"packages/registry-types/src/comfyRegistryTypes.ts",
|
||||
"src/extensions/core/*",
|
||||
"src/scripts/*",
|
||||
"src/types/generatedManagerTypes.ts",
|
||||
"src/types/vue-shim.d.ts"
|
||||
],
|
||||
"rules": {
|
||||
"no-async-promise-executor": "off",
|
||||
"no-control-regex": "off",
|
||||
"no-eval": "off",
|
||||
"no-self-assign": "allow",
|
||||
"no-unused-expressions": "off",
|
||||
"no-unused-private-class-members": "off",
|
||||
"no-useless-rename": "off",
|
||||
"typescript/no-this-alias": "off",
|
||||
"typescript/no-unnecessary-parameter-property-assignment": "off",
|
||||
"typescript/no-unsafe-declaration-merging": "off",
|
||||
"typescript/no-unused-vars": "off",
|
||||
"unicorn/no-empty-file": "off",
|
||||
"unicorn/no-new-array": "off",
|
||||
"unicorn/no-single-promise-in-promise-methods": "off",
|
||||
"unicorn/no-useless-fallback-in-spread": "off",
|
||||
"unicorn/no-useless-spread": "off",
|
||||
"typescript/await-thenable": "off",
|
||||
"typescript/no-base-to-string": "off",
|
||||
"typescript/no-duplicate-type-constituents": "off",
|
||||
"typescript/no-for-in-array": "off",
|
||||
"typescript/no-meaningless-void-operator": "off",
|
||||
"typescript/no-redundant-type-constituents": "off",
|
||||
"typescript/restrict-template-expressions": "off",
|
||||
"typescript/unbound-method": "off",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"vue/no-import-compiler-macros": "error"
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ const config: StorybookConfig = {
|
||||
deep: true,
|
||||
extensions: ['vue']
|
||||
})
|
||||
// Note: Explicitly NOT including generateImportMapPlugin to avoid externalization
|
||||
],
|
||||
server: {
|
||||
allowedHosts: true
|
||||
|
||||
10
.yamllint
@@ -1,10 +0,0 @@
|
||||
extends: default
|
||||
|
||||
ignore: |
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
rules:
|
||||
line-length: disable
|
||||
document-start: disable
|
||||
truthy: disable
|
||||
@@ -17,7 +17,6 @@ This bootstraps the monorepo with dependencies, builds, tests, and dev server ve
|
||||
- `pnpm typecheck`: Type checking
|
||||
- `pnpm build`: Build for production (via nx)
|
||||
- `pnpm lint`: Linting (via nx)
|
||||
- `pnpm oxlint`: Fast Rust-based linting with Oxc
|
||||
- `pnpm format`: Prettier formatting
|
||||
- `pnpm test:unit`: Run all unit tests
|
||||
- `pnpm test:browser`: Run E2E tests via Playwright
|
||||
|
||||
21
CODEOWNERS
@@ -1,11 +1,8 @@
|
||||
# Global Ownership
|
||||
* @Comfy-org/comfy_frontend_devs
|
||||
|
||||
# Desktop/Electron
|
||||
/apps/desktop-ui/ @benceruleanlu
|
||||
/src/stores/electronDownloadStore.ts @benceruleanlu
|
||||
/src/extensions/core/electronAdapter.ts @benceruleanlu
|
||||
/vite.electron.config.mts @benceruleanlu
|
||||
/apps/desktop-ui/ @webfiltered
|
||||
/src/stores/electronDownloadStore.ts @webfiltered
|
||||
/src/extensions/core/electronAdapter.ts @webfiltered
|
||||
/vite.electron.config.mts @webfiltered
|
||||
|
||||
# Common UI Components
|
||||
/src/components/chip/ @viva-jinyi
|
||||
@@ -34,7 +31,10 @@
|
||||
/src/components/graph/selectionToolbox/ @Myestery
|
||||
|
||||
# Minimap
|
||||
/src/renderer/extensions/minimap/ @jtydhr88 @Myestery
|
||||
/src/renderer/extensions/minimap/ @jtydhr88
|
||||
|
||||
# Assets
|
||||
/src/platform/assets/ @arjansingh
|
||||
|
||||
# Workflow Templates
|
||||
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||
@@ -53,12 +53,11 @@
|
||||
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
||||
|
||||
# Translations
|
||||
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs
|
||||
/src/locales/pt-BR/ @JonatanAtila @Yorha4D @KarryCharon @shinshin86
|
||||
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
|
||||
|
||||
# LLM Instructions (blank on purpose)
|
||||
.claude/
|
||||
.cursor/
|
||||
.cursorrules
|
||||
**/AGENTS.md
|
||||
**/CLAUDE.md
|
||||
**/CLAUDE.md
|
||||
@@ -243,7 +243,7 @@ pnpm format
|
||||
|
||||
### Styling
|
||||
- Use Tailwind CSS classes instead of custom CSS
|
||||
- NEVER use `dark:` or `dark-theme:` tailwind variants. Instead use a semantic value from the `style.css` theme, e.g. `bg-node-component-surface`
|
||||
- Follow the existing dark theme pattern: `dark-theme:` prefix (not `dark:`)
|
||||
|
||||
### Internationalization
|
||||
- All user-facing strings must use vue-i18n
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@comfyorg/desktop-ui",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"nx": {
|
||||
"tags": [
|
||||
@@ -91,7 +91,7 @@
|
||||
"build-storybook": "storybook build -o dist/storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"@comfyorg/comfyui-electron-types": "catalog:",
|
||||
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
||||
"@comfyorg/shared-frontend-utils": "workspace:*",
|
||||
"@primevue/core": "catalog:",
|
||||
"@primevue/themes": "catalog:",
|
||||
|
||||
@@ -59,8 +59,7 @@ const LOCALES = [
|
||||
['fr', 'Français'],
|
||||
['es', 'Español'],
|
||||
['ar', 'عربي'],
|
||||
['tr', 'Türkçe'],
|
||||
['pt-BR', 'Português (BR)']
|
||||
['tr', 'Türkçe']
|
||||
] as const satisfies ReadonlyArray<[string, string]>
|
||||
|
||||
type SupportedLocale = (typeof LOCALES)[number][0]
|
||||
|
||||
@@ -22,11 +22,7 @@
|
||||
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
|
||||
{{ title }}
|
||||
</h1>
|
||||
<p
|
||||
v-if="statusText"
|
||||
class="text-lg text-neutral-400"
|
||||
data-testid="startup-status-text"
|
||||
>
|
||||
<p v-if="statusText" class="text-lg text-neutral-400">
|
||||
{{ statusText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -115,18 +115,19 @@ import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { ModelRef } from 'vue'
|
||||
import { type ModelRef, computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { PYPI_MIRROR, PYTHON_MIRROR } from '@/constants/uvMirrors'
|
||||
import type { UVMirror } from '@/constants/uvMirrors'
|
||||
import MigrationPicker from '@/components/install/MigrationPicker.vue'
|
||||
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
|
||||
import {
|
||||
PYPI_MIRROR,
|
||||
PYTHON_MIRROR,
|
||||
type UVMirror
|
||||
} from '@/constants/uvMirrors'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { ValidationState } from '@/utils/validationUtil'
|
||||
|
||||
import MigrationPicker from './MigrationPicker.vue'
|
||||
import MirrorItem from './mirror/MirrorItem.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const installPath = defineModel<string>('installPath', { required: true })
|
||||
@@ -228,10 +229,6 @@ const validatePath = async (path: string | undefined) => {
|
||||
}
|
||||
if (validation.parentMissing) errors.push(t('install.parentMissing'))
|
||||
if (validation.isOneDrive) errors.push(t('install.isOneDrive'))
|
||||
if (validation.isInsideAppInstallDir)
|
||||
errors.push(t('install.insideAppInstallDir'))
|
||||
if (validation.isInsideUpdaterCache)
|
||||
errors.push(t('install.insideUpdaterCache'))
|
||||
|
||||
if (validation.error)
|
||||
errors.push(`${t('install.unhandledError')}: ${validation.error}`)
|
||||
|
||||
@@ -16,8 +16,7 @@ export const DESKTOP_MAINTENANCE_TASKS: Readonly<MaintenanceTask>[] = [
|
||||
execute: async () => await electron.setBasePath(),
|
||||
name: 'Base path',
|
||||
shortDescription: 'Change the application base path.',
|
||||
errorDescription:
|
||||
'The current base path is invalid or unsafe. Please select a new location.',
|
||||
errorDescription: 'Unable to open the base path. Please select a new one.',
|
||||
description:
|
||||
'The base path is the default location where ComfyUI stores data. It is the location for the python environment, and may also contain models, custom nodes, and other extensions.',
|
||||
isInstallationFix: true,
|
||||
|
||||
@@ -40,8 +40,7 @@ const localeLoaders: Record<
|
||||
ru: () => import('@frontend-locales/ru/main.json'),
|
||||
tr: () => import('@frontend-locales/tr/main.json'),
|
||||
zh: () => import('@frontend-locales/zh/main.json'),
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/main.json'),
|
||||
'pt-BR': () => import('@frontend-locales/pt-BR/main.json')
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/main.json')
|
||||
}
|
||||
|
||||
const nodeDefsLoaders: Record<
|
||||
@@ -56,8 +55,7 @@ const nodeDefsLoaders: Record<
|
||||
ru: () => import('@frontend-locales/ru/nodeDefs.json'),
|
||||
tr: () => import('@frontend-locales/tr/nodeDefs.json'),
|
||||
zh: () => import('@frontend-locales/zh/nodeDefs.json'),
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json'),
|
||||
'pt-BR': () => import('@frontend-locales/pt-BR/nodeDefs.json')
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json')
|
||||
}
|
||||
|
||||
const commandsLoaders: Record<
|
||||
@@ -72,8 +70,7 @@ const commandsLoaders: Record<
|
||||
ru: () => import('@frontend-locales/ru/commands.json'),
|
||||
tr: () => import('@frontend-locales/tr/commands.json'),
|
||||
zh: () => import('@frontend-locales/zh/commands.json'),
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json'),
|
||||
'pt-BR': () => import('@frontend-locales/pt-BR/commands.json')
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json')
|
||||
}
|
||||
|
||||
const settingsLoaders: Record<
|
||||
@@ -88,8 +85,7 @@ const settingsLoaders: Record<
|
||||
ru: () => import('@frontend-locales/ru/settings.json'),
|
||||
tr: () => import('@frontend-locales/tr/settings.json'),
|
||||
zh: () => import('@frontend-locales/zh/settings.json'),
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json'),
|
||||
'pt-BR': () => import('@frontend-locales/pt-BR/settings.json')
|
||||
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json')
|
||||
}
|
||||
|
||||
// Track which locales have been loaded
|
||||
|
||||
@@ -85,7 +85,6 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
const electron = electronAPI()
|
||||
|
||||
// Reactive state
|
||||
const lastUpdate = ref<InstallValidation | null>(null)
|
||||
const isRefreshing = ref(false)
|
||||
const isRunningTerminalCommand = computed(() =>
|
||||
tasks.value
|
||||
@@ -98,13 +97,6 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
.some((task) => getRunner(task)?.executing)
|
||||
)
|
||||
|
||||
const unsafeBasePath = computed(
|
||||
() => lastUpdate.value?.unsafeBasePath === true
|
||||
)
|
||||
const unsafeBasePathReason = computed(
|
||||
() => lastUpdate.value?.unsafeBasePathReason
|
||||
)
|
||||
|
||||
// Task list
|
||||
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
|
||||
|
||||
@@ -131,7 +123,6 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
* @param validationUpdate Update details passed in by electron
|
||||
*/
|
||||
const processUpdate = (validationUpdate: InstallValidation) => {
|
||||
lastUpdate.value = validationUpdate
|
||||
const update = validationUpdate as IndexedUpdate
|
||||
isRefreshing.value = true
|
||||
|
||||
@@ -164,11 +155,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
}
|
||||
|
||||
const execute = async (task: MaintenanceTask) => {
|
||||
const success = await getRunner(task).execute(task)
|
||||
if (success && task.isInstallationFix) {
|
||||
await refreshDesktopTasks()
|
||||
}
|
||||
return success
|
||||
return getRunner(task).execute(task)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -176,8 +163,6 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
isRefreshing,
|
||||
isRunningTerminalCommand,
|
||||
isRunningInstallationFix,
|
||||
unsafeBasePath,
|
||||
unsafeBasePathReason,
|
||||
execute,
|
||||
getRunner,
|
||||
processUpdate,
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
// eslint-disable-next-line storybook/no-renderer-packages
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type UnsafeReason = 'appInstallDir' | 'updaterCache' | 'oneDrive' | null
|
||||
type ValidationIssueState = 'OK' | 'warning' | 'error' | 'skipped'
|
||||
|
||||
type ValidationState = {
|
||||
inProgress: boolean
|
||||
installState: string
|
||||
basePath?: ValidationIssueState
|
||||
unsafeBasePath: boolean
|
||||
unsafeBasePathReason: UnsafeReason
|
||||
venvDirectory?: ValidationIssueState
|
||||
pythonInterpreter?: ValidationIssueState
|
||||
pythonPackages?: ValidationIssueState
|
||||
uv?: ValidationIssueState
|
||||
git?: ValidationIssueState
|
||||
vcRedist?: ValidationIssueState
|
||||
upgradePackages?: ValidationIssueState
|
||||
}
|
||||
|
||||
const validationState: ValidationState = {
|
||||
inProgress: false,
|
||||
installState: 'installed',
|
||||
basePath: 'OK',
|
||||
unsafeBasePath: false,
|
||||
unsafeBasePathReason: null,
|
||||
venvDirectory: 'OK',
|
||||
pythonInterpreter: 'OK',
|
||||
pythonPackages: 'OK',
|
||||
uv: 'OK',
|
||||
git: 'OK',
|
||||
vcRedist: 'OK',
|
||||
upgradePackages: 'OK'
|
||||
}
|
||||
|
||||
const createMockElectronAPI = () => {
|
||||
const logListeners: Array<(message: string) => void> = []
|
||||
|
||||
const getValidationUpdate = () => ({
|
||||
...validationState
|
||||
})
|
||||
|
||||
return {
|
||||
getPlatform: () => 'darwin',
|
||||
changeTheme: (_theme: unknown) => {},
|
||||
onLogMessage: (listener: (message: string) => void) => {
|
||||
logListeners.push(listener)
|
||||
},
|
||||
showContextMenu: (_options: unknown) => {},
|
||||
Events: {
|
||||
trackEvent: (_eventName: string, _data?: unknown) => {}
|
||||
},
|
||||
Validation: {
|
||||
onUpdate: (_callback: (update: unknown) => void) => {},
|
||||
async getStatus() {
|
||||
return getValidationUpdate()
|
||||
},
|
||||
async validateInstallation(callback: (update: unknown) => void) {
|
||||
callback(getValidationUpdate())
|
||||
},
|
||||
async complete() {
|
||||
// Only allow completion when the base path is safe
|
||||
return !validationState.unsafeBasePath
|
||||
},
|
||||
dispose: () => {}
|
||||
},
|
||||
setBasePath: () => Promise.resolve(true),
|
||||
reinstall: () => Promise.resolve(),
|
||||
uv: {
|
||||
installRequirements: () => Promise.resolve(),
|
||||
clearCache: () => Promise.resolve(),
|
||||
resetVenv: () => Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ensureElectronAPI = () => {
|
||||
const globalWindow = window as unknown as { electronAPI?: unknown }
|
||||
if (!globalWindow.electronAPI) {
|
||||
globalWindow.electronAPI = createMockElectronAPI()
|
||||
}
|
||||
|
||||
return globalWindow.electronAPI
|
||||
}
|
||||
|
||||
const MaintenanceView = defineAsyncComponent(async () => {
|
||||
ensureElectronAPI()
|
||||
const module = await import('./MaintenanceView.vue')
|
||||
return module.default
|
||||
})
|
||||
|
||||
const meta: Meta<typeof MaintenanceView> = {
|
||||
title: 'Desktop/Views/MaintenanceView',
|
||||
component: MaintenanceView,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
backgrounds: {
|
||||
default: 'dark',
|
||||
values: [
|
||||
{ name: 'dark', value: '#0a0a0a' },
|
||||
{ name: 'neutral-900', value: '#171717' },
|
||||
{ name: 'neutral-950', value: '#0a0a0a' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
name: 'All tasks OK',
|
||||
render: () => ({
|
||||
components: { MaintenanceView },
|
||||
setup() {
|
||||
validationState.inProgress = false
|
||||
validationState.installState = 'installed'
|
||||
validationState.basePath = 'OK'
|
||||
validationState.unsafeBasePath = false
|
||||
validationState.unsafeBasePathReason = null
|
||||
validationState.venvDirectory = 'OK'
|
||||
validationState.pythonInterpreter = 'OK'
|
||||
validationState.pythonPackages = 'OK'
|
||||
validationState.uv = 'OK'
|
||||
validationState.git = 'OK'
|
||||
validationState.vcRedist = 'OK'
|
||||
validationState.upgradePackages = 'OK'
|
||||
ensureElectronAPI()
|
||||
return {}
|
||||
},
|
||||
template: '<MaintenanceView />'
|
||||
})
|
||||
}
|
||||
|
||||
export const UnsafeBasePathOneDrive: Story = {
|
||||
name: 'Unsafe base path (OneDrive)',
|
||||
render: () => ({
|
||||
components: { MaintenanceView },
|
||||
setup() {
|
||||
validationState.inProgress = false
|
||||
validationState.installState = 'installed'
|
||||
validationState.basePath = 'error'
|
||||
validationState.unsafeBasePath = true
|
||||
validationState.unsafeBasePathReason = 'oneDrive'
|
||||
validationState.venvDirectory = 'OK'
|
||||
validationState.pythonInterpreter = 'OK'
|
||||
validationState.pythonPackages = 'OK'
|
||||
validationState.uv = 'OK'
|
||||
validationState.git = 'OK'
|
||||
validationState.vcRedist = 'OK'
|
||||
validationState.upgradePackages = 'OK'
|
||||
ensureElectronAPI()
|
||||
return {}
|
||||
},
|
||||
template: '<MaintenanceView />'
|
||||
})
|
||||
}
|
||||
@@ -47,28 +47,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unsafe migration warning -->
|
||||
<div v-if="taskStore.unsafeBasePath" class="my-4">
|
||||
<p class="flex items-start gap-3 text-neutral-300">
|
||||
<Tag
|
||||
icon="pi pi-exclamation-triangle"
|
||||
severity="warn"
|
||||
:value="t('icon.exclamation-triangle')"
|
||||
/>
|
||||
<span>
|
||||
<strong class="block mb-1">
|
||||
{{ t('maintenance.unsafeMigration.title') }}
|
||||
</strong>
|
||||
<span class="block mb-1">
|
||||
{{ unsafeReasonText }}
|
||||
</span>
|
||||
<span class="block text-sm text-neutral-400">
|
||||
{{ t('maintenance.unsafeMigration.action') }}
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tasks -->
|
||||
<TaskListPanel
|
||||
class="border-neutral-700 border-solid border-x-0 border-y"
|
||||
@@ -111,10 +89,10 @@
|
||||
import { PrimeIcons } from '@primevue/core/api'
|
||||
import Button from 'primevue/button'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import Tag from 'primevue/tag'
|
||||
import Toast from 'primevue/toast'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import RefreshButton from '@/components/common/RefreshButton.vue'
|
||||
import StatusTag from '@/components/maintenance/StatusTag.vue'
|
||||
@@ -161,27 +139,6 @@ const filterOptions = ref([
|
||||
/** Filter binding; can be set to show all tasks, or only errors. */
|
||||
const filter = ref<MaintenanceFilter>(filterOptions.value[0])
|
||||
|
||||
const unsafeReasonText = computed(() => {
|
||||
const reason = taskStore.unsafeBasePathReason
|
||||
if (!reason) {
|
||||
return t('maintenance.unsafeMigration.generic')
|
||||
}
|
||||
|
||||
if (reason === 'appInstallDir') {
|
||||
return t('maintenance.unsafeMigration.appInstallDir')
|
||||
}
|
||||
|
||||
if (reason === 'updaterCache') {
|
||||
return t('maintenance.unsafeMigration.updaterCache')
|
||||
}
|
||||
|
||||
if (reason === 'oneDrive') {
|
||||
return t('maintenance.unsafeMigration.oneDrive')
|
||||
}
|
||||
|
||||
return t('maintenance.unsafeMigration.generic')
|
||||
})
|
||||
|
||||
/** If valid, leave the validation window. */
|
||||
const completeValidation = async () => {
|
||||
const isValid = await electron.Validation.complete()
|
||||
|
||||
@@ -71,8 +71,8 @@ const updateConsent = async () => {
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('install.settings.errorUpdatingConsent'),
|
||||
detail: t('install.settings.errorUpdatingConsentDetail'),
|
||||
summary: t('install.errorUpdatingConsent'),
|
||||
detail: t('install.errorUpdatingConsentDetail'),
|
||||
life: 3000
|
||||
})
|
||||
} finally {
|
||||
|
||||
@@ -85,10 +85,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { InstallStage, ProgressStatus } from '@comfyorg/comfyui-electron-types'
|
||||
import type {
|
||||
InstallStageInfo,
|
||||
InstallStageName
|
||||
import {
|
||||
InstallStage,
|
||||
type InstallStageInfo,
|
||||
type InstallStageName,
|
||||
ProgressStatus
|
||||
} from '@comfyorg/comfyui-electron-types'
|
||||
import type { Terminal } from '@xterm/xterm'
|
||||
import Button from 'primevue/button'
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/*": ["src/*"],
|
||||
"@frontend-locales/*": ["../../src/locales/*"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||
import { SettingDialog } from './components/SettingDialog'
|
||||
import {
|
||||
NodeLibrarySidebarTab,
|
||||
QueueSidebarTab,
|
||||
WorkflowsSidebarTab
|
||||
} from './components/SidebarTab'
|
||||
import { Topbar } from './components/Topbar'
|
||||
@@ -30,6 +31,7 @@ type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
|
||||
class ComfyMenu {
|
||||
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
|
||||
private _workflowsTab: WorkflowsSidebarTab | null = null
|
||||
private _queueTab: QueueSidebarTab | null = null
|
||||
private _topbar: Topbar | null = null
|
||||
|
||||
public readonly sideToolbar: Locator
|
||||
@@ -58,6 +60,11 @@ class ComfyMenu {
|
||||
return this._workflowsTab
|
||||
}
|
||||
|
||||
get queueTab() {
|
||||
this._queueTab ??= new QueueSidebarTab(this.page)
|
||||
return this._queueTab
|
||||
}
|
||||
|
||||
get topbar() {
|
||||
this._topbar ??= new Topbar(this.page)
|
||||
return this._topbar
|
||||
@@ -557,7 +564,7 @@ export class ComfyPage {
|
||||
async dragAndDrop(source: Position, target: Position) {
|
||||
await this.page.mouse.move(source.x, source.y)
|
||||
await this.page.mouse.down()
|
||||
await this.page.mouse.move(target.x, target.y, { steps: 100 })
|
||||
await this.page.mouse.move(target.x, target.y)
|
||||
await this.page.mouse.up()
|
||||
await this.nextFrame()
|
||||
}
|
||||
@@ -1651,10 +1658,7 @@ export const comfyPageFixture = base.extend<{
|
||||
// Set tutorial completed to true to avoid loading the tutorial workflow.
|
||||
'Comfy.TutorialCompleted': true,
|
||||
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize,
|
||||
'Comfy.VueNodes.AutoScaleLayout': false,
|
||||
// Disable toast warning about version compatibility, as they may or
|
||||
// may not appear - depending on upstream ComfyUI dependencies
|
||||
'Comfy.VersionCompatibility.DisableWarnings': true
|
||||
'Comfy.VueNodes.AutoScaleLayout': false
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
@@ -65,9 +65,7 @@ export class VueNodeHelpers {
|
||||
* Select a specific Vue node by ID
|
||||
*/
|
||||
async selectNode(nodeId: string): Promise<void> {
|
||||
await this.page
|
||||
.locator(`[data-node-id="${nodeId}"] .lg-node-header`)
|
||||
.click()
|
||||
await this.page.locator(`[data-node-id="${nodeId}"]`).click()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,13 +77,11 @@ export class VueNodeHelpers {
|
||||
// Select first node normally
|
||||
await this.selectNode(nodeIds[0])
|
||||
|
||||
// Add additional nodes with Ctrl+click on header
|
||||
// Add additional nodes with Ctrl+click
|
||||
for (let i = 1; i < nodeIds.length; i++) {
|
||||
await this.page
|
||||
.locator(`[data-node-id="${nodeIds[i]}"] .lg-node-header`)
|
||||
.click({
|
||||
modifiers: ['Control']
|
||||
})
|
||||
await this.page.locator(`[data-node-id="${nodeIds[i]}"]`).click({
|
||||
modifiers: ['Control']
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,3 +148,124 @@ export class WorkflowsSidebarTab extends SidebarTab {
|
||||
.click()
|
||||
}
|
||||
}
|
||||
|
||||
export class QueueSidebarTab extends SidebarTab {
|
||||
constructor(public readonly page: Page) {
|
||||
super(page, 'queue')
|
||||
}
|
||||
|
||||
get root() {
|
||||
return this.page.locator('.sidebar-content-container', { hasText: 'Queue' })
|
||||
}
|
||||
|
||||
get tasks() {
|
||||
return this.root.locator('[data-virtual-grid-item]')
|
||||
}
|
||||
|
||||
get visibleTasks() {
|
||||
return this.tasks.locator('visible=true')
|
||||
}
|
||||
|
||||
get clearButton() {
|
||||
return this.root.locator('.clear-all-button')
|
||||
}
|
||||
|
||||
get collapseTasksButton() {
|
||||
return this.getToggleExpandButton(false)
|
||||
}
|
||||
|
||||
get expandTasksButton() {
|
||||
return this.getToggleExpandButton(true)
|
||||
}
|
||||
|
||||
get noResultsPlaceholder() {
|
||||
return this.root.locator('.no-results-placeholder')
|
||||
}
|
||||
|
||||
get galleryImage() {
|
||||
return this.page.locator('.galleria-image')
|
||||
}
|
||||
|
||||
private getToggleExpandButton(isExpanded: boolean) {
|
||||
const iconSelector = isExpanded ? '.pi-image' : '.pi-images'
|
||||
return this.root.locator(`.toggle-expanded-button ${iconSelector}`)
|
||||
}
|
||||
|
||||
async open() {
|
||||
await super.open()
|
||||
return this.root.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async close() {
|
||||
await super.close()
|
||||
await this.root.waitFor({ state: 'hidden' })
|
||||
}
|
||||
|
||||
async expandTasks() {
|
||||
await this.expandTasksButton.click()
|
||||
await this.collapseTasksButton.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async collapseTasks() {
|
||||
await this.collapseTasksButton.click()
|
||||
await this.expandTasksButton.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async waitForTasks() {
|
||||
return Promise.all([
|
||||
this.tasks.first().waitFor({ state: 'visible' }),
|
||||
this.tasks.last().waitFor({ state: 'visible' })
|
||||
])
|
||||
}
|
||||
|
||||
async scrollTasks(direction: 'up' | 'down') {
|
||||
const scrollToEl =
|
||||
direction === 'up' ? this.tasks.last() : this.tasks.first()
|
||||
await scrollToEl.scrollIntoViewIfNeeded()
|
||||
await this.waitForTasks()
|
||||
}
|
||||
|
||||
async clearTasks() {
|
||||
await this.clearButton.click()
|
||||
const confirmButton = this.page.getByLabel('Delete')
|
||||
await confirmButton.click()
|
||||
await this.noResultsPlaceholder.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
/** Set the width of the tab (out of 100). Must call before opening the tab */
|
||||
async setTabWidth(width: number) {
|
||||
if (width < 0 || width > 100) {
|
||||
throw new Error('Width must be between 0 and 100')
|
||||
}
|
||||
return this.page.evaluate((width) => {
|
||||
localStorage.setItem('queue', JSON.stringify([width, 100 - width]))
|
||||
}, width)
|
||||
}
|
||||
|
||||
getTaskPreviewButton(taskIndex: number) {
|
||||
return this.tasks.nth(taskIndex).getByRole('button')
|
||||
}
|
||||
|
||||
async openTaskPreview(taskIndex: number) {
|
||||
const previewButton = this.getTaskPreviewButton(taskIndex)
|
||||
await previewButton.click()
|
||||
return this.galleryImage.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
getGalleryImage(imageFilename: string) {
|
||||
return this.galleryImage.and(this.page.getByAltText(imageFilename))
|
||||
}
|
||||
|
||||
getTaskImage(imageFilename: string) {
|
||||
return this.tasks.getByAltText(imageFilename)
|
||||
}
|
||||
|
||||
/** Trigger the queue store and tasks to update */
|
||||
async triggerTasksUpdate() {
|
||||
await this.page.evaluate(() => {
|
||||
window['app']['api'].dispatchCustomEvent('status', {
|
||||
exec_info: { queue_remaining: 0 }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
144
browser_tests/tests/chatHistory.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
interface ChatHistoryEntry {
|
||||
prompt: string
|
||||
response: string
|
||||
response_id: string
|
||||
}
|
||||
|
||||
async function renderChatHistory(page: Page, history: ChatHistoryEntry[]) {
|
||||
const nodeId = await page.evaluate(() => window['app'].graph.nodes[0]?.id)
|
||||
// Simulate API sending display_component message
|
||||
await page.evaluate(
|
||||
({ nodeId, history }) => {
|
||||
const event = new CustomEvent('display_component', {
|
||||
detail: {
|
||||
node_id: nodeId,
|
||||
component: 'ChatHistoryWidget',
|
||||
props: {
|
||||
history: JSON.stringify(history)
|
||||
}
|
||||
}
|
||||
})
|
||||
window['app'].api.dispatchEvent(event)
|
||||
return true
|
||||
},
|
||||
{ nodeId, history }
|
||||
)
|
||||
|
||||
return nodeId
|
||||
}
|
||||
|
||||
test.describe('Chat History Widget', () => {
|
||||
let nodeId: string
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
nodeId = await renderChatHistory(comfyPage.page, [
|
||||
{ prompt: 'Hello', response: 'World', response_id: '123' }
|
||||
])
|
||||
// Wait for chat history to be rendered
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
})
|
||||
|
||||
test('displays chat history when receiving display_component message', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Verify the chat history is displayed correctly
|
||||
await expect(comfyPage.page.getByText('Hello')).toBeVisible()
|
||||
await expect(comfyPage.page.getByText('World')).toBeVisible()
|
||||
})
|
||||
|
||||
test('handles message editing interaction', async ({ comfyPage }) => {
|
||||
// Get first node's ID
|
||||
nodeId = await comfyPage.page.evaluate(() => {
|
||||
const node = window['app'].graph.nodes[0]
|
||||
|
||||
// Make sure the node has a prompt widget (for editing functionality)
|
||||
if (!node.widgets) {
|
||||
node.widgets = []
|
||||
}
|
||||
|
||||
// Add a prompt widget if it doesn't exist
|
||||
if (!node.widgets.find((w) => w.name === 'prompt')) {
|
||||
node.widgets.push({
|
||||
name: 'prompt',
|
||||
type: 'text',
|
||||
value: 'Original prompt'
|
||||
})
|
||||
}
|
||||
|
||||
return node.id
|
||||
})
|
||||
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Message 1',
|
||||
response: 'Response 1',
|
||||
response_id: '123'
|
||||
},
|
||||
{
|
||||
prompt: 'Message 2',
|
||||
response: 'Response 2',
|
||||
response_id: '456'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
|
||||
const originalTextAreaInput = await comfyPage.page
|
||||
.getByPlaceholder('text')
|
||||
.nth(1)
|
||||
.inputValue()
|
||||
|
||||
// Click edit button on first message
|
||||
await comfyPage.page.getByLabel('Edit').first().click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify cancel button appears
|
||||
await expect(comfyPage.page.getByLabel('Cancel')).toBeVisible()
|
||||
|
||||
// Click cancel edit
|
||||
await comfyPage.page.getByLabel('Cancel').click()
|
||||
|
||||
// Verify prompt input is restored
|
||||
await expect(comfyPage.page.getByPlaceholder('text').nth(1)).toHaveValue(
|
||||
originalTextAreaInput
|
||||
)
|
||||
})
|
||||
|
||||
test('handles real-time updates to chat history', async ({ comfyPage }) => {
|
||||
// Send initial history
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Initial message',
|
||||
response: 'Initial response',
|
||||
response_id: '123'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
|
||||
// Update history with additional messages
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Follow-up',
|
||||
response: 'New response',
|
||||
response_id: '456'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
|
||||
// Move mouse over the canvas to force update
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify new messages appear
|
||||
await expect(comfyPage.page.getByText('Follow-up')).toBeVisible()
|
||||
await expect(comfyPage.page.getByText('New response')).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -88,10 +88,6 @@ test.describe('Missing models warning', () => {
|
||||
|
||||
const downloadButton = missingModelsWarning.getByLabel('Download')
|
||||
await expect(downloadButton).toBeVisible()
|
||||
|
||||
// Check that the copy URL button is also visible for Desktop environment
|
||||
const copyUrlButton = missingModelsWarning.getByLabel('Copy URL')
|
||||
await expect(copyUrlButton).toBeVisible()
|
||||
})
|
||||
|
||||
test('Should display a warning when missing models are found in node properties', async ({
|
||||
@@ -105,10 +101,6 @@ test.describe('Missing models warning', () => {
|
||||
|
||||
const downloadButton = missingModelsWarning.getByLabel('Download')
|
||||
await expect(downloadButton).toBeVisible()
|
||||
|
||||
// Check that the copy URL button is also visible for Desktop environment
|
||||
const copyUrlButton = missingModelsWarning.getByLabel('Copy URL')
|
||||
await expect(copyUrlButton).toBeVisible()
|
||||
})
|
||||
|
||||
test('Should not display a warning when no missing models are found', async ({
|
||||
|
||||
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 98 KiB |
@@ -2,7 +2,6 @@ import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../fixtures/ComfyPage'
|
||||
import { fitToViewInstant } from '../helpers/fitToView'
|
||||
|
||||
// TODO: there might be a better solution for this
|
||||
// Helper function to pan canvas and select node
|
||||
@@ -517,7 +516,6 @@ This is English documentation.
|
||||
)
|
||||
|
||||
await comfyPage.loadWorkflow('default')
|
||||
await fitToViewInstant(comfyPage)
|
||||
|
||||
// Select KSampler first
|
||||
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')
|
||||
|
||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
210
browser_tests/tests/sidebar/queue.spec.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||
|
||||
test.describe.skip('Queue sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('can display tasks', async ({ comfyPage }) => {
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
|
||||
test('can display tasks after closing then opening', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.close()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
|
||||
test.describe('Virtual scroll', () => {
|
||||
const layouts = [
|
||||
{ description: 'Five columns layout', width: 95, rows: 3, cols: 5 },
|
||||
{ description: 'Three columns layout', width: 55, rows: 3, cols: 3 },
|
||||
{ description: 'Two columns layout', width: 40, rows: 3, cols: 2 }
|
||||
]
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp'])
|
||||
.repeat(50)
|
||||
.setupRoutes()
|
||||
})
|
||||
|
||||
layouts.forEach(({ description, width, rows, cols }) => {
|
||||
const preRenderedRows = 1
|
||||
const preRenderedTasks = preRenderedRows * cols * 2
|
||||
const visibleTasks = rows * cols
|
||||
const expectRenderLimit = visibleTasks + preRenderedTasks
|
||||
|
||||
test.describe(description, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.setTabWidth(width)
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
})
|
||||
|
||||
test('should not render items outside of view', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
|
||||
test('should teardown items after scrolling away', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.queueTab.scrollTasks('down')
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
|
||||
test('should re-render items after scrolling away then back', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.queueTab.scrollTasks('down')
|
||||
await comfyPage.menu.queueTab.scrollTasks('up')
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Expand tasks', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// 2-item batch and 3-item batch -> 3 additional items when expanded
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp', 'example.webp', 'example.webp'])
|
||||
.withTask(['example.webp', 'example.webp'])
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
})
|
||||
|
||||
test('can expand tasks with multiple outputs', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
await comfyPage.menu.queueTab.expandTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
|
||||
initialCount + 3
|
||||
)
|
||||
})
|
||||
|
||||
test('can collapse flat tasks', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
await comfyPage.menu.queueTab.expandTasks()
|
||||
await comfyPage.menu.queueTab.collapseTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
|
||||
initialCount
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Clear tasks', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp'])
|
||||
.repeat(6)
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
})
|
||||
|
||||
test('can clear all tasks', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.clearTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(0)
|
||||
expect(
|
||||
await comfyPage.menu.queueTab.noResultsPlaceholder.isVisible()
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('can load new tasks after clearing all', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.clearTasks()
|
||||
await comfyPage.menu.queueTab.close()
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Gallery', () => {
|
||||
const firstImage = 'example.webp'
|
||||
const secondImage = 'image32x32.webp'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask([secondImage])
|
||||
.withTask([firstImage])
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
await comfyPage.menu.queueTab.openTaskPreview(0)
|
||||
})
|
||||
|
||||
test('displays gallery image after opening task preview', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.nextFrame()
|
||||
await expect(
|
||||
comfyPage.menu.queueTab.getGalleryImage(firstImage)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('maintains active gallery item when new tasks are added', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Add a new task while the gallery is still open
|
||||
const newImage = 'image64x64.webp'
|
||||
comfyPage.setupHistory().withTask([newImage])
|
||||
await comfyPage.menu.queueTab.triggerTasksUpdate()
|
||||
await comfyPage.page.waitForTimeout(500)
|
||||
const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage)
|
||||
await newTask.waitFor({ state: 'visible' })
|
||||
// The active gallery item should still be the initial image
|
||||
await expect(
|
||||
comfyPage.menu.queueTab.getGalleryImage(firstImage)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.describe('Gallery navigation', () => {
|
||||
const paths: {
|
||||
description: string
|
||||
path: ('Right' | 'Left')[]
|
||||
end: string
|
||||
}[] = [
|
||||
{ description: 'Right', path: ['Right'], end: secondImage },
|
||||
{ description: 'Left', path: ['Right', 'Left'], end: firstImage },
|
||||
{ description: 'Left wrap', path: ['Left'], end: secondImage },
|
||||
{ description: 'Right wrap', path: ['Right', 'Right'], end: firstImage }
|
||||
]
|
||||
|
||||
paths.forEach(({ description, path, end }) => {
|
||||
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
|
||||
for (const direction of path)
|
||||
await comfyPage.page.keyboard.press(`Arrow${direction}`, {
|
||||
delay: 256
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
await expect(
|
||||
comfyPage.menu.queueTab.getGalleryImage(end)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 33 KiB |
@@ -6,7 +6,6 @@ import {
|
||||
test.describe('Vue Nodes Zoom', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 34 KiB |
@@ -1,54 +0,0 @@
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../../../../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Node Resizing', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should resize node without position drift after selecting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Get a Vue node fixture
|
||||
const node = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
|
||||
const initialBox = await node.boundingBox()
|
||||
if (!initialBox) throw new Error('Node bounding box not found')
|
||||
|
||||
// Select the node first (this was causing the bug)
|
||||
await node.header.click()
|
||||
await comfyPage.page.waitForTimeout(100) // Brief pause after selection
|
||||
|
||||
// Get position after selection
|
||||
const selectedBox = await node.boundingBox()
|
||||
if (!selectedBox)
|
||||
throw new Error('Node bounding box not found after select')
|
||||
|
||||
// Verify position unchanged after selection
|
||||
expect(selectedBox.x).toBeCloseTo(initialBox.x, 1)
|
||||
expect(selectedBox.y).toBeCloseTo(initialBox.y, 1)
|
||||
|
||||
// Now resize from bottom-right corner
|
||||
const resizeStartX = selectedBox.x + selectedBox.width - 5
|
||||
const resizeStartY = selectedBox.y + selectedBox.height - 5
|
||||
|
||||
await comfyPage.page.mouse.move(resizeStartX, resizeStartY)
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.page.mouse.move(resizeStartX + 50, resizeStartY + 30)
|
||||
await comfyPage.page.mouse.up()
|
||||
|
||||
// Get final position and size
|
||||
const finalBox = await node.boundingBox()
|
||||
if (!finalBox) throw new Error('Node bounding box not found after resize')
|
||||
|
||||
// Position should NOT have changed (the bug was position drift)
|
||||
expect(finalBox.x).toBeCloseTo(initialBox.x, 1)
|
||||
expect(finalBox.y).toBeCloseTo(initialBox.y, 1)
|
||||
|
||||
// Size should have increased
|
||||
expect(finalBox.width).toBeGreaterThan(initialBox.width)
|
||||
expect(finalBox.height).toBeGreaterThan(initialBox.height)
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 104 KiB |
48
browser_tests/tests/vueNodes/nodeStates/lod.spec.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../../../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Vue Nodes - LOD', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('default')
|
||||
})
|
||||
|
||||
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
expect(initialNodeCount).toBeGreaterThan(0)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png')
|
||||
|
||||
const vueNodesContainer = comfyPage.vueNodes.nodes
|
||||
const textboxesInNodes = vueNodesContainer.getByRole('textbox')
|
||||
const comboboxesInNodes = vueNodesContainer.getByRole('combobox')
|
||||
|
||||
await expect(textboxesInNodes.first()).toBeVisible()
|
||||
await expect(comboboxesInNodes.first()).toBeVisible()
|
||||
|
||||
await comfyPage.zoom(120, 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png')
|
||||
|
||||
await expect(textboxesInNodes.first()).toBeHidden()
|
||||
await expect(comboboxesInNodes.first()).toBeHidden()
|
||||
|
||||
await comfyPage.zoom(-120, 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-lod-inactive.png'
|
||||
)
|
||||
await expect(textboxesInNodes.first()).toBeVisible()
|
||||
await expect(comboboxesInNodes.first()).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 77 KiB |
154
build/plugins/generateImportMapPlugin.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import glob from 'fast-glob'
|
||||
import fs from 'fs-extra'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite'
|
||||
|
||||
interface ImportMapSource {
|
||||
name: string
|
||||
pattern: string | RegExp
|
||||
entry: string
|
||||
recursiveDependence?: boolean
|
||||
override?: Record<string, Partial<ImportMapSource>>
|
||||
}
|
||||
|
||||
const parseDeps = (root: string, pkg: string) => {
|
||||
const pkgPath = join(root, 'node_modules', pkg, 'package.json')
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
const content = fs.readFileSync(pkgPath, 'utf-8')
|
||||
const pkg = JSON.parse(content)
|
||||
return Object.keys(pkg.dependencies || {})
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin that generates an import map for vendor chunks.
|
||||
*
|
||||
* This plugin creates a browser-compatible import map that maps module specifiers
|
||||
* (like 'vue' or 'primevue') to their actual file locations in the build output.
|
||||
* This improves module loading in modern browsers and enables better caching.
|
||||
*
|
||||
* The plugin:
|
||||
* 1. Tracks vendor chunks during bundle generation
|
||||
* 2. Creates mappings between module names and their file paths
|
||||
* 3. Injects an import map script tag into the HTML head
|
||||
* 4. Configures manual chunk splitting for vendor libraries
|
||||
*
|
||||
* @param vendorLibraries - An array of vendor libraries to split into separate chunks
|
||||
* @returns {Plugin} A Vite plugin that generates and injects an import map
|
||||
*/
|
||||
export function generateImportMapPlugin(
|
||||
importMapSources: ImportMapSource[]
|
||||
): Plugin {
|
||||
const importMapEntries: Record<string, string> = {}
|
||||
const resolvedImportMapSources: Map<string, ImportMapSource> = new Map()
|
||||
const assetDir = 'assets/lib'
|
||||
let root: string
|
||||
|
||||
return {
|
||||
name: 'generate-import-map-plugin',
|
||||
|
||||
// Configure manual chunks during the build process
|
||||
configResolved(config) {
|
||||
root = config.root
|
||||
|
||||
if (config.build) {
|
||||
// Ensure rollupOptions exists
|
||||
if (!config.build.rollupOptions) {
|
||||
config.build.rollupOptions = {}
|
||||
}
|
||||
|
||||
for (const source of importMapSources) {
|
||||
resolvedImportMapSources.set(source.name, source)
|
||||
if (source.recursiveDependence) {
|
||||
const deps = parseDeps(root, source.name)
|
||||
|
||||
while (deps.length) {
|
||||
const dep = deps.shift()!
|
||||
const depSource = Object.assign({}, source, {
|
||||
name: dep,
|
||||
pattern: dep,
|
||||
...source.override?.[dep]
|
||||
})
|
||||
resolvedImportMapSources.set(depSource.name, depSource)
|
||||
|
||||
const _deps = parseDeps(root, depSource.name)
|
||||
deps.unshift(..._deps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const external: (string | RegExp)[] = []
|
||||
for (const [, source] of resolvedImportMapSources) {
|
||||
external.push(source.pattern)
|
||||
}
|
||||
config.build.rollupOptions.external = external
|
||||
}
|
||||
},
|
||||
|
||||
generateBundle(_options) {
|
||||
for (const [, source] of resolvedImportMapSources) {
|
||||
if (source.entry) {
|
||||
const moduleFile = join(source.name, source.entry)
|
||||
const sourceFile = join(root, 'node_modules', moduleFile)
|
||||
const targetFile = join(root, 'dist', assetDir, moduleFile)
|
||||
|
||||
importMapEntries[source.name] =
|
||||
'./' + normalizePath(join(assetDir, moduleFile))
|
||||
|
||||
const targetDir = dirname(targetFile)
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true })
|
||||
}
|
||||
fs.copyFileSync(sourceFile, targetFile)
|
||||
}
|
||||
|
||||
if (source.recursiveDependence) {
|
||||
const files = glob.sync(['**/*.{js,mjs}'], {
|
||||
cwd: join(root, 'node_modules', source.name)
|
||||
})
|
||||
|
||||
for (const file of files) {
|
||||
const moduleFile = join(source.name, file)
|
||||
const sourceFile = join(root, 'node_modules', moduleFile)
|
||||
const targetFile = join(root, 'dist', assetDir, moduleFile)
|
||||
|
||||
importMapEntries[normalizePath(join(source.name, dirname(file)))] =
|
||||
'./' + normalizePath(join(assetDir, moduleFile))
|
||||
|
||||
const targetDir = dirname(targetFile)
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true })
|
||||
}
|
||||
fs.copyFileSync(sourceFile, targetFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
transformIndexHtml(html) {
|
||||
if (Object.keys(importMapEntries).length === 0) {
|
||||
console.warn(
|
||||
'[ImportMap Plugin] No vendor chunks found to create import map.'
|
||||
)
|
||||
return html
|
||||
}
|
||||
|
||||
const importMap = {
|
||||
imports: importMapEntries
|
||||
}
|
||||
|
||||
const importMapTag: HtmlTagDescriptor = {
|
||||
tag: 'script',
|
||||
attrs: { type: 'importmap' },
|
||||
children: JSON.stringify(importMap, null, 2),
|
||||
injectTo: 'head'
|
||||
}
|
||||
|
||||
return {
|
||||
html,
|
||||
tags: [importMapTag]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { comfyAPIPlugin } from './comfyAPIPlugin'
|
||||
export { generateImportMapPlugin } from './generateImportMapPlugin'
|
||||
|
||||
49
demo-snapshots/CHANGELOG-DEMO.md
Normal file
@@ -0,0 +1,49 @@
|
||||
## v1.30.2 (2025-11-01)
|
||||
|
||||
Comparing v1.29.0 → v1.30.2. This changelog documents changes to the public API surface that third-party extensions and custom nodes depend on.
|
||||
|
||||
### ✨ Additions
|
||||
|
||||
**Type Aliases**
|
||||
|
||||
- `WorkflowId`
|
||||
|
||||
**Interfaces**
|
||||
|
||||
- `ExtensionMetadata`
|
||||
- Members: `id`, `name`, `version`, `description`
|
||||
|
||||
### 🔄 Modifications
|
||||
|
||||
> **Note**: Some modifications may be breaking changes.
|
||||
|
||||
**Interfaces**
|
||||
|
||||
- `ComfyApi`
|
||||
- ✨ Added member: `queuePromptAsync`
|
||||
- ✨ Added member: `cancelPrompt`
|
||||
- ✨ Added member: `getQueueStatus`
|
||||
- ⚠️ **Breaking**: Removed member: `queuePrompt`
|
||||
- `NodeDef`
|
||||
- ✨ Added member: `input`
|
||||
- ✨ Added member: `output`
|
||||
- ✨ Added member: `output_name`
|
||||
- `WorkflowMetadata`
|
||||
- ✨ Added member: `tags`
|
||||
- ✨ Added member: `thumbnail`
|
||||
|
||||
**Enums**
|
||||
|
||||
- `NodeStatus`
|
||||
- ✨ Added enum value: `ERROR`
|
||||
- ✨ Added enum value: `COMPLETED`
|
||||
|
||||
**Classes**
|
||||
|
||||
- `WorkflowManager`
|
||||
- ✨ Added member: `cache`
|
||||
- ✨ Added method: `deleteWorkflow()`
|
||||
- ✨ Added method: `searchWorkflows()`
|
||||
|
||||
---
|
||||
|
||||
188
demo-snapshots/README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# API Changelog Generation Demo
|
||||
|
||||
This demo showcases the automated API changelog generation system comparing two versions of the ComfyUI Frontend public API.
|
||||
|
||||
## Overview
|
||||
|
||||
The demo compares **v1.29.0** → **v1.30.2** to demonstrate:
|
||||
- Breaking change detection
|
||||
- API additions tracking
|
||||
- Non-breaking modifications
|
||||
- Human-readable changelog generation
|
||||
|
||||
## Demo Files
|
||||
|
||||
### Input Files
|
||||
- **`v1.29.0.d.ts`** - TypeScript definitions representing the v1.29.0 API surface
|
||||
- **`v1.30.2.d.ts`** - TypeScript definitions representing the v1.30.2 API surface
|
||||
|
||||
### Generated Files
|
||||
- **`v1.29.0.json`** - Structured API snapshot from v1.29.0
|
||||
- **`v1.30.2.json`** - Structured API snapshot from v1.30.2
|
||||
- **`CHANGELOG-DEMO.md`** - Generated changelog comparing the two versions
|
||||
|
||||
## Running the Demo
|
||||
|
||||
```bash
|
||||
# Generate API snapshots
|
||||
node scripts/snapshot-api.js demo-snapshots/v1.29.0.d.ts > demo-snapshots/v1.29.0.json
|
||||
node scripts/snapshot-api.js demo-snapshots/v1.30.2.d.ts > demo-snapshots/v1.30.2.json
|
||||
|
||||
# Compare snapshots and generate changelog
|
||||
node scripts/compare-api-snapshots.js \
|
||||
demo-snapshots/v1.29.0.json \
|
||||
demo-snapshots/v1.30.2.json \
|
||||
1.29.0 \
|
||||
1.30.2 \
|
||||
> demo-snapshots/CHANGELOG-DEMO.md
|
||||
```
|
||||
|
||||
## Key Changes Detected
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
|
||||
1. **`ComfyApi.queuePrompt()` removed**
|
||||
- Replaced with `queuePromptAsync()` which includes additional options
|
||||
- Extension developers need to update their code to use the new async method
|
||||
|
||||
### ✨ New Additions
|
||||
|
||||
1. **New Interface: `ExtensionMetadata`**
|
||||
- Provides metadata for extensions
|
||||
- Fields: `id`, `name`, `version`, `description`
|
||||
|
||||
2. **New Type: `WorkflowId`**
|
||||
- Type alias for workflow identifiers
|
||||
|
||||
3. **Enhanced `ComfyApi` Interface**
|
||||
- `queuePromptAsync()` - Async queue with priority support
|
||||
- `cancelPrompt()` - Cancel queued prompts
|
||||
- `getQueueStatus()` - Query queue state
|
||||
|
||||
4. **Extended `NodeDef` Interface**
|
||||
- `input` - Input specification
|
||||
- `output` - Output types
|
||||
- `output_name` - Output names
|
||||
|
||||
5. **Enhanced `NodeStatus` Enum**
|
||||
- Added `ERROR` state
|
||||
- Added `COMPLETED` state
|
||||
|
||||
6. **Extended `WorkflowManager` Class**
|
||||
- `cache` property for workflow caching
|
||||
- `deleteWorkflow()` method
|
||||
- `searchWorkflows()` method
|
||||
|
||||
### 🔄 Non-Breaking Modifications
|
||||
|
||||
1. **`WorkflowMetadata` enhancements**
|
||||
- Added optional `tags` field
|
||||
- Added optional `thumbnail` field
|
||||
|
||||
## Real-World Usage
|
||||
|
||||
In production, this system will:
|
||||
|
||||
1. **Automatic Triggering**: Run after each NPM types release
|
||||
2. **Version Detection**: Automatically detect current and previous versions from git tags
|
||||
3. **Build Integration**: Build actual TypeScript types from the repository
|
||||
4. **PR Creation**: Generate draft pull requests with the changelog
|
||||
5. **Human Review**: Allow maintainers to review and enhance before merging
|
||||
|
||||
## Benefits for Extension Developers
|
||||
|
||||
### Clear Breaking Change Visibility
|
||||
Extension developers can immediately see:
|
||||
- What APIs were removed
|
||||
- What signatures changed
|
||||
- How to migrate their code
|
||||
|
||||
### Migration Planning
|
||||
With clear documentation of additions and changes, developers can:
|
||||
- Plan updates around breaking changes
|
||||
- Adopt new features when ready
|
||||
- Understand version compatibility
|
||||
|
||||
### Historical Reference
|
||||
The cumulative `docs/API-CHANGELOG.md` provides:
|
||||
- Complete API evolution history
|
||||
- Context for design decisions
|
||||
- Migration guides for major versions
|
||||
|
||||
## Example Extension Migration
|
||||
|
||||
### Before (v1.29.0)
|
||||
```typescript
|
||||
// Old code using queuePrompt
|
||||
const result = await api.queuePrompt(workflow);
|
||||
console.log('Queued:', result.prompt_id);
|
||||
```
|
||||
|
||||
### After (v1.30.2)
|
||||
```typescript
|
||||
// New code using queuePromptAsync with priority
|
||||
const result = await api.queuePromptAsync(workflow, { priority: 1 });
|
||||
console.log('Queued:', result.prompt_id, 'Position:', result.number);
|
||||
```
|
||||
|
||||
## Snapshot Structure
|
||||
|
||||
The JSON snapshots contain structured representations of:
|
||||
|
||||
```json
|
||||
{
|
||||
"types": { /* Type aliases */ },
|
||||
"interfaces": { /* Interface definitions with members */ },
|
||||
"enums": { /* Enum values */ },
|
||||
"functions": { /* Exported functions */ },
|
||||
"classes": { /* Class definitions with methods */ },
|
||||
"constants": { /* Exported constants */ }
|
||||
}
|
||||
```
|
||||
|
||||
Each entry includes:
|
||||
- **Name**: Identifier
|
||||
- **Kind**: Type of declaration
|
||||
- **Members/Methods**: Properties and functions
|
||||
- **Types**: Parameter and return types
|
||||
- **Visibility**: Public/private/protected modifiers
|
||||
- **Optional**: Whether parameters/properties are optional
|
||||
|
||||
## Comparison Algorithm
|
||||
|
||||
The comparison script:
|
||||
|
||||
1. **Categorizes changes** into breaking, additions, and modifications
|
||||
2. **Detects breaking changes**:
|
||||
- Removed interfaces, classes, functions
|
||||
- Removed methods or properties
|
||||
- Changed method signatures
|
||||
- Changed return types
|
||||
- Removed enum values
|
||||
3. **Tracks additions**:
|
||||
- New interfaces, classes, types
|
||||
- New methods and properties
|
||||
- New enum values
|
||||
4. **Identifies modifications**:
|
||||
- Type changes
|
||||
- Optionality changes
|
||||
- Signature changes
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned improvements include:
|
||||
|
||||
- **LLM Enhancement**: Use AI to generate better descriptions and migration guides
|
||||
- **Email Notifications**: Alert developers on mailing list for major changes
|
||||
- **Release Notes Integration**: Auto-include in GitHub releases
|
||||
- **Deprecation Tracking**: Mark APIs as deprecated before removal
|
||||
- **Example Code**: Generate migration code snippets automatically
|
||||
|
||||
## Conclusion
|
||||
|
||||
This automated system ensures:
|
||||
- ✅ Zero manual effort for changelog generation
|
||||
- ✅ Consistent documentation format
|
||||
- ✅ Clear breaking change visibility
|
||||
- ✅ Historical API evolution tracking
|
||||
- ✅ Better extension developer experience
|
||||
58
demo-snapshots/v1.29.0.d.ts
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Mock TypeScript definitions representing v1.29.0 API surface
|
||||
* This represents the public API as it existed in version 1.29.0
|
||||
*/
|
||||
|
||||
export interface ComfyApi {
|
||||
/**
|
||||
* Get API URL for backend calls
|
||||
*/
|
||||
apiURL(path: string): string
|
||||
|
||||
/**
|
||||
* Get file URL for static resources
|
||||
*/
|
||||
fileURL(path: string): string
|
||||
|
||||
/**
|
||||
* Queue a prompt for execution
|
||||
*/
|
||||
queuePrompt(prompt: object): Promise<{ prompt_id: string }>
|
||||
|
||||
/**
|
||||
* Interrupt current execution
|
||||
*/
|
||||
interrupt(): Promise<void>
|
||||
}
|
||||
|
||||
export interface NodeDef {
|
||||
name: string
|
||||
category: string
|
||||
display_name?: string
|
||||
description?: string
|
||||
python_module: string
|
||||
}
|
||||
|
||||
export enum NodeStatus {
|
||||
IDLE = 'idle',
|
||||
QUEUED = 'queued',
|
||||
RUNNING = 'running'
|
||||
}
|
||||
|
||||
export interface WorkflowMetadata {
|
||||
title?: string
|
||||
description?: string
|
||||
author?: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export class WorkflowManager {
|
||||
workflows: Map<string, object>
|
||||
|
||||
constructor()
|
||||
|
||||
loadWorkflow(id: string): Promise<object>
|
||||
saveWorkflow(id: string, data: object): Promise<void>
|
||||
}
|
||||
|
||||
export type NodeId = string
|
||||
192
demo-snapshots/v1.29.0.json
Normal file
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"types": {
|
||||
"NodeId": {
|
||||
"kind": "type",
|
||||
"name": "NodeId",
|
||||
"text": "export type NodeId = string;",
|
||||
"exported": true
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"ComfyApi": {
|
||||
"kind": "interface",
|
||||
"name": "ComfyApi",
|
||||
"members": [
|
||||
{
|
||||
"name": "apiURL",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "string"
|
||||
},
|
||||
{
|
||||
"name": "fileURL",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "string"
|
||||
},
|
||||
{
|
||||
"name": "queuePrompt",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "object",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<{ prompt_id: string }>"
|
||||
},
|
||||
{
|
||||
"name": "interrupt",
|
||||
"kind": "method",
|
||||
"parameters": [],
|
||||
"returnType": "Promise<void>"
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
},
|
||||
"NodeDef": {
|
||||
"kind": "interface",
|
||||
"name": "NodeDef",
|
||||
"members": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "display_name",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "python_module",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
},
|
||||
"WorkflowMetadata": {
|
||||
"kind": "interface",
|
||||
"name": "WorkflowMetadata",
|
||||
"members": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"NodeStatus": {
|
||||
"kind": "enum",
|
||||
"name": "NodeStatus",
|
||||
"members": [
|
||||
{
|
||||
"name": "IDLE",
|
||||
"value": "\"idle\""
|
||||
},
|
||||
{
|
||||
"name": "QUEUED",
|
||||
"value": "\"queued\""
|
||||
},
|
||||
{
|
||||
"name": "RUNNING",
|
||||
"value": "\"running\""
|
||||
}
|
||||
],
|
||||
"exported": true
|
||||
}
|
||||
},
|
||||
"functions": {},
|
||||
"classes": {
|
||||
"WorkflowManager": {
|
||||
"kind": "class",
|
||||
"name": "WorkflowManager",
|
||||
"members": [
|
||||
{
|
||||
"name": "workflows",
|
||||
"type": "Map<string, object>",
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "loadWorkflow",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<object>",
|
||||
"visibility": "public"
|
||||
},
|
||||
{
|
||||
"name": "saveWorkflow",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"type": "object",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<void>",
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
}
|
||||
},
|
||||
"constants": {}
|
||||
}
|
||||
92
demo-snapshots/v1.30.2.d.ts
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Mock TypeScript definitions representing v1.30.2 API surface
|
||||
* This represents the public API with several breaking changes and additions
|
||||
*/
|
||||
|
||||
export interface ComfyApi {
|
||||
/**
|
||||
* Get API URL for backend calls
|
||||
*/
|
||||
apiURL(path: string): string
|
||||
|
||||
/**
|
||||
* Get file URL for static resources
|
||||
*/
|
||||
fileURL(path: string): string
|
||||
|
||||
/**
|
||||
* Queue a prompt for execution (async version)
|
||||
*/
|
||||
queuePromptAsync(
|
||||
prompt: object,
|
||||
options?: { priority?: number }
|
||||
): Promise<{ prompt_id: string; number: number }>
|
||||
|
||||
/**
|
||||
* Cancel a queued prompt
|
||||
*/
|
||||
cancelPrompt(prompt_id: string): Promise<void>
|
||||
|
||||
/**
|
||||
* Interrupt current execution
|
||||
*/
|
||||
interrupt(): Promise<void>
|
||||
|
||||
/**
|
||||
* Get queue status
|
||||
*/
|
||||
getQueueStatus(): Promise<{ queue_running: any[]; queue_pending: any[] }>
|
||||
}
|
||||
|
||||
export interface NodeDef {
|
||||
name: string
|
||||
category: string
|
||||
display_name?: string
|
||||
description?: string
|
||||
python_module: string
|
||||
input: {
|
||||
required?: Record<string, any>
|
||||
optional?: Record<string, any>
|
||||
}
|
||||
output: string[]
|
||||
output_name: string[]
|
||||
}
|
||||
|
||||
export enum NodeStatus {
|
||||
IDLE = 'idle',
|
||||
QUEUED = 'queued',
|
||||
RUNNING = 'running',
|
||||
ERROR = 'error',
|
||||
COMPLETED = 'completed'
|
||||
}
|
||||
|
||||
export interface WorkflowMetadata {
|
||||
title?: string
|
||||
description?: string
|
||||
author?: string
|
||||
version?: string
|
||||
tags?: string[]
|
||||
thumbnail?: string
|
||||
}
|
||||
|
||||
export interface ExtensionMetadata {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export class WorkflowManager {
|
||||
workflows: Map<string, object>
|
||||
cache: Map<string, object>
|
||||
|
||||
constructor()
|
||||
|
||||
loadWorkflow(id: string): Promise<object>
|
||||
saveWorkflow(id: string, data: object): Promise<void>
|
||||
deleteWorkflow(id: string): Promise<void>
|
||||
searchWorkflows(query: string): Promise<object[]>
|
||||
}
|
||||
|
||||
export type NodeId = string
|
||||
export type WorkflowId = string
|
||||
311
demo-snapshots/v1.30.2.json
Normal file
@@ -0,0 +1,311 @@
|
||||
{
|
||||
"types": {
|
||||
"NodeId": {
|
||||
"kind": "type",
|
||||
"name": "NodeId",
|
||||
"text": "export type NodeId = string;",
|
||||
"exported": true
|
||||
},
|
||||
"WorkflowId": {
|
||||
"kind": "type",
|
||||
"name": "WorkflowId",
|
||||
"text": "export type WorkflowId = string;",
|
||||
"exported": true
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"ComfyApi": {
|
||||
"kind": "interface",
|
||||
"name": "ComfyApi",
|
||||
"members": [
|
||||
{
|
||||
"name": "apiURL",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "string"
|
||||
},
|
||||
{
|
||||
"name": "fileURL",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "string"
|
||||
},
|
||||
{
|
||||
"name": "queuePromptAsync",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "object",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"type": "{ priority?: number }",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<{ prompt_id: string; number: number }>"
|
||||
},
|
||||
{
|
||||
"name": "cancelPrompt",
|
||||
"kind": "method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "prompt_id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<void>"
|
||||
},
|
||||
{
|
||||
"name": "interrupt",
|
||||
"kind": "method",
|
||||
"parameters": [],
|
||||
"returnType": "Promise<void>"
|
||||
},
|
||||
{
|
||||
"name": "getQueueStatus",
|
||||
"kind": "method",
|
||||
"parameters": [],
|
||||
"returnType": "Promise<{ queue_running: any[]; queue_pending: any[] }>"
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
},
|
||||
"NodeDef": {
|
||||
"kind": "interface",
|
||||
"name": "NodeDef",
|
||||
"members": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "display_name",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "python_module",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "input",
|
||||
"type": "{\n required?: Record<string, any>;\n optional?: Record<string, any>;\n }",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "output",
|
||||
"type": "string[]",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "output_name",
|
||||
"type": "string[]",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
},
|
||||
"WorkflowMetadata": {
|
||||
"kind": "interface",
|
||||
"name": "WorkflowMetadata",
|
||||
"members": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "string[]",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "thumbnail",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
},
|
||||
"ExtensionMetadata": {
|
||||
"kind": "interface",
|
||||
"name": "ExtensionMetadata",
|
||||
"members": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"NodeStatus": {
|
||||
"kind": "enum",
|
||||
"name": "NodeStatus",
|
||||
"members": [
|
||||
{
|
||||
"name": "IDLE",
|
||||
"value": "\"idle\""
|
||||
},
|
||||
{
|
||||
"name": "QUEUED",
|
||||
"value": "\"queued\""
|
||||
},
|
||||
{
|
||||
"name": "RUNNING",
|
||||
"value": "\"running\""
|
||||
},
|
||||
{
|
||||
"name": "ERROR",
|
||||
"value": "\"error\""
|
||||
},
|
||||
{
|
||||
"name": "COMPLETED",
|
||||
"value": "\"completed\""
|
||||
}
|
||||
],
|
||||
"exported": true
|
||||
}
|
||||
},
|
||||
"functions": {},
|
||||
"classes": {
|
||||
"WorkflowManager": {
|
||||
"kind": "class",
|
||||
"name": "WorkflowManager",
|
||||
"members": [
|
||||
{
|
||||
"name": "workflows",
|
||||
"type": "Map<string, object>",
|
||||
"visibility": "public"
|
||||
},
|
||||
{
|
||||
"name": "cache",
|
||||
"type": "Map<string, object>",
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "loadWorkflow",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<object>",
|
||||
"visibility": "public"
|
||||
},
|
||||
{
|
||||
"name": "saveWorkflow",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"type": "object",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<void>",
|
||||
"visibility": "public"
|
||||
},
|
||||
{
|
||||
"name": "deleteWorkflow",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<void>",
|
||||
"visibility": "public"
|
||||
},
|
||||
{
|
||||
"name": "searchWorkflows",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"type": "string",
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"returnType": "Promise<object[]>",
|
||||
"visibility": "public"
|
||||
}
|
||||
],
|
||||
"exported": true,
|
||||
"heritage": []
|
||||
}
|
||||
},
|
||||
"constants": {}
|
||||
}
|
||||
40
docs/API-CHANGELOG.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Public API Changelog
|
||||
|
||||
This changelog documents changes to the ComfyUI Frontend public API surface across versions. The public API surface includes types, interfaces, and objects used by third-party extensions and custom nodes.
|
||||
|
||||
**Important**: This is an automatically generated changelog based on TypeScript type definitions. Breaking changes are marked with ⚠️.
|
||||
|
||||
## What is tracked
|
||||
|
||||
This changelog tracks changes to the following public API components exported from `@comfyorg/comfyui-frontend-types`:
|
||||
|
||||
- **Type Aliases**: Type definitions used by extensions
|
||||
- **Interfaces**: Object shapes and contracts
|
||||
- **Enums**: Enumerated values
|
||||
- **Functions**: Public utility functions
|
||||
- **Classes**: Exported classes and their public members
|
||||
- **Constants**: Public constant values
|
||||
|
||||
## Migration Guide
|
||||
|
||||
When breaking changes occur, refer to the specific version section below for:
|
||||
- What changed
|
||||
- Why it changed (if applicable)
|
||||
- How to migrate your code
|
||||
|
||||
---
|
||||
|
||||
<!-- Automated changelog entries will be added below -->
|
||||
## v1.32.4 (2025-11-10)
|
||||
|
||||
Comparing v1.32.3 → v1.32.4. This changelog documents changes to the public API surface that third-party extensions and custom nodes depend on.
|
||||
|
||||
### 🔄 Modifications
|
||||
|
||||
**Interfaces**
|
||||
|
||||
- [`ComfyExtension`](https://github.com/Comfy-Org/ComfyUI_frontend/blob/e36e25ebd614c1c996e66b5c382b6b1b1bd4587a/src/types/comfy.ts#L98)
|
||||
- ✨ Added member: `actionBarButtons`
|
||||
|
||||
---
|
||||
|
||||