mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 06:35:10 +00:00
*PR Created by the Glary-Bot Agent* --- ## Summary `Release: Website` only refreshed the Ashby snapshot, so the cloud-nodes snapshot (`apps/website/src/data/cloud-nodes.snapshot.json`) was stale on every release. `loadPacksForBuild()` then silently fell back to that snapshot because `WEBSITE_CLOUD_API_KEY` was never plumbed through CI or Vercel, leaving production at `/cloud/supported-nodes` with placeholder data (e.g. `rgthree-comfy` listed as supported when it isn't — visible at line 104 of the committed snapshot, last fetched 2026-05-04). ## Changes - **New composite action `.github/actions/cloud-nodes-pull`** mirroring `ashby-pull`: runs `pnpm --filter @comfyorg/website cloud-nodes:refresh-snapshot` with `WEBSITE_CLOUD_API_KEY`. The script already `process.exit(1)`s on any non-`fresh` outcome, so refresh failures are loud. - **`release-website.yaml`** now runs both refreshes and opens a single PR with both updated snapshots. Renamed the job to `refresh-snapshots`, updated branch/commit/title/body for the wider scope, and kept the existing `Release:Website` label so downstream automation is unaffected. - **`cloudNodes.build.ts`** throws when the outcome is `'stale'` **and** `VERCEL_ENV === 'production'`. Preview / local builds keep the snapshot fallback so contributors without key access are unaffected. The CI reporter still runs first so the GitHub annotation explaining *why* it's stale is visible in the failed job. - **`ci-vercel-website-preview.yaml`**: passes `WEBSITE_CLOUD_API_KEY` to `vercel build` in both preview and production jobs, and adds a preflight step on `deploy-production` that hard-fails before `vercel build --prod` if the secret is missing — surfacing config drift with a maintainer-friendly error annotation instead of mid-build. - **`apps/website/README.md`**: documents the production-strictness behavior, the new required secret (GitHub Actions + Vercel env), and the manual refresh path. - **New unit tests** in `cloudNodes.build.test.ts` (6 cases): fresh, stale-no-VERCEL_ENV, stale-on-preview, stale-on-production, failed-regardless, and "still reports on stale-in-production before throwing". ## Manual / one-time steps required before merging This PR cannot finish the job alone. A maintainer must also: 1. Add `WEBSITE_CLOUD_API_KEY` as a **GitHub Actions repo secret** in `Comfy-Org/ComfyUI_frontend`. 2. Add `WEBSITE_CLOUD_API_KEY` to the **Vercel project environment** (`production` env at minimum; `preview` recommended). 3. Investigate why `rgthree-comfy` is in the current snapshot — either the Cloud API was actually returning it on 2026-05-04, the snapshot was generated against a non-production environment, or it was hand-edited. The first manual run of `Release: Website` after this PR merges will confirm. Without step 1, the new `Release: Website` job will fail loudly (the refresh script exits 1 with `missing WEBSITE_CLOUD_API_KEY`). Without step 2, the new preflight will fail the production deploy with a clear error annotation pointing at `apps/website/README.md`. Both failure modes are intentional — they replace today's silent stale snapshot. ## Related (out of scope for this PR) The other half of the original report — production 404s on `/p/supported-models/*`, `/cloud/supported-nodes/*`, `/demos/community-workflows` from PRs #11892 / #11903 / #11942 — is a `comfy-router` allow-list gap (those paths exist in the Vercel build as pre-rendered static HTML). That fix needs to land in `Comfy-Org/comfy-router` and is being handled separately since glary doesn't have access to that repo. ## Verification - `pnpm --filter @comfyorg/website test:unit` — 75/75 pass (6 new in `cloudNodes.build.test.ts`) - `pnpm --filter @comfyorg/website typecheck` — 0 errors, 0 warnings (2 pre-existing hints unrelated to this PR) - `pnpm format` + `pnpm exec eslint` on changed files — clean - `js-yaml` validates `release-website.yaml`, `cloud-nodes-pull/action.yaml`, `ci-vercel-website-preview.yaml` - Oracle code review (round 1) raised 1 warning + 1 suggestion; both addressed in commit 2. **Manual verification not applicable**: the runtime changes are GitHub Actions workflows and a Vercel-env-gated branch in a build-time module — they cannot meaningfully run outside of GitHub Actions / Vercel, and the strict-on-stale path is exhaustively covered by the 6 unit tests (including the exact assertions a manual run would check: throws on `VERCEL_ENV=production` + stale, passes on preview, reports observability annotation before throwing). The end-to-end behavior will be verified by the first `Release: Website` dispatch and the next production deploy after the maintainer adds the secret. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12219-fix-website-refresh-cloud-nodes-snapshot-in-release-workflow-strict-production-build-35f6d73d3650816d8f32d403cb39d733) by [Unito](https://www.unito.io) --------- Co-authored-by: glary <bot@glary.dev>
129 lines
3.5 KiB
TypeScript
129 lines
3.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import type { FetchOutcome } from './cloudNodes'
|
|
import type { NodesSnapshot } from '../data/cloudNodes'
|
|
|
|
const fetchCloudNodesMock = vi.hoisted(() =>
|
|
vi.fn<() => Promise<FetchOutcome>>()
|
|
)
|
|
const reportCloudNodesOutcomeMock = vi.hoisted(() => vi.fn())
|
|
|
|
vi.mock('./cloudNodes', () => ({
|
|
fetchCloudNodesForBuild: fetchCloudNodesMock
|
|
}))
|
|
|
|
vi.mock('./cloudNodes.ci', () => ({
|
|
reportCloudNodesOutcome: reportCloudNodesOutcomeMock
|
|
}))
|
|
|
|
import { loadPacksForBuild } from './cloudNodes.build'
|
|
|
|
const SNAPSHOT: NodesSnapshot = {
|
|
fetchedAt: '2026-04-01T00:00:00.000Z',
|
|
packs: [
|
|
{
|
|
id: 'snapshot-pack',
|
|
displayName: 'Snapshot Pack',
|
|
nodes: [
|
|
{ name: 'SnapshotNode', displayName: 'Snapshot Node', category: 'x' }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
describe('loadPacksForBuild', () => {
|
|
const savedVercelEnv = process.env.VERCEL_ENV
|
|
|
|
beforeEach(() => {
|
|
fetchCloudNodesMock.mockReset()
|
|
reportCloudNodesOutcomeMock.mockReset()
|
|
delete process.env.VERCEL_ENV
|
|
})
|
|
|
|
afterEach(() => {
|
|
if (savedVercelEnv === undefined) {
|
|
delete process.env.VERCEL_ENV
|
|
return
|
|
}
|
|
process.env.VERCEL_ENV = savedVercelEnv
|
|
})
|
|
|
|
it('returns packs when fetch is fresh', async () => {
|
|
fetchCloudNodesMock.mockResolvedValue({
|
|
status: 'fresh',
|
|
snapshot: SNAPSHOT,
|
|
droppedCount: 0,
|
|
droppedNodes: []
|
|
})
|
|
|
|
const packs = await loadPacksForBuild()
|
|
expect(packs).toBe(SNAPSHOT.packs)
|
|
expect(reportCloudNodesOutcomeMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('returns snapshot packs when outcome is stale outside production', async () => {
|
|
fetchCloudNodesMock.mockResolvedValue({
|
|
status: 'stale',
|
|
snapshot: SNAPSHOT,
|
|
reason: 'missing WEBSITE_CLOUD_API_KEY'
|
|
})
|
|
|
|
const packs = await loadPacksForBuild()
|
|
expect(packs).toBe(SNAPSHOT.packs)
|
|
expect(reportCloudNodesOutcomeMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('returns snapshot packs when outcome is stale on Vercel preview', async () => {
|
|
process.env.VERCEL_ENV = 'preview'
|
|
fetchCloudNodesMock.mockResolvedValue({
|
|
status: 'stale',
|
|
snapshot: SNAPSHOT,
|
|
reason: 'HTTP 503'
|
|
})
|
|
|
|
const packs = await loadPacksForBuild()
|
|
expect(packs).toBe(SNAPSHOT.packs)
|
|
expect(reportCloudNodesOutcomeMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('throws when outcome is stale on Vercel production', async () => {
|
|
process.env.VERCEL_ENV = 'production'
|
|
fetchCloudNodesMock.mockResolvedValue({
|
|
status: 'stale',
|
|
snapshot: SNAPSHOT,
|
|
reason: 'missing WEBSITE_CLOUD_API_KEY'
|
|
})
|
|
|
|
await expect(loadPacksForBuild()).rejects.toThrow(
|
|
/stale data in a production build/
|
|
)
|
|
await expect(loadPacksForBuild()).rejects.toThrow(
|
|
/missing WEBSITE_CLOUD_API_KEY/
|
|
)
|
|
})
|
|
|
|
it('throws when outcome is failed regardless of environment', async () => {
|
|
fetchCloudNodesMock.mockResolvedValue({
|
|
status: 'failed',
|
|
reason: 'network error: ECONNREFUSED'
|
|
})
|
|
|
|
await expect(loadPacksForBuild()).rejects.toThrow(
|
|
/Cloud nodes fetch failed and no snapshot is available/
|
|
)
|
|
await expect(loadPacksForBuild()).rejects.toThrow(/ECONNREFUSED/)
|
|
})
|
|
|
|
it('still reports outcome before throwing on stale-in-production', async () => {
|
|
process.env.VERCEL_ENV = 'production'
|
|
fetchCloudNodesMock.mockResolvedValue({
|
|
status: 'stale',
|
|
snapshot: SNAPSHOT,
|
|
reason: 'HTTP 503'
|
|
})
|
|
|
|
await expect(loadPacksForBuild()).rejects.toThrow()
|
|
expect(reportCloudNodesOutcomeMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|