← back to Dw Contact Us Pages

scripts/enumerate.mjs

121 lines

#!/usr/bin/env node
// enumerate.mjs — READ ONLY. TK-11925.
// Pages `vendor:"X" status:active` for the 3 contact-us vendors and writes
// data/targets.json (full preimage: variants, inventory levels, publications).
// Makes ZERO writes.
import { writeFileSync, mkdirSync } from 'node:fs';
import { DATA_DIR, VENDORS, TARGET_PUBLICATIONS, SHOP, API_VERSION, TICKET, COHORT, gql, isSampleVariant } from './lib.mjs';

const QUERY = `
query Targets($q: String!, $cursor: String) {
  products(first: 40, query: $q, after: $cursor) {
    pageInfo { hasNextPage endCursor }
    nodes {
      id handle title vendor productType status templateSuffix tags onlineStoreUrl
      variants(first: 100) {
        nodes {
          id sku title price inventoryPolicy inventoryQuantity
          inventoryItem {
            id tracked
            inventoryLevels(first: 5) {
              nodes {
                location { id name }
                quantities(names: ["available","on_hand"]) { name quantity }
              }
            }
          }
        }
      }
      resourcePublicationsV2(first: 30) {
        nodes { isPublished publication { id name } }
      }
    }
  }
}`;

const products = [];
for (const vendor of VENDORS) {
  const q = `vendor:"${vendor}" status:active`;
  let cursor = null, page = 0, n = 0;
  do {
    const d = await gql(QUERY, { q, cursor });
    const c = d.products;
    for (const p of c.nodes) {
      products.push({
        id: p.id, handle: p.handle, title: p.title, vendor: p.vendor,
        productType: p.productType, status: p.status,
        templateSuffix: p.templateSuffix, tags: p.tags, onlineStoreUrl: p.onlineStoreUrl,
        variants: p.variants.nodes.map((v) => ({
          id: v.id, sku: v.sku, title: v.title, price: v.price,
          inventoryPolicy: v.inventoryPolicy, inventoryQuantity: v.inventoryQuantity,
          isSample: isSampleVariant(v),
          inventoryItem: {
            id: v.inventoryItem?.id, tracked: v.inventoryItem?.tracked,
            inventoryLevels: (v.inventoryItem?.inventoryLevels?.nodes || []).map((l) => ({
              location: l.location, quantities: l.quantities,
            })),
          },
        })),
        resourcePublicationsV2: p.resourcePublicationsV2.nodes.map((r) => ({
          isPublished: r.isPublished, publication: r.publication,
        })),
      });
      n++;
    }
    cursor = c.pageInfo.hasNextPage ? c.pageInfo.endCursor : null;
    page++;
    process.stderr.write(`\r${vendor}: ${n} products (page ${page})   `);
  } while (cursor);
  process.stderr.write('\n');
}

// ---- derived counts (these are the numbers every write script re-derives) ----
const byVendor = {};
let nonSample = 0, sampleCount = 0, sampleOnlyProducts = 0, untracked = 0, qtyPos = 0;
const policies = {}, locations = new Map();
let unpublishOps = 0;
const TARGET_IDS = new Set(TARGET_PUBLICATIONS.map((p) => p.id));
const suffixHist = {};

for (const p of products) {
  byVendor[p.vendor] = (byVendor[p.vendor] || 0) + 1;
  suffixHist[p.templateSuffix === null ? '(null)' : `'${p.templateSuffix}'`] =
    (suffixHist[p.templateSuffix === null ? '(null)' : `'${p.templateSuffix}'`] || 0) + 1;
  let sellable = 0;
  for (const v of p.variants) {
    if (v.isSample) { sampleCount++; continue; }
    sellable++; nonSample++;
    policies[v.inventoryPolicy] = (policies[v.inventoryPolicy] || 0) + 1;
    if (v.inventoryItem.tracked === false) untracked++;
    if ((v.inventoryQuantity || 0) > 0) qtyPos++;
    for (const l of v.inventoryItem.inventoryLevels) locations.set(l.location.id, l.location.name);
  }
  if (sellable === 0) sampleOnlyProducts++;
  for (const rp of p.resourcePublicationsV2) if (TARGET_IDS.has(rp.publication.id) && rp.isPublished) unpublishOps++;
}

const out = {
  captured_at: new Date().toISOString(),
  shop: SHOP, api_version: API_VERSION, ticket: TICKET, cohort: COHORT || 'tk-11925',
  vendors: VENDORS, target_publications: TARGET_PUBLICATIONS,
  summary: {
    products_total: products.length, per_vendor: byVendor,
    templateSuffix_histogram: suffixHist,
    variants_total: products.reduce((a, p) => a + p.variants.length, 0),
    sample_variants_untouched: sampleCount,
    nonsample_variants_to_harden: nonSample,
    nonsample_by_inventoryPolicy: policies,
    nonsample_untracked_inventory_items: untracked,
    nonsample_with_qty_gt_0: qtyPos,
    sample_only_products_nothing_to_harden: sampleOnlyProducts,
    distinct_locations: [...locations].map(([id, name]) => ({ id, name })),
    unpublish_operations: unpublishOps,
  },
  products,
};

mkdirSync(DATA_DIR, { recursive: true });
writeFileSync(`${DATA_DIR}/targets.json`, JSON.stringify(out, null, 1));
console.log(JSON.stringify(out.summary, null, 2));
console.log(`\nwrote ${DATA_DIR}/targets.json (${products.length} products)`);