[object Object]

← back to Corkwallcovering

Re-host 23 logo-cropped cork images as live Shopify product images; update products.json to new CDN URLs

8d520278ad7b66f4d2760715514d2186b7e8e638 · 2026-06-30 16:34:47 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 8d520278ad7b66f4d2760715514d2186b7e8e638
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 30 16:34:47 2026 -0700

    Re-host 23 logo-cropped cork images as live Shopify product images; update products.json to new CDN URLs
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/products.json               |  48 ++++++------
 data/rehost-cropped-results.json | 163 ++++++++++++++++++++++++++++++++++++++
 scripts/rehost-cropped-cork.js   | 165 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 352 insertions(+), 24 deletions(-)

diff --git a/data/products.json b/data/products.json
index 4d8fae9..75eeded 100644
--- a/data/products.json
+++ b/data/products.json
@@ -2039,7 +2039,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/43a2aa9b40b4e3e80fad9f478a92f125_c9e2a136-03a2-4d8d-bb78-72f1675aac0e.jpg?v=1745457023",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34178.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -4659,7 +4659,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/e018f8a3dfd210857fe67c8b2b7ba357_0058d1ca-0dcb-4949-ba4e-c4a53aa8816c.jpg?v=1745457027",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34176.jpg",
     "tags": [
       "Architectural",
       "Beige",
@@ -4692,7 +4692,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/5f56df919efeaf3368db50a01fc05cb2_5b979270-553c-492a-9ff6-9162e861ebef.jpg?v=1745457013",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34183.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -4724,7 +4724,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/3ba26b0a8af19094f37c2db0694fc60c_06c69f17-bd41-4986-9096-2726b266727d.jpg?v=1745457009",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34185.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -4757,7 +4757,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/e0e4f3337edc54c774e4347f527ecd75_54967173-52a3-4d8c-b823-32907798a9da.jpg?v=1745457017",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34181.jpg",
     "tags": [
       "Architectural",
       "Beige",
@@ -4788,7 +4788,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/2113990fe114e980f1d501fb7f069f4e_93f78c72-f9c5-4e78-a698-6af0284dd4bd.jpg?v=1745457019",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34180.jpg",
     "tags": [
       "Architectural",
       "Beige",
@@ -4819,7 +4819,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/622d2e0a9a2032b11d3d8c9e012afd04_d66944e1-c338-4382-88e8-66a79917757c.jpg?v=1745457021",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34179.jpg",
     "tags": [
       "Architectural",
       "Beige",
@@ -4851,7 +4851,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/4a10302ba32ad9bebb385088c0b1c8d5_6639e138-54b9-47e9-b6d1-ba61a9541b9e.jpg?v=1745457025",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34177.jpg",
     "tags": [
       "Architectural",
       "Brown",
@@ -4882,7 +4882,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/f88d206c12f2405f59d494ba116994a7_3233a3d0-5f59-4096-9417-24fcbfd42586.jpg?v=1745457011",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34184.jpg",
     "tags": [
       "Architectural",
       "Brown",
@@ -4913,7 +4913,7 @@
     "title": "Faguli Real Cork | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/b4f9a63f50e48cc51b575fc15b1f9305_133f25f8-54d8-428b-964f-45a5c880b93d.jpg?v=1745457015",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34182.jpg",
     "tags": [
       "Architectural",
       "Beige",
@@ -5679,7 +5679,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/f17920060be22c14011d9b8693933060.jpg?v=1745456184",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74040.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -5712,7 +5712,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/66c1f555d63f11648f31137f661dbe27.jpg?v=1745456181",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74041.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -5776,7 +5776,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/367de05b47ed67a63e79363cadf9f203.jpg?v=1745456166",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74048.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -7956,7 +7956,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/0d57e40ef1d2e8127a4c2f4723fe621d.jpg?v=1745456186",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74039.jpg",
     "tags": [
       "Abstract",
       "Architectural",
@@ -7988,7 +7988,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/676a51eb91ea689a8faf7da1246860ef.jpg?v=1745456161",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74050.jpg",
     "tags": [
       "Abstract",
       "AI-Analyzed-v2",
@@ -8086,7 +8086,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/7038db2ed0703f48b8e56ce0142d2ac8.jpg?v=1745456191",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74037.jpg",
     "tags": [
       "Architectural",
       "Brown",
@@ -8148,7 +8148,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/60f2f021774d2b25bfb4b238d7493131.jpg?v=1745456150",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74055.jpg",
     "tags": [
       "Abstract",
       "AI-Analyzed-v2",
@@ -8218,7 +8218,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/058a2415387a12f5153beb947a24e0d9.jpg?v=1745456157",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74052.jpg",
     "tags": [
       "Abstract",
       "AI-Analyzed-v2",
@@ -8289,7 +8289,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/2e0c1bae163e2a484be0c9a47449f403.jpg?v=1745456141",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74059.jpg",
     "tags": [
       "Abstract",
       "AI-Analyzed-v2",
@@ -8320,7 +8320,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/c305292ba73b06e8617f42b37487dd4f.jpg?v=1745456154",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74053.jpg",
     "tags": [
       "Abstract",
       "AI-Analyzed-v2",
@@ -8356,7 +8356,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/0a7d319a25a554a9ac146d27d82d5f2a.jpg?v=1745456159",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74051.jpg",
     "tags": [
       "Abstract",
       "AI-Analyzed-v2",
@@ -8428,7 +8428,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/bc112f88c065de11d24240a96af1b638.jpg?v=1745456168",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74047.jpg",
     "tags": [
       "Architectural",
       "Brown",
@@ -8550,7 +8550,7 @@
     "title": "La Corka | Phillipe Romano",
     "vendor": "Phillipe Romano",
     "product_type": "Wallcovering",
-    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/2097cd68e3568c7c8ba10a780f05dfe4.jpg?v=1745456179",
+    "image_url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74042.jpg",
     "tags": [
       "Architectural",
       "Brown",
@@ -14445,4 +14445,4 @@
     "created_at": "2022-02-28T17:45:19-08:00",
     "dominant_hex_real": "#aaabac"
   }
-]
\ No newline at end of file
+]
diff --git a/data/rehost-cropped-results.json b/data/rehost-cropped-results.json
new file mode 100644
index 0000000..c137da4
--- /dev/null
+++ b/data/rehost-cropped-results.json
@@ -0,0 +1,163 @@
+[
+  {
+    "sku": "faguli-real-cork-cork-34178",
+    "handle": "faguli-real-cork-cork-34178",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34178.jpg?v=1782862328"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34176",
+    "handle": "faguli-real-cork-cork-34176",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34176.jpg?v=1782862333"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34183",
+    "handle": "faguli-real-cork-cork-34183",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34183.jpg?v=1782862338"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34185",
+    "handle": "faguli-real-cork-cork-34185",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34185.jpg?v=1782862343"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34181",
+    "handle": "faguli-real-cork-cork-34181",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34181.jpg?v=1782862348"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34180",
+    "handle": "faguli-real-cork-cork-34180",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34180.jpg?v=1782862353"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34179",
+    "handle": "faguli-real-cork-cork-34179",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34179.jpg?v=1782862358"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34177",
+    "handle": "faguli-real-cork-cork-34177",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34177.jpg?v=1782862364"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34184",
+    "handle": "faguli-real-cork-cork-34184",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34184.jpg?v=1782862369"
+  },
+  {
+    "sku": "faguli-real-cork-cork-34182",
+    "handle": "faguli-real-cork-cork-34182",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/faguli-real-cork-cork-34182.jpg?v=1782862374"
+  },
+  {
+    "sku": "la-corka-prt-74040",
+    "handle": "la-corka-prt-74040",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 10 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74040.jpg?v=1782862379"
+  },
+  {
+    "sku": "la-corka-prt-74041",
+    "handle": "la-corka-prt-74041",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 12 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74041.jpg?v=1782862384"
+  },
+  {
+    "sku": "la-corka-prt-74048",
+    "handle": "la-corka-prt-74048",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74048.jpg?v=1782862390"
+  },
+  {
+    "sku": "la-corka-prt-74039",
+    "handle": "la-corka-prt-74039",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74039.jpg?v=1782862395"
+  },
+  {
+    "sku": "la-corka-prt-74050",
+    "handle": "la-corka-prt-74050",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74050.jpg?v=1782862400"
+  },
+  {
+    "sku": "la-corka-prt-74037",
+    "handle": "la-corka-prt-74037",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74037.jpg?v=1782862405"
+  },
+  {
+    "sku": "la-corka-prt-74055",
+    "handle": "la-corka-prt-74055",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74055.jpg?v=1782862411"
+  },
+  {
+    "sku": "la-corka-prt-74052",
+    "handle": "la-corka-prt-74052",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74052.jpg?v=1782862416"
+  },
+  {
+    "sku": "la-corka-prt-74059",
+    "handle": "la-corka-prt-74059",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74059.jpg?v=1782862421"
+  },
+  {
+    "sku": "la-corka-prt-74053",
+    "handle": "la-corka-prt-74053",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74053.jpg?v=1782862426"
+  },
+  {
+    "sku": "la-corka-prt-74051",
+    "handle": "la-corka-prt-74051",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74051.jpg?v=1782862431"
+  },
+  {
+    "sku": "la-corka-prt-74047",
+    "handle": "la-corka-prt-74047",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 1 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74047.jpg?v=1782862437"
+  },
+  {
+    "sku": "la-corka-prt-74042",
+    "handle": "la-corka-prt-74042",
+    "status": "OK",
+    "detail": "verified: cropped at position 1, banner gone (now 9 imgs)",
+    "newSrc": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/la-corka-prt-74042.jpg?v=1782862442"
+  }
+]
\ No newline at end of file
diff --git a/scripts/rehost-cropped-cork.js b/scripts/rehost-cropped-cork.js
new file mode 100644
index 0000000..43dd7b7
--- /dev/null
+++ b/scripts/rehost-cropped-cork.js
@@ -0,0 +1,165 @@
+#!/usr/bin/env node
+/* Re-host the 23 logo-header-cropped cork images as the live product images.
+ * Idempotent, serial, one product at a time, verify each.
+ * Steve APPROVED ("all go") 2026-06-30.
+ */
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const SECRETS = '/Users/stevestudio2/Projects/secrets-manager/.env';
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+
+// --- load admin token ---
+const envTxt = fs.readFileSync(SECRETS, 'utf8');
+const m = envTxt.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m);
+if (!m) { console.error('No SHOPIFY_ADMIN_TOKEN in secrets'); process.exit(1); }
+const TOKEN = m[1].trim();
+
+const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'cropped-manifest.json'), 'utf8'));
+const productsPath = path.join(ROOT, 'data', 'products.json');
+const products = JSON.parse(fs.readFileSync(productsPath, 'utf8'));
+
+const base = `https://${SHOP}/admin/api/${API}`;
+const headers = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };
+
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+// normalize a Shopify CDN url to its path (strip query string) for comparison
+function normUrl(u) {
+  if (!u) return '';
+  try { const x = new URL(u); return x.origin + x.pathname; }
+  catch { return u.split('?')[0]; }
+}
+
+async function shopify(method, urlPath, body) {
+  const res = await fetch(base + urlPath, {
+    method, headers, body: body ? JSON.stringify(body) : undefined,
+  });
+  const text = await res.text();
+  let json = null;
+  try { json = text ? JSON.parse(text) : null; } catch { /* keep text */ }
+  return { ok: res.ok, status: res.status, json, text, headers: res.headers };
+}
+
+async function getProductByHandle(handle) {
+  // handle is unique; query products by handle
+  const r = await shopify('GET', `/products.json?handle=${encodeURIComponent(handle)}&fields=id,handle,title,images`);
+  if (!r.ok) return { error: `lookup HTTP ${r.status}: ${r.text.slice(0,200)}` };
+  const list = (r.json && r.json.products) || [];
+  const exact = list.find(p => p.handle === handle);
+  return { product: exact || null, raw: list };
+}
+
+async function getFullProduct(id) {
+  const r = await shopify('GET', `/products/${id}.json?fields=id,handle,images`);
+  if (!r.ok) return { error: `get HTTP ${r.status}` };
+  return { product: r.json.product };
+}
+
+const results = [];
+
+(async () => {
+  console.log(`Processing ${manifest.length} cork SKUs...\n`);
+  for (const item of manifest) {
+    const { sku, handle, cropped_file, image_url: bannerUrl } = item;
+    const log = (msg) => console.log(`[${sku}] ${msg}`);
+    const rec = { sku, handle, status: 'PENDING', detail: '', newSrc: '' };
+    try {
+      // 1. resolve product
+      const lookup = await getProductByHandle(handle);
+      if (lookup.error) { rec.status = 'SKIP'; rec.detail = `lookup failed: ${lookup.error}`; log(rec.detail); results.push(rec); await sleep(700); continue; }
+      const product = lookup.product;
+      if (!product) { rec.status = 'SKIP'; rec.detail = 'handle did not resolve to a product'; log(rec.detail); results.push(rec); await sleep(700); continue; }
+      const pid = product.id;
+      log(`product_id=${pid} ("${product.title}") images=${product.images.length}`);
+
+      // 2. identify banner image (matches manifest original image_url by path)
+      const bannerNorm = normUrl(bannerUrl);
+      let banner = product.images.find(img => normUrl(img.src) === bannerNorm);
+
+      // idempotency: detect if a cropped image already added (filename basename of cropped_file present in some src)
+      const croppedBase = path.basename(cropped_file).replace(/\.[a-z]+$/i, '');
+      const alreadyCropped = product.images.find(img => (img.src || '').includes(croppedBase));
+
+      if (!banner && alreadyCropped) {
+        rec.status = 'OK';
+        rec.detail = 'idempotent: banner already gone, cropped image already present';
+        rec.newSrc = alreadyCropped.src;
+        log(rec.detail + ' -> ' + alreadyCropped.src);
+        // ensure products.json carries the cropped src
+        updateProductsJson(sku, handle, alreadyCropped.src.split('?')[0]);
+        results.push(rec); await sleep(700); continue;
+      }
+      if (!banner) { rec.status = 'SKIP'; rec.detail = `no image matching banner url (${bannerNorm}); not guessing`; log(rec.detail); results.push(rec); await sleep(700); continue; }
+      log(`banner image id=${banner.id} pos=${banner.position}`);
+
+      // 3. upload cropped as new image at position 1
+      const filePath = path.join(ROOT, 'public', cropped_file);
+      if (!fs.existsSync(filePath)) { rec.status = 'SKIP'; rec.detail = `cropped file missing: ${filePath}`; log(rec.detail); results.push(rec); await sleep(700); continue; }
+      const b64 = fs.readFileSync(filePath).toString('base64');
+      const filename = path.basename(cropped_file);
+      const up = await shopify('POST', `/products/${pid}/images.json`, {
+        image: { attachment: b64, filename, position: 1 }
+      });
+      if (!up.ok) { rec.status = 'SKIP'; rec.detail = `upload failed HTTP ${up.status}: ${up.text.slice(0,200)}`; log(rec.detail); results.push(rec); await sleep(1000); continue; }
+      const newImg = up.json.image;
+      log(`uploaded new image id=${newImg.id} src=${newImg.src}`);
+      rec.newSrc = newImg.src;
+      await sleep(800);
+
+      // 4. delete the old banner image
+      const del = await shopify('DELETE', `/products/${pid}/images/${banner.id}.json`);
+      if (!del.ok) { rec.status = 'PARTIAL'; rec.detail = `uploaded cropped (id ${newImg.id}) but DELETE of banner ${banner.id} failed HTTP ${del.status}`; log(rec.detail); results.push(rec); await sleep(1000); continue; }
+      log(`deleted banner image id=${banner.id}`);
+      await sleep(800);
+
+      // 5. verify
+      const verify = await getFullProduct(pid);
+      if (verify.error) { rec.status = 'PARTIAL'; rec.detail = `actions done but verify GET failed: ${verify.error}`; results.push(rec); await sleep(800); continue; }
+      const imgs = verify.product.images;
+      const bannerGone = !imgs.find(i => i.id === banner.id);
+      const newPresent = imgs.find(i => i.id === newImg.id);
+      const pos1 = imgs.find(i => i.position === 1);
+      const pos1IsCropped = pos1 && newPresent && pos1.id === newImg.id;
+      if (bannerGone && newPresent && pos1IsCropped) {
+        rec.status = 'OK';
+        rec.detail = `verified: cropped at position 1, banner gone (now ${imgs.length} imgs)`;
+        rec.newSrc = newPresent.src;
+        log(rec.detail);
+      } else {
+        rec.status = 'PARTIAL';
+        rec.detail = `verify mismatch: bannerGone=${bannerGone} newPresent=${!!newPresent} pos1IsCropped=${pos1IsCropped}`;
+        if (newPresent) rec.newSrc = newPresent.src;
+        log(rec.detail);
+      }
+
+      // 6. update products.json with new cropped CDN url (strip ?v for stability)
+      if (rec.newSrc) updateProductsJson(sku, handle, rec.newSrc.split('?')[0]);
+
+    } catch (e) {
+      rec.status = 'SKIP'; rec.detail = 'exception: ' + (e && e.message);
+      log(rec.detail);
+    }
+    results.push(rec);
+    await sleep(900); // rate-limit gap between SKUs
+  }
+
+  // persist products.json once at end (updates applied in-memory)
+  fs.writeFileSync(productsPath, JSON.stringify(products, null, 2) + '\n');
+
+  // summary
+  console.log('\n===== SUMMARY =====');
+  const ok = results.filter(r => r.status === 'OK').length;
+  const skip = results.filter(r => r.status === 'SKIP').length;
+  const partial = results.filter(r => r.status === 'PARTIAL').length;
+  for (const r of results) console.log(`${r.status.padEnd(8)} ${r.sku}  ${r.detail}`);
+  console.log(`\nOK=${ok}  PARTIAL=${partial}  SKIP=${skip}  total=${results.length}`);
+  fs.writeFileSync(path.join(ROOT, 'data', 'rehost-cropped-results.json'), JSON.stringify(results, null, 2));
+})();
+
+function updateProductsJson(sku, handle, newUrl) {
+  const p = products.find(x => x && (x.sku === sku || x.handle === handle));
+  if (p) { p.image_url = newUrl; }
+}

← a6f5277 Crop PHILLIPE ROMANO logo-header band off 23 cork product im  ·  back to Corkwallcovering  ·  auto-save: 2026-07-01T09:41:09 (1 files) — scripts/enrich-co 6bbcce9 →