← back to Pattern Vault
scripts/build-kits.js
64 lines
// Build upload-ready marketplace kits from the catalog — one folder per channel with
// images + metadata formatted for that channel's upload flow. Signup day = drag-and-drop day.
// $0 (local): images pulled from wallpapersback.com (our own server).
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.join(__dirname, '..');
const items = JSON.parse(fs.readFileSync(path.join(ROOT, 'data/catalog.json'), 'utf8'));
const KITS = path.join(ROOT, 'kits');
const dl = (url, dest) => new Promise((ok, bad) => {
if (fs.existsSync(dest) && fs.statSync(dest).size > 1000) return ok('cached');
const f = fs.createWriteStream(dest);
https.get(url, r => { if (r.statusCode !== 200) return bad(new Error(url + ' → ' + r.statusCode)); r.pipe(f); f.on('finish', () => f.close(ok)); }).on('error', bad);
});
const csvEsc = s => '"' + String(s ?? '').replace(/"/g, '""') + '"';
const tags = d => [d.category, 'seamless pattern', 'original design', 'wallpaper', 'surface design',
'repeat pattern', 'print ready', (d.styleLine || '').split(' ').slice(0, 2).join(' ')].filter(Boolean).slice(0, 13);
(async () => {
const imgDir = path.join(KITS, '_images'); fs.mkdirSync(imgDir, { recursive: true });
let got = 0;
for (const d of items) {
try { await dl(d.img, path.join(imgDir, `${d.id}.png`)); got++; } catch (e) { console.error('img fail', d.id, e.message); }
}
// Spoonflower: manual upload UI — provide a per-design worksheet CSV (title, description, tags)
const sf = ['design_file,title,description,tags'];
// Etsy: listing CSV compatible with bulk tools / the Etsy API listing fields
const etsy = ['image,title,description,price_usd,quantity,tags,who_made,when_made,is_digital'];
// Creative Market / generic digital marketplaces
const cm = ['file,title,subtitle,description,category,price_usd,tags'];
for (const d of items) {
const t1 = (d.licenseTiers || [])[0] || {};
const desc = `${d.styleLine || d.title}. Original seamless repeat from the Wallpaper's Back studio — ` +
`seam-verified, print-clean at 150 DPI. Digital single-use license included; commercial and exclusive tiers available.`;
sf.push([`_images/${d.id}.png`, csvEsc(d.title), csvEsc(desc), csvEsc(tags(d).join(', '))].join(','));
etsy.push([`_images/${d.id}.png`, csvEsc(`${d.title} — Seamless Digital Pattern`), csvEsc(desc), t1.priceUsd || 149, 999, csvEsc(tags(d).join(',')), 'i_did', '2020_2026', 'TRUE'].join(','));
cm.push([`_images/${d.id}.png`, csvEsc(d.title), csvEsc((d.styleLine || '').slice(0, 80)), csvEsc(desc), csvEsc(d.category || 'patterns'), t1.priceUsd || 149, csvEsc(tags(d).join(', '))].join(','));
}
for (const [dir, rows] of [['spoonflower', sf], ['etsy', etsy], ['creative-market', cm]]) {
fs.mkdirSync(path.join(KITS, dir), { recursive: true });
fs.writeFileSync(path.join(KITS, dir, 'listings.csv'), rows.join('\n'));
}
fs.writeFileSync(path.join(KITS, 'README.md'), `# Marketplace upload kits (generated ${new Date().toISOString()})
Images: kits/_images/<id>.png (${got}/${items.length} downloaded from wallpapersback.com).
NOTE: marketplace masters should be the FULL-RES tiles — those live on Kamatera prod
(wallco-tif pipeline); pulling them is a Steve-gated prod step. These web-res copies are for
listing previews + copy; swap in the 150-DPI masters at upload time.
- spoonflower/listings.csv — title/description/tags worksheet for the manual upload UI
- etsy/listings.csv — digital-listing rows (also maps to the Etsy API listing fields)
- creative-market/listings.csv — shop-application + product rows
Each row's copy was LLM-written from the design's style line. All GATED: creating the
accounts + uploading is Steve's switch (see pending-approval/abramsego-golive-260701.md §5).
`);
console.log(`kits built: ${items.length} designs · ${got} images · 3 channels`);
})();