← back to Dw Width Fix
scan.mjs
85 lines
import fs from 'fs';
const env = Object.fromEntries(fs.readFileSync('.env','utf8').split('\n').filter(l=>l.includes('=')&&!l.startsWith('#')).map(l=>{const i=l.indexOf('=');return [l.slice(0,i).trim(), l.slice(i+1).trim()];}));
const TOKEN = env.SHOPIFY_ADMIN_TOKEN;
const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
const API = `https://${DOMAIN}/admin/api/2024-10/graphql.json`;
async function gql(query, variables={}) {
const r = await fetch(API, {
method:'POST',
headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},
body:JSON.stringify({query, variables})
});
const j = await r.json();
if (j.errors) { console.error(JSON.stringify(j.errors)); throw new Error('gql'); }
return j.data;
}
// broken classifier per memo
function classify(v) {
if (v == null) return {broken:false, reason:'null'};
const s = String(v).trim();
if (s === '') return {broken:true, reason:'empty'};
const low = s.toLowerCase();
// NOT broken (verified legit non-numeric)
if (low === 'hide') return {broken:false, reason:'leather-hide'};
if (/untrimmed/.test(low)) return {broken:false, reason:'maya-untrimmed'};
if (/sold per yard/.test(low)) return {broken:false, reason:'per-yard'};
if (/pillow|cushion/.test(low)) return {broken:false, reason:'pillow'};
// Elitis european decimal like "0,7m (27\")" -> has a real inch width in parens
if (/^0[.,]\d+\s*m/.test(low) && /\d+\s*("|in)/.test(low)) return {broken:false, reason:'euro-decimal-with-inches'};
// BROKEN classes
if (s === '0') return {broken:true, reason:'literal-zero'};
if (/^0\s*cm\s*\(0\.00/.test(low)) return {broken:true, reason:'zero-cm'};
if (low === 'tbd') return {broken:true, reason:'placeholder-tbd'};
if (/call to verify/.test(low)) return {broken:true, reason:'placeholder-call'};
if (low === '(ft)') return {broken:true, reason:'unit-only-ft'};
if (low === 'roll length') return {broken:true, reason:'label-only'};
if (s === ':') return {broken:true, reason:'colon-only'};
if (/wallpaper\.?$/.test(low) && !/\d/.test(s)) return {broken:true, reason:'desc-leaked'};
// general: no digit at all AND not a known-legit non-numeric => suspicious (report, not auto)
if (!/\d/.test(s)) return {broken:true, reason:'no-digit-nonlegit'};
// has a digit but is it a real zero width? "0 cm" variants, or purely "0..."
if (/^0(\.0+)?\s*(cm|in|"|mm|m)?$/.test(low)) return {broken:true, reason:'zero-width'};
return {broken:false, reason:'ok'};
}
const out = [];
let cursor = null, page=0;
const q = `query($cursor:String){
products(first:200, after:$cursor, query:"status:active") {
pageInfo{hasNextPage endCursor}
nodes{ id title status vendor handle
metafield(namespace:"custom", key:"width"){ id value }
}
}
}`;
let total=0, withWidth=0;
while(true){
const d = await gql(q,{cursor});
const conn = d.products;
for(const n of conn.nodes){
total++;
if(!n.metafield) continue;
withWidth++;
const c = classify(n.metafield.value);
if(c.broken){
out.push({id:n.id, title:n.title, vendor:n.vendor, handle:n.handle,
mfId:n.metafield.id, value:n.metafield.value, reason:c.reason});
}
}
page++;
process.stderr.write(`page ${page} scanned=${total} withWidth=${withWidth} broken=${out.length}\r`);
if(!conn.pageInfo.hasNextPage) break;
cursor = conn.pageInfo.endCursor;
}
process.stderr.write('\n');
fs.writeFileSync('broken-live.json', JSON.stringify(out,null,2));
console.log(`\nTOTAL active scanned: ${total}`);
console.log(`with custom.width: ${withWidth}`);
console.log(`BROKEN now: ${out.length}`);
const byReason={}, byVendor={};
for(const o of out){ byReason[o.reason]=(byReason[o.reason]||0)+1; byVendor[o.vendor]=(byVendor[o.vendor]||0)+1; }
console.log('by reason:', JSON.stringify(byReason,null,2));
console.log('by vendor:', JSON.stringify(byVendor,null,2));