fix(website): query every raw-id alias when pack slugs collide

Addresses Oracle review feedback: when two raw upstream ids slugify to
the same URL slug (e.g. ComfyUI-QwenVL + ComfyUI_QwenVL both -> comfyui-qwenvl)
the previous merge kept only the first rawId and used only that single
alias to fetch registry metadata. If that one alias missed but its twin
would have resolved, the merged pack lost banner/icon/license info.

Now NodePack carries rawIds: string[] holding every raw alias seen for
the slug. parseCloudNodes flattens all aliases into a single registry
batch and pickRegistryPack walks the alias list in insertion order to
find the first non-null hit.
This commit is contained in:
Glary-Bot
2026-05-14 20:07:27 +00:00
parent 30c106d972
commit b58d24403b
4 changed files with 79 additions and 5 deletions

View File

@@ -345,6 +345,53 @@ describe('fetchCloudNodesForBuild', () => {
)
})
it('queries every raw-id alias when packs collide on the same slug and picks the first hit', async () => {
fetchRegistryPacksMock.mockResolvedValue(
new Map<string, unknown>([
['ComfyUI-QwenVL', null],
[
'ComfyUI_QwenVL',
{
id: 'ComfyUI_QwenVL',
name: 'ComfyUI QwenVL',
repository: 'https://github.com/example/ComfyUI_QwenVL'
}
]
])
)
const fetchImpl = vi.fn(async () =>
response({
QwenDash: validNode({
name: 'QwenDash',
python_module: 'custom_nodes.ComfyUI-QwenVL.nodes'
}),
QwenUnder: validNode({
name: 'QwenUnder',
python_module: 'custom_nodes.ComfyUI_QwenVL.nodes'
})
})
)
const outcome = await fetchCloudNodesForBuild({
apiKey: KEY,
baseUrl: BASE_URL,
fetchImpl: fetchImpl as typeof fetch
})
expect(outcome.status).toBe('fresh')
if (outcome.status !== 'fresh') return
expect(outcome.snapshot.packs).toHaveLength(1)
expect(outcome.snapshot.packs[0]?.id).toBe('comfyui-qwenvl')
expect(outcome.snapshot.packs[0]?.registryId).toBe('ComfyUI_QwenVL')
expect(outcome.snapshot.packs[0]?.repoUrl).toBe(
'https://github.com/example/ComfyUI_QwenVL'
)
expect(fetchRegistryPacksMock).toHaveBeenCalledWith(
['ComfyUI-QwenVL', 'ComfyUI_QwenVL'],
expect.anything()
)
})
it('normalizes pack ids when reading a fallback snapshot', async () => {
const snapshotUrl = withSnapshotDir({
fetchedAt: '2026-04-01T00:00:00.000Z',

View File

@@ -238,12 +238,12 @@ async function parseCloudNodes(
)
const grouped = groupNodesByPack(sanitizedDefs)
const allAliases = grouped.flatMap((pack) => pack.rawIds)
let registryMap = new Map<string, RegistryPack | null>()
try {
registryMap = await fetchRegistryPacks(
grouped.map((pack) => pack.rawId),
{ fetchImpl: options.fetchImpl }
)
registryMap = await fetchRegistryPacks(allAliases, {
fetchImpl: options.fetchImpl
})
} catch {
registryMap = new Map()
}
@@ -253,13 +253,24 @@ async function parseCloudNodes(
pack.id,
pack.displayName,
pack.nodes,
registryMap.get(pack.rawId)
pickRegistryPack(registryMap, pack.rawIds)
)
)
return { kind: 'ok', packs, droppedNodes }
}
function pickRegistryPack(
registryMap: Map<string, RegistryPack | null>,
aliases: readonly string[]
): RegistryPack | null | undefined {
for (const alias of aliases) {
const hit = registryMap.get(alias)
if (hit) return hit
}
return registryMap.get(aliases[0])
}
function safeExternalUrl(value: string | undefined): string | undefined {
if (!value) return undefined
try {

View File

@@ -92,6 +92,17 @@ describe('groupNodesByPack', () => {
'QwenA',
'QwenB'
])
expect(grouped[0].rawIds).toEqual(['ComfyUI-QwenVL', 'ComfyUI_QwenVL'])
})
it('does not record duplicate aliases when the same raw id appears twice', () => {
const grouped = groupNodesByPack({
QwenA: makeNodeDef('QwenA', 'custom_nodes.ComfyUI-QwenVL.nodes'),
QwenB: makeNodeDef('QwenB', 'custom_nodes.ComfyUI-QwenVL.nodes')
})
expect(grouped).toHaveLength(1)
expect(grouped[0].rawIds).toEqual(['ComfyUI-QwenVL'])
})
it('strips version suffix before slugifying', () => {

View File

@@ -10,6 +10,7 @@ export interface PackedNode {
export interface NodePack {
id: string
rawId: string
rawIds: string[]
displayName: string
nodes: PackedNode[]
}
@@ -40,12 +41,16 @@ export function groupNodesByPack(
if (existing) {
existing.nodes.push(node)
if (!existing.rawIds.includes(rawId)) {
existing.rawIds.push(rawId)
}
continue
}
byPackId.set(slug, {
id: slug,
rawId,
rawIds: [rawId],
displayName: source.displayText,
nodes: [node]
})