Spaces:
Paused
Paused
Download scripts/verify.mjs from shootstuff/flux-image-space: direct link, hf CLI and curl.
- Browser
- Download file 4.92 kB
-
https://huggingface.co/spaces/shootstuff/flux-image-space/resolve/main/scripts/verify.mjs
- Command line
-
hf download hf://spaces/shootstuff/flux-image-space/scripts/verify.mjs
-
curl -L -o verify.mjs https://huggingface.co/spaces/shootstuff/flux-image-space/resolve/main/scripts/verify.mjs
4.92 kB
| /** | |
| * Build verification for Flux Studio — Zero GPU. | |
| * | |
| * Runs as `npm run build` inside the Docker image (and locally before | |
| * every push). It fails loudly if anything in the app is broken, so a | |
| * broken build can never reach the Hugging Face builder silently. | |
| * | |
| * Checks: | |
| * 1. All required files exist. | |
| * 2. server.mjs parses and imports cleanly (no listen — import only). | |
| * 3. Config defaults match the spec | |
| * (flux-2-pro-preview primary, flux-2-klein-9b fallback, | |
| * https://api.bfl.ai base, port 7860). | |
| * 4. Placeholder-key detection behaves. | |
| * 5. Request validation behaves (accepts good input, rejects bad). | |
| * 6. The image proxy host allowlist blocks non-BFL hosts. | |
| * 7. The static client ships index.html/app.js/style.css and the | |
| * client never references the API key. | |
| */ | |
| import fs from 'node:fs'; | |
| import path from 'node:path'; | |
| import { fileURLToPath } from 'node:url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const root = path.resolve(__dirname, '..'); | |
| const failures = []; | |
| function check(name, fn) { | |
| try { | |
| fn(); | |
| console.log(` ok ${name}`); | |
| } catch (err) { | |
| failures.push({ name, message: err.message }); | |
| console.error(` FAIL ${name}: ${err.message}`); | |
| } | |
| } | |
| function assert(cond, msg) { | |
| if (!cond) throw new Error(msg); | |
| } | |
| /* 1. Required files --------------------------------------------------- */ | |
| const REQUIRED = [ | |
| 'server.mjs', | |
| 'package.json', | |
| 'package-lock.json', | |
| 'Dockerfile', | |
| 'README.md', | |
| 'public/index.html', | |
| 'public/style.css', | |
| 'public/app.js', | |
| ]; | |
| check('required files exist', () => { | |
| for (const f of REQUIRED) { | |
| assert(fs.existsSync(path.join(root, f)), `missing file: ${f}`); | |
| } | |
| }); | |
| /* 2/3. Server module imports cleanly with spec defaults --------------- */ | |
| const server = await import(path.join(root, 'server.mjs')); | |
| check('config defaults match the spec', () => { | |
| const { config } = server; | |
| assert(config.modelId === 'flux-2-pro-preview', `modelId=${config.modelId}, expected flux-2-pro-preview`); | |
| assert(config.fallbackModelId === 'flux-2-klein-9b', `fallbackModelId=${config.fallbackModelId}, expected flux-2-klein-9b`); | |
| assert(config.apiBaseHost === 'api.bfl.ai', `apiBaseHost=${config.apiBaseHost}, expected api.bfl.ai`); | |
| assert(config.port === 7860, `port=${config.port}, expected 7860`); | |
| assert(typeof config.ready === 'boolean', 'config.ready should be boolean'); | |
| }); | |
| /* 4. Placeholder detection -------------------------------------------- */ | |
| check('placeholder key detection', () => { | |
| assert(server.isPlaceholderKey('') === true, 'empty key must count as placeholder'); | |
| assert(server.isPlaceholderKey('placeholder-replace-with-real-bfl-key') === true, 'placeholder value must count'); | |
| assert(server.isPlaceholderKey('sk-1234567890abcdef') === false, 'normal-looking key must not count'); | |
| }); | |
| /* 5. Validation -------------------------------------------------------- */ | |
| check('request validation', () => { | |
| const good = server.validateBody({ prompt: 'a cat', aspectRatio: '16:9', outputFormat: 'jpeg' }); | |
| assert(good.width === 1344 && good.height === 768, `aspect preset wrong: ${good.width}x${good.height}`); | |
| const seeded = server.validateBody({ prompt: 'a cat', width: 1024, height: 1024, seed: '42' }); | |
| assert(seeded.seed === 42, 'seed coercion failed'); | |
| let threw = false; | |
| try { | |
| server.validateBody({ prompt: '', width: 99999 }); | |
| } catch { | |
| threw = true; | |
| } | |
| assert(threw, 'invalid body must throw'); | |
| const payload = server.buildPayload({ prompt: 'x', width: 256, height: 256, outputFormat: 'png', seed: 7 }); | |
| assert(payload.seed === 7 && payload.output_format === 'png', 'BFL payload mapping wrong'); | |
| }); | |
| /* 6. Image proxy allowlist --------------------------------------------- */ | |
| check('image proxy host allowlist', () => { | |
| assert(server.isAllowedDeliveryHost('delivery.bfl.ai') === true, 'delivery.bfl.ai should be allowed'); | |
| assert(server.isAllowedDeliveryHost('bflai.blob.core.windows.net') === true, 'azure blob host should be allowed'); | |
| assert(server.isAllowedDeliveryHost('evil.example.com') === false, 'random host must be blocked'); | |
| assert(server.isAllowedDeliveryHost('bfl.ai.evil.com') === false, 'suffix spoof must be blocked'); | |
| }); | |
| /* 7. Client hygiene ---------------------------------------------------- */ | |
| check('client has no server secret access', () => { | |
| const appJs = fs.readFileSync(path.join(root, 'public/app.js'), 'utf8'); | |
| assert(!/BFL_API_KEY\s*=/.test(appJs), 'client must not assign BFL_API_KEY'); | |
| assert(!/x-key/i.test(appJs), 'client must not set the x-key header'); | |
| }); | |
| /* ---------------------------------------------------------------------- */ | |
| console.log(''); | |
| if (failures.length) { | |
| console.error(`BUILD FAILED — ${failures.length} check(s) failed.`); | |
| process.exit(1); | |
| } else { | |
| console.log('BUILD OK — all checks passed.'); | |
| process.exit(0); | |
| } | |