import fs from 'fs/promises'; import path from 'path'; import { spawn } from 'child_process'; const ROOT = process.cwd(); const REPO = process.env.BOOSAPI_WORKFLOW_UI_REPO || ''; const VARIANT = (process.env.BOOSAPI_WORKFLOW_UI_VARIANT || process.argv[2] || 'all').toLowerCase(); const DEFAULT_EXTERNAL_REF = 'v1.14.6'; const DEFAULT_CLOUD_REF = 'v1.46.2'; const SUBMODULE_DIR = path.join(ROOT, 'vendor', 'ComfyUI_frontend'); const CACHE_DIR = path.join(ROOT, '.cache', 'comfyui-frontend'); const DEFAULT_DIST = path.join(ROOT, 'dist', 'workflow-ui'); // External/RunningHub currently targets ComfyUI 0.3.27, where v1.14.x is the // most stable match. Cloud tracks the newer ComfyUI_frontend release line. const variants = { external: { ref: process.env.BOOSAPI_WORKFLOW_UI_EXTERNAL_REF || process.env.BOOSAPI_WORKFLOW_UI_REF || DEFAULT_EXTERNAL_REF, dist: process.env.BOOSAPI_WORKFLOW_UI_EXTERNAL_DIST || path.join(ROOT, 'dist', 'workflow-ui-external'), description: 'External/RunningHub compatible frontend', }, cloud: { ref: process.env.BOOSAPI_WORKFLOW_UI_CLOUD_REF || process.env.BOOSAPI_WORKFLOW_UI_REF || DEFAULT_CLOUD_REF, dist: process.env.BOOSAPI_WORKFLOW_UI_CLOUD_DIST || path.join(ROOT, 'dist', 'workflow-ui-cloud'), description: 'Comfy Cloud frontend', }, }; function run(command, args, options = {}) { return new Promise((resolve, reject) => { const env = { ...process.env, ...(options.env || {}) }; if (options.cleanPackageEnv) { Object.keys(env) .filter((key) => key.startsWith('npm_package_')) .forEach((key) => { delete env[key]; }); Object.assign(env, options.env || {}); } const child = spawn(command, args, { cwd: options.cwd || ROOT, env, stdio: 'inherit', shell: process.platform === 'win32', }); child.on('error', reject); child.on('exit', (code) => { if (code === 0) resolve(); else reject(new Error(`${command} ${args.join(' ')} exited with ${code}`)); }); }); } function selectedVariants() { if (VARIANT === 'all') return Object.entries(variants); if (variants[VARIANT]) return [[VARIANT, variants[VARIANT]]]; throw new Error(`Unknown workflow-ui variant: ${VARIANT}. Use external, cloud, or all.`); } async function readPackageInfo(dir) { const raw = await fs.readFile(path.join(dir, 'package.json'), 'utf8'); return JSON.parse(raw); } async function buildVariant(name, config) { const ref = config.ref; const dist = config.dist; const dir = path.join(CACHE_DIR, `${name}-${ref.replace(/[^a-zA-Z0-9._-]/g, '_')}`); console.log(`\n==> Building workflow-ui:${name} (${config.description}) from ${ref}`); await fs.mkdir(path.dirname(CACHE_DIR), { recursive: true }); await fs.rm(dir, { recursive: true, force: true }); if (REPO) { await run('git', ['clone', '--depth', '1', '--branch', ref, REPO, dir]); } else { const hasSubmodule = await fs.access(path.join(SUBMODULE_DIR, 'package.json')).then(() => true, () => false); if (!hasSubmodule) { await run('git', ['submodule', 'update', '--init', '--recursive', 'vendor/ComfyUI_frontend']); } await run('git', ['fetch', '--tags', 'origin', ref], { cwd: SUBMODULE_DIR }); await run('git', ['clone', '--shared', SUBMODULE_DIR, dir]); await run('git', ['-c', 'advice.detachedHead=false', 'checkout', '--quiet', ref], { cwd: dir }); await run('git', ['submodule', 'update', '--init', '--recursive'], { cwd: dir }); } const packageInfo = await readPackageInfo(dir); const packageEnv = { npm_package_name: packageInfo.name || '', npm_package_version: packageInfo.version || ref.replace(/^v/, ''), }; const hasPnpmLock = await fs.access(path.join(dir, 'pnpm-lock.yaml')).then(() => true, () => false); if (hasPnpmLock) { await run('pnpm', ['install', '--frozen-lockfile'], { cwd: dir }); await run('pnpm', ['exec', 'vite', 'build', '--config', 'vite.config.mts'], { cwd: dir, cleanPackageEnv: true, env: packageEnv, }); } else { await run('npm', ['ci'], { cwd: dir }); await run('npm', ['run', 'build'], { cwd: dir, cleanPackageEnv: true, env: packageEnv, }); } await fs.rm(dist, { recursive: true, force: true }); await fs.cp(path.join(dir, 'dist'), dist, { recursive: true }); await fs.writeFile( path.join(dist, 'boosapi-workflow-ui.json'), `${JSON.stringify({ variant: name, ref, packageName: packageInfo.name || null, packageVersion: packageInfo.version || null, }, null, 2)}\n`, ); console.log(`workflow-ui:${name} installed to ${dist}`); if (name === 'cloud') { await fs.rm(DEFAULT_DIST, { recursive: true, force: true }); await fs.cp(dist, DEFAULT_DIST, { recursive: true }); console.log(`workflow-ui default compatibility copy installed to ${DEFAULT_DIST}`); } } for (const [name, config] of selectedVariants()) { await buildVariant(name, config); }