comfy-qa 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comfy-qa",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "ComfyUI QA automation CLI",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,7 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
- import { fetchPR, fetchIssue, parseRef } from "../utils/github";
4
- import { detectRunningInstance, bootstrapWorkspace, type ComfyUIInstance } from "../utils/comfyui";
3
+ import { fetchPR, fetchIssue, parseRef, fetchDeploymentPreviewUrl } from "../utils/github";
4
+ import { detectRunningInstance, bootstrapWorkspace, type ComfyUIInstance, COMFYUI_REPOS, REPO_PROD_URLS } from "../utils/comfyui";
5
5
  import { researchPR, researchIssue } from "./research";
6
6
  import { startRecorder, navigateWithHUD } from "../browser/recorder";
7
7
  import { runScenarioWithAgent, runScenarioResearchOnly } from "./browser-agent";
@@ -83,41 +83,57 @@ export async function runQA(opts: QAOptions): Promise<void> {
83
83
  if (record) {
84
84
  console.log(`\n[3/5] Recording phase — Playwright + HUD…`);
85
85
 
86
- // Resolve ComfyUI URL: explicit flag → auto-detect → auto-bootstrap
87
- let comfyUrl = opts.comfyUrl || await detectRunningInstance();
86
+ // Resolve target URL:
87
+ // ComfyUI repos detect local instance or bootstrap
88
+ // Other web-app repos → Vercel/preview URL from PR comments, then prod fallback
89
+ let comfyUrl = opts.comfyUrl ?? null;
88
90
  let bootstrappedInstance: ComfyUIInstance | null = null;
89
91
 
90
92
  if (!comfyUrl) {
91
- // Auto clone + build the target repo
92
- console.log(` [bootstrap] No running instance — cloning & building target repo…`);
93
- const prBranch = targetType === "pr"
94
- ? (target as Awaited<ReturnType<typeof fetchPR>>).headRefName
95
- : undefined;
96
-
97
- // Clone workspace first so we can set up QA skill before bootstrapping
98
- const wsPath = await cloneWorkspace({
99
- owner: parsed.owner,
100
- repo: parsed.repo,
101
- outputBase,
102
- branch: prBranch,
103
- prNumber: targetType === "pr" ? parsed.number : undefined,
104
- });
105
-
106
- // Ensure QA skill exists (creates comfy-qa branch + generates files if missing)
107
- await ensureQASkill(wsPath);
108
-
109
- try {
110
- bootstrappedInstance = await bootstrapWorkspace({
111
- owner: parsed.owner,
112
- repo: parsed.repo,
113
- outputBase,
114
- branch: prBranch,
115
- prNumber: targetType === "pr" ? parsed.number : undefined,
116
- });
117
- comfyUrl = bootstrappedInstance.url;
118
- } catch (err) {
119
- console.log(` [bootstrap] Failed: ${err}`);
120
- console.log(` [bootstrap] Falling back to research-only mode`);
93
+ if (COMFYUI_REPOS.has(parsed.repo)) {
94
+ comfyUrl = await detectRunningInstance();
95
+ if (!comfyUrl) {
96
+ console.log(` [bootstrap] No running ComfyUI — cloning & building target repo…`);
97
+ const prBranch = targetType === "pr"
98
+ ? (target as Awaited<ReturnType<typeof fetchPR>>).headRefName
99
+ : undefined;
100
+
101
+ const wsPath = await cloneWorkspace({
102
+ owner: parsed.owner,
103
+ repo: parsed.repo,
104
+ outputBase,
105
+ branch: prBranch,
106
+ prNumber: targetType === "pr" ? parsed.number : undefined,
107
+ });
108
+ await ensureQASkill(wsPath);
109
+
110
+ try {
111
+ bootstrappedInstance = await bootstrapWorkspace({
112
+ owner: parsed.owner,
113
+ repo: parsed.repo,
114
+ outputBase,
115
+ branch: prBranch,
116
+ prNumber: targetType === "pr" ? parsed.number : undefined,
117
+ });
118
+ comfyUrl = bootstrappedInstance.url;
119
+ } catch (err) {
120
+ console.log(` [bootstrap] Failed: ${err}`);
121
+ console.log(` [bootstrap] Falling back to research-only mode`);
122
+ }
123
+ }
124
+ } else {
125
+ // Non-ComfyUI repo: use Vercel/preview URL or production fallback
126
+ if (targetType === "pr") {
127
+ console.log(` [target] Fetching deployment preview URL from PR comments…`);
128
+ comfyUrl = await fetchDeploymentPreviewUrl(parsed.owner, parsed.repo, parsed.number);
129
+ if (comfyUrl) {
130
+ console.log(` [target] Preview URL: ${comfyUrl}`);
131
+ }
132
+ }
133
+ if (!comfyUrl) {
134
+ comfyUrl = REPO_PROD_URLS[parsed.repo] ?? null;
135
+ if (comfyUrl) console.log(` [target] Using production URL: ${comfyUrl}`);
136
+ }
121
137
  }
122
138
  }
123
139
 
@@ -11,6 +11,16 @@ export interface ComfyUIInstance {
11
11
  stop: () => Promise<void>;
12
12
  }
13
13
 
14
+ /** Repos that ARE ComfyUI itself — use detectRunningInstance() for these */
15
+ export const COMFYUI_REPOS = new Set(["ComfyUI", "ComfyUI_frontend"]);
16
+
17
+ /** Production URLs for known web-app repos (fallback when no preview URL found) */
18
+ export const REPO_PROD_URLS: Record<string, string> = {
19
+ "registry-web": "https://registry.comfy.org",
20
+ "website": "https://www.comfy.org",
21
+ "comfy-portal": "https://comfy.org",
22
+ };
23
+
14
24
  /** Known repo dependency graph: repo → related repos to clone into tmp/ */
15
25
  const RELATED_REPOS: Record<string, { owner: string; repo: string; setup?: string }[]> = {
16
26
  // Web UIs testable with Playwright — real backend, no mocks
@@ -95,3 +95,25 @@ export async function fetchRecentIssues(owner: string, repo: string, limit = 20)
95
95
  const json = await $`gh issue list --repo ${owner}/${repo} --limit ${limit} --state open --json number,title,body,state,author,labels,url`.text();
96
96
  return JSON.parse(json);
97
97
  }
98
+
99
+ /** Extract the first Vercel/Netlify/preview deployment URL from PR bot comments */
100
+ export async function fetchDeploymentPreviewUrl(owner: string, repo: string, prNumber: number): Promise<string | null> {
101
+ const commentsJson = await $`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate`.text();
102
+ const comments: { user: { login: string }; body: string }[] = JSON.parse(commentsJson);
103
+
104
+ for (const c of comments) {
105
+ const login = c.user?.login ?? "";
106
+ // Only trust bot comments
107
+ if (!login.includes("bot") && login !== "github-actions") continue;
108
+
109
+ // Extract first https URL from known deploy platforms
110
+ const match = c.body.match(/https:\/\/[^\s\)\"\'<]+(?:vercel\.app|netlify\.app|pages\.dev|fly\.dev|railway\.app)[^\s\)\"\'<]*/);
111
+ if (match) {
112
+ // Skip feedback/avatar/non-app URLs
113
+ const url = match[0].replace(/[.,;]+$/, "");
114
+ if (url.includes("feedback") || url.includes("avatar") || url.includes("badge") || url.includes("svg")) continue;
115
+ return url;
116
+ }
117
+ }
118
+ return null;
119
+ }