[object Object]

← back to CelebritySignatures

wear: extend printify-cost-check.mjs to also verify left-chest placement (TK-10286)

2e8f61bb7c99d9d00f51900d99ccce4797f64207 · 2026-09-08 09:36:05 -0700 · Steve Abrams

Combines the cost check with a real placement verification in one run: uploads
a real signature PNG (Samuel Adams — already wear-eligible), creates each
draft product with print_areas at the SAME x/y/scale wear-templates.json
carries, fetches back any auto-generated mockup image URLs to eyeball, then
deletes + verifies + logs exactly as before. Pauses for a manual dashboard
check if mockups aren't generated yet by the time it fetches, rather than
deleting blind. Still not run — blocked from autonomous execution by the
harness's classifier (external account write); Steve runs it directly.

node --check clean. Untested against the live Printify API (can't run it from
here) — flagged to Steve as such.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm

Files touched

Diff

commit 2e8f61bb7c99d9d00f51900d99ccce4797f64207
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 8 09:36:05 2026 -0700

    wear: extend printify-cost-check.mjs to also verify left-chest placement (TK-10286)
    
    Combines the cost check with a real placement verification in one run: uploads
    a real signature PNG (Samuel Adams — already wear-eligible), creates each
    draft product with print_areas at the SAME x/y/scale wear-templates.json
    carries, fetches back any auto-generated mockup image URLs to eyeball, then
    deletes + verifies + logs exactly as before. Pauses for a manual dashboard
    check if mockups aren't generated yet by the time it fetches, rather than
    deleting blind. Still not run — blocked from autonomous execution by the
    harness's classifier (external account write); Steve runs it directly.
    
    node --check clean. Untested against the live Printify API (can't run it from
    here) — flagged to Steve as such.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
---
 scripts/printify-cost-check.mjs | 66 +++++++++++++++++++++++++++++++++--------
 1 file changed, 54 insertions(+), 12 deletions(-)

diff --git a/scripts/printify-cost-check.mjs b/scripts/printify-cost-check.mjs
index 24f03ad..4ccd15d 100644
--- a/scripts/printify-cost-check.mjs
+++ b/scripts/printify-cost-check.mjs
@@ -1,17 +1,27 @@
 #!/usr/bin/env node
-// ONE-OFF, throwaway cost-discovery helper for TK-10286. Creates ONE minimal,
-// single-variant, unpublished (visible:false) draft product per garment on
-// shop 496929 (Designer Wallcoverings and Fabrics) purely to read Printify's
-// real per-unit cost, then IMMEDIATELY deletes it and verifies the delete
+// ONE-OFF, throwaway cost + placement-verification helper for TK-10286.
+//
+// For each candidate garment: uploads a REAL signature PNG (Samuel Adams —
+// public-domain-historical, already wear-eligible), creates ONE minimal
+// single-variant, unpublished (visible:false) draft product with the print
+// positioned at the SAME x/y/scale wear-templates.json already carries, then
+// fetches the product back to read (a) real Printify cost and (b) any
+// auto-generated mockup image URLs so left-chest placement can be eyeballed
+// visually — then IMMEDIATELY deletes the product and verifies the delete
 // with a follow-up GET (expects 404). Logs created→deleted id pairs so
-// nothing is left dangling. Creates NOTHING else — no order, no charge.
+// nothing is left dangling, and deletes the uploaded image too.
+//
+// Creates NOTHING else — no order, no charge, nothing published.
 import { readFileSync } from 'node:fs';
 import { fileURLToPath } from 'node:url';
 import { join } from 'node:path';
+import { createInterface } from 'node:readline/promises';
+import { stdin, stdout } from 'node:process';
 
 const ROOT = fileURLToPath(new URL('..', import.meta.url));
 const API = 'https://api.printify.com/v1';
 const SHOP_ID = 496929; // Designer Wallcoverings and Fabrics — confirmed by Steve 2026-09-08
+const SIG_IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/0/0a/Samuel_Adams_signature.png';
 
 function envVal(name) {
   if (process.env[name]) return process.env[name];
@@ -32,31 +42,60 @@ async function req(method, path, body) {
   return { status: r.status, ok: r.ok, body: j };
 }
 
-// blueprint_id, print_provider_id, one representative variant id (White/M),
-// matching what's now mapped in data/wear-templates.json (bp12/provider99 for
-// the tee, bp1402/provider99 for the polo — both DTG, Printify Choice).
+// wear-templates.json's current printPosition for both garments (kept identical
+// here on purpose — this run is exactly what a real order would use).
+const PRINT_POSITION = { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 };
+
 const CANDIDATES = [
-  { garment: 'classic-tee', blueprintId: 12, printProviderId: 99, variantId: 18541, label: 'Bella+Canvas 3001 White/M (DTG, provider 99 Printify Choice)' },
-  { garment: 'polo', blueprintId: 1402, printProviderId: 99, variantId: 104648, label: 'JERZEES 443M Piqué Polo White/M (DTG, provider 99 Printify Choice)' },
+  { garment: 'classic-tee', blueprintId: 12, printProviderId: 99, variantId: 18541, label: 'Bella+Canvas 3001 White/M' },
+  { garment: 'polo', blueprintId: 1402, printProviderId: 99, variantId: 104648, label: 'JERZEES 443M Piqué Polo White/M' },
 ];
 
+async function uploadImage() {
+  console.log('Uploading test signature image (Samuel Adams — public-domain-historical)...');
+  const up = await req('POST', '/uploads/images.json', { file_name: 'samuel-adams-signature.png', url: SIG_IMAGE_URL });
+  if (!up.ok) throw new Error(`upload failed (${up.status}): ${JSON.stringify(up.body).slice(0, 300)}`);
+  console.log(`  uploaded image id=${up.body.id}`);
+  return up.body.id;
+}
+
 async function main() {
+  const imageId = await uploadImage();
+
   for (const c of CANDIDATES) {
     console.log(`\n— ${c.garment}: ${c.label} —`);
     const created = await req('POST', `/shops/${SHOP_ID}/products.json`, {
       title: `TEMP-COST-CHECK-DELETE-ME (TK-10286, ${c.garment})`,
-      description: 'Throwaway — created only to read Printify cost, deleted immediately by this script.',
+      description: 'Throwaway — created only to read Printify cost + verify left-chest placement, deleted immediately by this script.',
       blueprint_id: c.blueprintId,
       print_provider_id: c.printProviderId,
       variants: [{ id: c.variantId, price: 3400, is_enabled: true }],
+      print_areas: [{
+        variant_ids: [c.variantId],
+        placeholders: [{ position: PRINT_POSITION.placeholder, images: [{ id: imageId, x: PRINT_POSITION.x, y: PRINT_POSITION.y, scale: PRINT_POSITION.scale, angle: 0 }] }],
+      }],
       visible: false,
     });
-    if (!created.ok) { console.log(`  CREATE FAILED (${created.status}):`, JSON.stringify(created.body).slice(0, 500)); continue; }
+    if (!created.ok) { console.log(`  CREATE FAILED (${created.status}):`, JSON.stringify(created.body).slice(0, 800)); continue; }
     const productId = created.body.id;
     const variant = (created.body.variants || [])[0] || {};
     console.log(`  created product id=${productId}`);
     console.log(`  cost: $${((variant.cost || 0) / 100).toFixed(2)}  suggested retail: $${((variant.price || 0) / 100).toFixed(2)}`);
 
+    // Fetch it back — mockup generation can lag a moment behind creation.
+    await new Promise(r => setTimeout(r, 3000));
+    const fetched = await req('GET', `/shops/${SHOP_ID}/products/${productId}.json`);
+    const images = (fetched.ok ? fetched.body.images : created.body.images) || [];
+    if (images.length) {
+      console.log(`  MOCKUP IMAGES (open these to eyeball left-chest placement):`);
+      for (const img of images) console.log(`    ${img.src}`);
+    } else {
+      console.log('  no mockup images returned yet — product is still live on your account (unpublished, id=' + productId + ').');
+      const rl = createInterface({ input: stdin, output: stdout });
+      await rl.question('  Check https://printify.com/app/products for the mockup now if you want, then press Enter to delete it and continue... ');
+      rl.close();
+    }
+
     const del = await req('DELETE', `/shops/${SHOP_ID}/products/${productId}.json`);
     console.log(`  delete: ${del.ok ? 'OK' : 'FAILED ' + del.status}`);
 
@@ -64,5 +103,8 @@ async function main() {
     console.log(`  verify-deleted (expect 404): got ${verify.status} — ${verify.status === 404 ? 'CONFIRMED GONE' : 'STILL PRESENT — MANUAL CLEANUP NEEDED'}`);
     console.log(`  LOG: created=${productId} deleted=${del.ok} verified_gone=${verify.status === 404}`);
   }
+
+  const delImg = await req('DELETE', `/uploads/images/${imageId}.json`).catch(() => ({ ok: false, status: 'n/a' }));
+  console.log(`\nuploaded test image cleanup: ${delImg.ok ? 'deleted' : 'left in place (id ' + imageId + ' — Printify may not support deleting an already-used upload; harmless, it is just a Samuel Adams signature PNG in your Uploads library)'}`);
 }
 main().catch(e => { console.error(e.message); process.exit(1); });

← ba30f90 wear: fix dark-garment preview + map Printify blueprints/var  ·  back to CelebritySignatures  ·  wear: verify + fix left-chest print position against real Pr 97b050d →