#!/usr/bin/env node import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; const REPO = 'bep40/semantic-image-field'; const CLONE = '/tmp/space'; const STABLE_BUNDLE = 'assets/index.js'; const STABLE_STYLE = 'assets/app.css'; const MIRROR = ['products.json', 'promos.json']; const COMMITTABLE = ['index.html', 'public/catalogue', 'dist/catalogue', 'dist/assets', 'dist/share', 'public/share']; const ok = m => console.log(`[refresh] ${m}`); function run(command, args, { cwd = CLONE } = {}) { return execFileSync(command, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 64 * 1024 * 1024 }); } function shell(script, cwd = CLONE) { return run('bash', ['-lc', script], { cwd }); } function git(...args) { return run('git', ['-c', `http.extraheader=Authorization: Bearer ${process.env.HF_TOKEN}`, ...args]); } function main() { if (!process.env.HF_TOKEN) throw new Error('HF_TOKEN required'); rmSync(CLONE, { recursive: true, force: true }); run('mkdir', ['-p', CLONE]); run('git', ['clone', '-q', `--filter=blob:none`, '--no-checkout', `https://huggingface.co/spaces/${REPO}`], { cwd: '/' }); git('checkout', '-q', '-B', 'main', 'main'); ok('cloned'); shell('set -euo pipefail; npm install --silent --no-audit --no-fund'); ok('dependencies installed'); shell('set -euo pipefail; node scripts/sync-catalogue.mjs'); for (const file of MIRROR) { const count = JSON.parse(readFileSync(`public/catalogue/${file}`, 'utf8')).length; ok(`public/catalogue/${file}: ${count} entries`); } shell('set -euo pipefail; npm run build'); ok('built'); for (const file of MIRROR) { const served = `dist/catalogue/${file}`; const source = JSON.parse(readFileSync(`public/catalogue/${file}`, 'utf8')).length; const built = JSON.parse(readFileSync(served, 'utf8')).length; if (source !== built) throw new Error(`${served} has ${built} but mirror has ${source}`); ok(`served mirror verified: ${file} (${built} entries)`); } const htmlPath = `${CLONE}/index.html`; let html = readFileSync(htmlPath, 'utf8'); if (/\.\/dist\/assets\/index\.js\b/.test(html) && /\.\/dist\/assets\/app\.css\b/.test(html)) { ok('index.html already references stable bundle'); } else { let next = html.replace(/(\.\/dist\/assets\/)index-[A-Za-z0-9_-]+\.js\b/g, `$1${STABLE_BUNDLE.slice('assets/'.length)}`); next = next.replace(/(\.\/dist\/assets\/)index-[A-Za-z0-9_-]+\.css\b/g, `$1${STABLE_STYLE.slice('assets/'.length)}`); if (next === html) throw new Error('index.html names bundle in unexpected way'); writeFileSync(htmlPath, next); ok('index.html repointed to ' + STABLE_BUNDLE); } for (const asset of [STABLE_BUNDLE, STABLE_STYLE]) { if (!existsSync(`${CLONE}/dist/${asset}`)) throw new Error(`dist/${asset} missing`); } const commit = shell(`set -euo pipefail; git config user.name "semantic-image-field refresh"; git config user.email "refresh@hf.space"; git add -- ${COMMITTABLE.map(p => `'${p}'`).join(' ')}; if git diff --cached --quiet; then echo NOCHANGE; else git commit -q -m "chore: rebuild with semantic search fix"; echo COMMITTED; fi`).trim(); if (commit.includes('COMMITTED')) { git('push', '-q', 'origin', 'main'); ok('pushed — the static Space will rebuild'); } else { ok(`no change needed (HEAD ${shell('git rev-parse --short HEAD').trim()})`); } console.log('REFRESH_OK'); } try { main(); } catch (e) { console.error('REFRESH_FAILED:', e.message, e.stderr || ''); process.exitCode = 1; }