JAE-IMAGE-SKILL/scripts/setup.js
2026-05-01 04:05:47 +00:00

119 lines
4.4 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* setup.js - Downloads/updates the JAE Image Skill reference library.
*
* Fully dynamic: reads manifest.json first to discover all categories.
* New/renamed/removed categories are handled automatically — no hardcoding.
*
* Usage:
* node scripts/setup.js # Download missing files only
* node scripts/setup.js --force # Force re-download all references
* node scripts/setup.js --check # Auto-update if stale (> 24h)
*
* Override source if needed:
* JAE_IMAGE_SKILL_BASE_URL=https://example.com/references node scripts/setup.js --force
*/
import { existsSync, mkdirSync, statSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const refsDir = join(__dirname, '..', 'references');
const stampFile = join(refsDir, '.last-updated');
const BASE_URL = (process.env.JAE_IMAGE_SKILL_BASE_URL || 'https://jaeswift.xyz/skills/JAE-image-skill/references').replace(/\/$/, '');
const STALE_HOURS = Number(process.env.JAE_IMAGE_SKILL_STALE_HOURS || 24);
function isStale() {
if (!existsSync(stampFile)) return true;
const ts = parseInt(readFileSync(stampFile, 'utf8').trim(), 10);
return (Date.now() - ts) / 1000 / 3600 > STALE_HOURS;
}
async function fetchText(url) {
if (typeof fetch !== 'function') {
throw new Error('This installer requires Node.js 18+ with global fetch. Node 20+ is recommended.');
}
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}${url}`);
return res.text();
}
async function setup() {
const args = process.argv.slice(2);
const forceMode = args.includes('--force');
const checkMode = args.includes('--check');
if (checkMode && !isStale()) {
return;
}
if (!existsSync(refsDir)) mkdirSync(refsDir, { recursive: true });
const label = forceMode ? 'Updating' : 'Downloading';
console.log(`[setup] ${label} JAE Image Skill reference library from ${BASE_URL} ...`);
let categories;
try {
const manifestText = await fetchText(`${BASE_URL}/manifest.json`);
const manifest = JSON.parse(manifestText);
categories = manifest.categories;
if (!Array.isArray(categories) || categories.length === 0) throw new Error('manifest has no categories');
writeFileSync(join(refsDir, 'manifest.json'), manifestText, 'utf8');
console.log(` manifest: ${categories.length} categories, ${manifest.totalPrompts} prompts total`);
} catch (err) {
console.warn(`[setup] Could not fetch manifest: ${err.message}`);
console.warn('[setup] Falling back to existing local manifest...');
const localManifest = join(refsDir, 'manifest.json');
if (!existsSync(localManifest)) {
console.error('[setup] No manifest available. Install from the bundled repo/archive or retry with --force.');
process.exit(0);
}
categories = JSON.parse(readFileSync(localManifest, 'utf8')).categories;
}
const validFiles = new Set([...categories.map(c => c.file), 'manifest.json', '.last-updated', '.gitkeep']);
if (forceMode && existsSync(refsDir)) {
for (const f of readdirSync(refsDir)) {
if (!validFiles.has(f)) {
unlinkSync(join(refsDir, f));
console.log(` removed stale: ${f}`);
}
}
}
let downloaded = 0, skipped = 0, failed = 0;
for (const cat of categories) {
const dest = join(refsDir, cat.file);
if (!forceMode && existsSync(dest) && statSync(dest).size > 100) {
skipped++;
continue;
}
process.stdout.write(`${cat.file} (${cat.title}, ${cat.count} prompts) ... `);
try {
const text = await fetchText(`${BASE_URL}/${cat.file}`);
JSON.parse(text);
writeFileSync(dest, text, 'utf8');
console.log('✓');
downloaded++;
} catch (err) {
console.log(`✗ (${err.message})`);
failed++;
}
}
if (failed === 0) writeFileSync(stampFile, String(Date.now()), 'utf8');
if (downloaded > 0) {
console.log(`[setup] Done! ${downloaded} file(s) ${forceMode ? 'updated' : 'downloaded'}. JAE Image Skill is ready.`);
} else if (skipped === categories.length) {
console.log('[setup] All references up to date. Use --force to refresh.');
}
if (failed > 0) console.warn(`[setup] ${failed} file(s) failed. Run again to retry.`);
}
setup().catch(err => {
console.warn('[setup] Warning (non-fatal):', err.message);
process.exit(0);
});