← 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`);
})();