← back to CelebritySignatures

scripts/fetch-printify-catalog.mjs

64 lines

#!/usr/bin/env node
// READ-ONLY Printify account/catalog helper for TK-10286.
// Lists the account's shops, blank-product blueprints, print providers, and
// variants. It never creates products, orders, or production jobs.
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';
import { readFileSync } from 'node:fs';

const ROOT = fileURLToPath(new URL('..', import.meta.url));
const API = 'https://api.printify.com/v1';
function envVal(name) {
  if (process.env[name]) return process.env[name];
  for (const p of [join(ROOT, '.env'), join(ROOT, '..', 'secrets-manager', '.env')]) {
    try { const m = readFileSync(p, 'utf8').match(new RegExp('^' + name + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {}
  }
  return null;
}
async function get(token, path) {
  const r = await fetch(API + path, { headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'CelebritySignatures/1.0' } });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(`Printify ${r.status}: ${j.message || j.error || 'error'} on ${path}`);
  return j;
}
async function main() {
  const token = envVal('PRINTIFY_API_TOKEN');
  if (!token) {
    console.log('PRINTIFY_API_TOKEN not set — generate a scoped personal access token in Printify Profile > Connections, then paste it via the secrets skill.');
    console.log('This helper is GET-only and will discover shops/products after the token is routed.');
    return;
  }
  const shops = await get(token, '/shops.json');
  const blueprintArg = process.argv[2];
  if (!blueprintArg) {
    console.log(`Printify shops (${shops.length}):`);
    for (const shop of shops) console.log(`  id=${shop.id}  ${shop.title}  (${shop.sales_channel || 'unknown channel'})`);
    const blueprints = await get(token, '/catalog/blueprints.json');
    const candidates = blueprints.filter(b => /t-?shirt|tee|polo/i.test(b.title || ''));
    console.log(`\nBlank garment blueprints matching tee/polo (${candidates.length}):`);
    for (const b of candidates.slice(0, 50)) console.log(`  id=${b.id}  ${b.title}`);
    console.log('\nNext: node scripts/fetch-printify-catalog.mjs <blueprintId> [printProviderId]');
    return;
  }
  const providerArg = process.argv[3];
  if (!providerArg) {
    const providers = await get(token, `/catalog/blueprints/${blueprintArg}/print_providers.json`);
    console.log(`Print providers for blueprint ${blueprintArg} (${providers.length}):`);
    for (const p of providers) console.log(`  id=${p.id}  ${p.title}`);
    console.log(`\nNext: node scripts/fetch-printify-catalog.mjs ${blueprintArg} <printProviderId>`);
    return;
  }
  const catalog = await get(token, `/catalog/blueprints/${blueprintArg}/print_providers/${providerArg}/variants.json`);
  const variants = {};
  for (const v of catalog.variants || []) {
    const title = String(v.title || '');
    const size = title.match(/(?:^|\s\/\s)(S|M|L|XL|2XL)(?:$|\s\/\s)/i)?.[1]?.toUpperCase();
    const color = title.split(/\s\/\s/).find(x => !/^(S|M|L|XL|2XL)$/i.test(x));
    if (color && size) variants[`${color}/${size}`] = v.id;
  }
  console.log(JSON.stringify({ blueprintId: Number(blueprintArg), printProviderId: Number(providerArg),
    printPosition: { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 }, variants }, null, 2));
  console.log('\nNormalize Printify color labels to our white/heather/sand/sky ids before saving the mapping.');
}
main().catch(e => { console.error(e.message); process.exit(1); });