← 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
M scripts/printify-cost-check.mjs
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 →