← back to Dw Silver Mylar Rename

scan.mjs

81 lines

// READ-ONLY scan: fetch each scoped product live, measure the option/variant
// surface, and build the full plan + restore-map for title / body / option values.
import { readFileSync, writeFileSync } from 'node:fs';
import { gql, swap, has, TOKEN_NAME } from './lib.mjs';

const scope = JSON.parse(readFileSync(new URL('./data/scope.json', import.meta.url)));
console.log(`token=${TOKEN_NAME}  scope=${scope.length}`);

const Q = `query($id: ID!){
  product(id:$id){
    id status title descriptionHtml
    options { id name optionValues { id name } }
    variants(first:100){ nodes { id title selectedOptions { name value } } }
  }
}`;

const plan = [];      // per product: what will change
const restore = [];   // reverse map for undo
let titleN = 0, bodyN = 0, optN = 0, variantHitN = 0;
const missing = [];

for (const row of scope) {
  const d = await gql(Q, { id: row.gid });
  const p = d.product;
  if (!p) { missing.push(row.gid); continue; }

  const entry = { gid: p.id, status: p.status, changes: {} };
  const rEntry = { gid: p.id, status: p.status, fields: {} };

  // TITLE (live truth, not mirror)
  if (has(p.title)) {
    const nt = swap(p.title);
    entry.changes.title = { from: p.title, to: nt };
    rEntry.fields.title = { from: nt, to: p.title }; // reverse
    titleN++;
  }
  // BODY / descriptionHtml
  if (has(p.descriptionHtml)) {
    const nb = swap(p.descriptionHtml);
    entry.changes.descriptionHtml = { from: p.descriptionHtml, to: nb };
    rEntry.fields.descriptionHtml = { from: nb, to: p.descriptionHtml };
    bodyN++;
  }
  // OPTION VALUES (buyable) — collect per-option value ids that need renaming
  const optionUpdates = [];
  for (const opt of p.options) {
    const vals = opt.optionValues.filter(v => has(v.name));
    if (vals.length) {
      optionUpdates.push({
        optionId: opt.id, optionName: opt.name,
        values: vals.map(v => ({ id: v.id, from: v.name, to: swap(v.name) })),
      });
      optN += vals.length;
    }
  }
  if (optionUpdates.length) {
    entry.changes.options = optionUpdates;
    rEntry.fields.options = optionUpdates.map(o => ({
      optionId: o.optionId,
      values: o.values.map(v => ({ id: v.id, from: v.to, to: v.from })),
    }));
  }
  // variant title hits (informational — variant titles derive from option values)
  const vhits = p.variants.nodes.filter(v => has(v.title) || v.selectedOptions.some(so => has(so.value)));
  if (vhits.length) variantHitN += vhits.length;

  if (Object.keys(entry.changes).length) { plan.push(entry); restore.push(rEntry); }
}

writeFileSync(new URL('./data/plan.json', import.meta.url), JSON.stringify(plan, null, 2));
const ts = new Date().toISOString();
writeFileSync(new URL('./data/restore-map.json', import.meta.url), JSON.stringify({ ts, note: 'Reverse map: apply .to to set values BACK to pre-change state (Silver Metallic -> original).', restore }, null, 2));

console.log('--- SCAN RESULT ---');
console.log('products with any change :', plan.length);
console.log('title fields to change   :', titleN);
console.log('body  fields to change   :', bodyN);
console.log('option VALUES to change  :', optN);
console.log('variant rows w/ token    :', variantHitN, '(derive from option values)');
if (missing.length) console.log('MISSING/unfetchable gids :', missing.length, missing);