← back to Flock Fix Viewer

reverse-june16.mjs

70 lines

// Reverse the June 16 2026 flock batch: ARCHIVE (reversible) all flock products
// created 2026-06-15/16/17 that are sample-only ($4.25/no roll price). NOT delete.
import fs from 'fs';
import os from 'os';
import path from 'path';

const SHOP = 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = fs.readFileSync(new URL('./.token', import.meta.url), 'utf8').trim();
const LEDGER = path.join(os.homedir(), '.claude/yolo-queue/executed-reversible/ledger.jsonl');
const SNAP = new URL('./reverse-june16-snapshot.json', import.meta.url);
const DRY = !process.argv.includes('--apply');
const DAYS = new Set(['2026-06-15', '2026-06-16', '2026-06-17']);

async function gql(query, variables = {}) {
  const r = await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`, {
    method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables })
  });
  return r.json();
}
const sleep = ms => new Promise(r => setTimeout(r, ms));

const Q = `query($c:String){
  products(first:100, query:"(tag:'Flock Velvet' OR title:flock OR handle:flock)", after:$c){
    pageInfo{hasNextPage endCursor}
    edges{node{ id handle title status createdAt variants(first:6){edges{node{title price}}} }}
  }
}`;
const ARCH = `mutation($id:ID!){ productUpdate(input:{id:$id, status:ARCHIVED}){ product{id status} userErrors{field message} } }`;

(async () => {
  let cur = null, all = [];
  do {
    const r = await gql(Q, { c: cur });
    const p = r?.data?.products; if (!p) { console.error(JSON.stringify(r)); break; }
    all = all.concat(p.edges);
    cur = p.pageInfo.hasNextPage ? p.pageInfo.endCursor : null;
  } while (cur);

  const targets = all.map(e => e.node).filter(n => {
    if (!DAYS.has((n.createdAt || '').slice(0, 10))) return false;
    if (n.status === 'ARCHIVED') return false;                 // already archived
    const roll = (n.variants.edges || []).map(v => v.node).filter(v => !/sample/i.test(v.title || ''));
    const maxp = roll.reduce((m, v) => Math.max(m, parseFloat(v.price) || 0), 0);
    return maxp <= 10;                                         // sample-only junk ONLY (safety guard)
  });

  const byStatus = targets.reduce((a, n) => (a[n.status] = (a[n.status] || 0) + 1, a), {});
  console.log(`Scanned ${all.length} flock · targets to ARCHIVE: ${targets.length}`, byStatus);
  fs.writeFileSync(SNAP, JSON.stringify(targets.map(n => ({ id: n.id, handle: n.handle, priorStatus: n.status })), null, 2));
  if (DRY) { targets.slice(0, 10).forEach(n => console.log('  would archive', n.status, n.handle)); return; }

  let done = 0, failed = 0;
  for (const n of targets) {
    const r = await gql(ARCH, { id: n.id });
    const errs = r?.data?.productUpdate?.userErrors;
    if (errs && errs.length) { failed++; console.log('  FAIL', n.handle, JSON.stringify(errs)); continue; }
    done++;
    fs.appendFileSync(LEDGER, JSON.stringify({
      ts: new Date().toISOString(), agent: 'main:flock-reverse-june16', ticket: 'flock-reverse-june16',
      action: `ARCHIVE ${n.handle} (was ${n.status}) — reverse June-16 sample-shell batch`, blast_radius: 1,
      undo_cmd: `productUpdate ${n.id} status:${n.priorStatus}`,
      verify: `product ${n.handle} status == ARCHIVED; gone from storefront`
    }) + '\n');
    if (done % 10 === 0) console.log(`  …${done}/${targets.length}`);
    await sleep(300);
  }
  console.log(`DONE · archived ${done} · failed ${failed} · snapshot: reverse-june16-snapshot.json`);
})();