← back to Fashion Style Guides
agent/refresh.js
88 lines
#!/usr/bin/env node
// Style Guide Agent — re-checks each brand's public guidelines + Pantone announcements.
// Strategy: (1) HEAD-check each brand URL is reachable, (2) note last-checked timestamp,
// (3) check Pantone color-of-the-year for current year if blank, (4) write back to data/.
// DRY_RUN=1 prints planned changes without writing.
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.join(__dirname, '..');
const DATA_DIR = path.join(ROOT, 'data');
const LOG_FILE = path.join(__dirname, 'refresh.log');
const DRY = !!process.env.DRY_RUN;
const log = (msg) => {
const line = `[${new Date().toISOString()}] ${msg}`;
console.log(line);
fs.appendFileSync(LOG_FILE, line + '\n');
};
function loadJson(file) { return JSON.parse(fs.readFileSync(path.join(DATA_DIR, file), 'utf8')); }
function saveJson(file, data) {
if (DRY) { log(`DRY: would write ${file}`); return; }
fs.writeFileSync(path.join(DATA_DIR, file), JSON.stringify(data, null, 2) + '\n');
log(`wrote ${file}`);
}
function headCheck(url) {
return new Promise((resolve) => {
try {
const u = new URL(url);
const req = https.request({
method: 'HEAD',
hostname: u.hostname,
path: u.pathname || '/',
timeout: 8000,
headers: { 'user-agent': 'FashionStyleGuidesAgent/0.1 (+steve@designerwallcoverings.com)' },
}, (res) => resolve({ ok: res.statusCode >= 200 && res.statusCode < 500, status: res.statusCode }));
req.on('timeout', () => { req.destroy(); resolve({ ok: false, status: 'timeout' }); });
req.on('error', (e) => resolve({ ok: false, status: e.code || 'error' }));
req.end();
} catch (e) { resolve({ ok: false, status: 'invalid-url' }); }
});
}
async function refreshBrands() {
const data = loadJson('brands.json');
log(`brand-refresh: checking ${data.brands.length} URLs`);
let okCount = 0, failCount = 0;
for (const b of data.brands) {
if (!b.url) continue;
const res = await headCheck(b.url);
b.last_checked = new Date().toISOString();
b.last_status = res.status;
if (res.ok) okCount++; else failCount++;
log(` ${b.id.padEnd(18)} ${res.ok ? 'OK' : 'FAIL'} (${res.status}) ${b.url}`);
}
data.last_updated = new Date().toISOString().slice(0, 10);
saveJson('brands.json', data);
log(`brand-refresh complete: ${okCount} OK, ${failCount} fail`);
}
async function refreshPantone() {
const data = loadJson('pantone.json');
const thisYear = new Date().getFullYear();
const entry = data.color_of_the_year.find(c => c.year === thisYear);
if (entry && !entry.hex) {
log(`pantone: ${thisYear} color of the year is still pending — agent has no live Pantone API; please update manually when announced.`);
} else if (entry) {
log(`pantone: ${thisYear} = ${entry.name} (${entry.pantone || 'n/a'})`);
} else {
log(`pantone: no entry for ${thisYear}; consider adding a TBA stub`);
data.color_of_the_year.unshift({ year: thisYear, name: 'TBA', pantone: null, hex: null, note: 'Auto-stub by Style Guide Agent — populate when Pantone publishes.' });
saveJson('pantone.json', data);
return;
}
data.last_updated = new Date().toISOString().slice(0, 10);
saveJson('pantone.json', data);
}
(async () => {
log(`=== refresh start ${DRY ? '(DRY RUN)' : ''} ===`);
try { await refreshBrands(); } catch (e) { log('brand-refresh ERROR: ' + e.message); }
try { await refreshPantone(); } catch (e) { log('pantone-refresh ERROR: ' + e.message); }
log('=== refresh end ===');
})();