← 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);