Files
ComfyUI_frontend/tests-ui/extractExamples.ts
Chenlei Hu 5611e90fda Add ts-strict-ignore plugin (#1235)
* Add ts-strict-ignore plugin

* nit

* Add to typecheck script
2024-10-12 11:56:49 -04:00

172 lines
4.8 KiB
TypeScript

// @ts-strict-ignore
/*
Script to generate test API json from the ComfyUI_examples repo.
Requires the repo to be cloned to the tests-ui directory or specified via the EXAMPLE_REPO_PATH env var.
*/
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'node:url'
import { getFromPngBuffer } from '@/scripts/metadata/png'
import { getFromFlacBuffer } from '@/scripts/metadata/flac'
import dotenv from 'dotenv'
dotenv.config()
const dirname = path.dirname(fileURLToPath(import.meta.url))
const repoPath =
process.env.EXAMPLE_REPO_PATH || path.resolve(dirname, 'ComfyUI_examples')
const workflowsPath = path.resolve(dirname, 'workflows', 'examples')
if (!fs.existsSync(repoPath)) {
console.error(
`ComfyUI_examples repo not found. Please clone this to ${repoPath} or set the EXAMPLE_REPO_PATH env var (see .env_example) and re-run.`
)
}
if (!fs.existsSync(workflowsPath)) {
await fs.promises.mkdir(workflowsPath)
}
async function* getFiles(
dir: string,
...exts: string[]
): AsyncGenerator<string, void, void> {
const dirents = await fs.promises.readdir(dir, { withFileTypes: true })
for (const dirent of dirents) {
const res = path.resolve(dir, dirent.name)
if (dirent.isDirectory()) {
yield* getFiles(res, ...exts)
} else if (exts.includes(path.extname(res))) {
yield res
}
}
}
async function validateMetadata(metadata: Record<string, string>) {
const check = (prop: 'prompt' | 'workflow') => {
const v = metadata?.[prop]
if (!v) throw `${prop} not found in metadata`
try {
JSON.parse(v)
} catch (error) {
throw `${prop} invalid json: ${error.message}`
}
return v
}
return { prompt: check('prompt'), workflow: check('workflow') }
}
async function hasExampleChanged(
existingFilePath: string,
exampleJson: string
) {
return exampleJson !== (await fs.promises.readFile(existingFilePath, 'utf8'))
}
// Example images to ignore as they don't contain workflows
const ignore = [
'unclip_sunset.png',
'unclip_mountains.png',
'inpaint_yosemite_inpaint_example.png',
'controlnet_shark_depthmap.png',
'controlnet_pose_worship.png',
'controlnet_pose_present.png',
'controlnet_input_scribble_example.png',
'controlnet_house_scribble.png'
]
// Find all existing examples so we can check if any are removed/changed
const existing = new Set(
(await fs.promises.readdir(workflowsPath, { withFileTypes: true }))
.filter((d) => d.isFile())
.map((d) => path.resolve(workflowsPath, d.name))
)
const results = {
new: [],
changed: [],
unchanged: [],
missing: [],
failed: []
}
let total = 0
for await (const file of getFiles(repoPath, '.png', '.flac')) {
const cleanedName = path
.relative(repoPath, file)
.replaceAll('/', '_')
.replaceAll('\\', '_')
if (ignore.includes(cleanedName)) continue
total++
let metadata: { prompt: string; workflow: string }
try {
const { buffer } = await fs.promises.readFile(file)
switch (path.extname(file)) {
case '.png':
metadata = await validateMetadata(getFromPngBuffer(buffer))
break
case '.flac':
metadata = await validateMetadata(getFromFlacBuffer(buffer))
break
}
const outPath = path.resolve(workflowsPath, cleanedName + '.json')
const exampleJson = JSON.stringify(metadata)
if (existing.has(outPath)) {
existing.delete(outPath)
if (await hasExampleChanged(outPath, exampleJson)) {
results.changed.push(outPath)
} else {
// Unchanged, no point in re-saving
results.unchanged.push(outPath)
continue
}
} else {
results.new.push(outPath)
}
await fs.promises.writeFile(outPath, exampleJson, 'utf8')
} catch (error) {
results.failed.push({ file, error })
}
}
// Any workflows left in the existing set are now missing, these will want checking and manually removing
results.missing.push(...existing)
const c = (v: number, gt0: 'red' | 'yellow' | 'green') =>
chalk[v > 0 ? gt0 : 'gray'](v)
console.log(`Processed ${chalk.green(total)} examples`)
console.log(` ${chalk.gray(results.unchanged.length)} unchanged`)
console.log(` ${c(results.changed.length, 'yellow')} changed`)
console.log(` ${c(results.new.length, 'green')} new`)
console.log(` ${c(results.missing.length, 'red')} missing`)
console.log(` ${c(results.failed.length, 'red')} failed`)
if (results.missing.length) {
console.log()
console.log(
chalk.red(
'The following examples are missing and require manual reviewing & removal:'
)
)
for (const m of results.missing) {
console.log(m)
}
}
if (results.failed.length) {
console.log()
console.log(chalk.red('The following examples failed to extract:'))
for (const m of results.failed) {
console.log(m.file)
console.error(m.error)
console.log()
}
}