[object Object]

← back to Designer Wallcoverings

Add reusable dry-run-gated refresh-product-image.sh + hi-res BA452 (Oatmeal) for memo-sample blur fix

63cc17b96d485651c2c49b36226c10d7ea9ace6f · 2026-06-24 08:10:50 -0700 · Steve

Files touched

Diff

commit 63cc17b96d485651c2c49b36226c10d7ea9ace6f
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 08:10:50 2026 -0700

    Add reusable dry-run-gated refresh-product-image.sh + hi-res BA452 (Oatmeal) for memo-sample blur fix
---
 pending-approval/portofino-oatmeal-BA452-hires.jpg | Bin 0 -> 1314485 bytes
 pending-approval/refresh-product-image.sh          |  57 +++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/pending-approval/portofino-oatmeal-BA452-hires.jpg b/pending-approval/portofino-oatmeal-BA452-hires.jpg
new file mode 100644
index 00000000..40f6f8ae
Binary files /dev/null and b/pending-approval/portofino-oatmeal-BA452-hires.jpg differ
diff --git a/pending-approval/refresh-product-image.sh b/pending-approval/refresh-product-image.sh
new file mode 100755
index 00000000..5415f376
--- /dev/null
+++ b/pending-approval/refresh-product-image.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+# ─────────────────────────────────────────────────────────────────────────────
+# Reusable: replace a Shopify product's image(s) with a local hi-res file.
+# GATED PROD WRITE — default = DRY RUN. Destructive (deletes images) only with CONFIRM=1.
+#
+# Usage:
+#   PID=<product_id> IMG=<file> ALT="<alt text>" DEL="id1 id2 ..." \
+#     [CONFIRM=1] bash refresh-product-image.sh
+#
+# Adds IMG (base64 attachment, position 1, given ALT), then deletes each id in DEL.
+# Idempotent on add: skips if an image with IMG's basename is already attached.
+# ─────────────────────────────────────────────────────────────────────────────
+set -euo pipefail
+
+SHOP="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
+: "${PID:?set PID}"; : "${IMG:?set IMG}"; : "${ALT:?set ALT}"; DEL="${DEL:-}"
+[ -f "$IMG" ] || { echo "ERROR: image file not found: $IMG"; exit 1; }
+
+TOKEN="${SHOPIFY_ADMIN_TOKEN:-}"
+[ -z "$TOKEN" ] && TOKEN="$(grep -hE '^SHOPIFY_ADMIN_TOKEN=' "$HOME/Projects/secrets-manager/.env" 2>/dev/null | head -1 | cut -d= -f2-)"
+[ -z "$TOKEN" ] && { echo "ERROR: no SHOPIFY_ADMIN_TOKEN"; exit 1; }
+
+BASE="https://$SHOP/admin/api/$API"; H=(-H "X-Shopify-Access-Token: $TOKEN"); HJ=(-H "Content-Type: application/json")
+DRY=1; [ "${CONFIRM:-0}" = "1" ] && DRY=0
+BN="$(basename "$IMG")"
+say(){ printf '\n\033[1m%s\033[0m\n' "$*"; }
+
+DIMS="$(sips -g pixelWidth -g pixelHeight "$IMG" 2>/dev/null | awk '/pixelWidth/{w=$2}/pixelHeight/{h=$2}END{print w"x"h}')"
+say "Product $PID on $SHOP | image: $BN ($DIMS) | mode: $([ $DRY = 1 ] && echo DRY-RUN || echo LIVE)"
+
+say "0) Current images"
+curl -s "${H[@]}" "$BASE/products/$PID.json" \
+ | python3 -c 'import json,sys;p=json.load(sys.stdin)["product"];print("  title:",p["title"]);[print("   ",i["id"],i["width"],"x",i["height"],i["src"].split("/")[-1]) for i in p["images"]]'
+
+say "1) Add hi-res image (alt: $ALT)"
+ALREADY="$(curl -s "${H[@]}" "$BASE/products/$PID.json" | python3 -c "import json,sys;print('yes' if any('${BN%.*}' in (i.get('src') or '') for i in json.load(sys.stdin)['product']['images']) else 'no')")"
+if [ "$ALREADY" = yes ]; then echo "  already attached — skip add";
+elif [ $DRY = 1 ]; then echo "  [dry-run] would POST base64 attachment ($(wc -c <"$IMG") bytes) at position 1";
+else
+  PAY=$(mktemp /tmp/rp-img.XXXXXX)
+  python3 - "$IMG" "$ALT" "$BN" > "$PAY" <<'PY'
+import base64,json,sys
+b64=base64.b64encode(open(sys.argv[1],'rb').read()).decode()
+print(json.dumps({"image":{"attachment":b64,"filename":sys.argv[3],"position":1,"alt":sys.argv[2]}}))
+PY
+  R=$(curl -s -w $'\n%{http_code}' -X POST "$BASE/products/$PID/images.json" "${H[@]}" "${HJ[@]}" --data-binary @"$PAY")
+  C="${R##*$'\n'}"; B="${R%$'\n'*}"; rm -f "$PAY"; echo "  POST HTTP $C"
+  case "$C" in 200|201) echo "$B" | python3 -c 'import json,sys;i=json.load(sys.stdin)["image"];print("  added",i["id"],i["width"],"x",i["height"])';; *) echo "  ERR: $B"; exit 1;; esac
+fi
+
+say "2) Delete listed images: ${DEL:-(none)}"
+for IID in $DEL; do
+  if [ $DRY = 1 ]; then echo "  [dry-run] would DELETE image $IID";
+  else curl -s -X DELETE "$BASE/products/$PID/images/$IID.json" "${H[@]}" -o /dev/null -w "  delete $IID HTTP %{http_code}\n"; fi
+done
+
+say "3) $([ $DRY = 1 ] && echo '(dry-run — re-run with CONFIRM=1 to apply)' || { echo 'Verify'; curl -s "${H[@]}" "$BASE/products/$PID.json" | python3 -c 'import json,sys;p=json.load(sys.stdin)["product"];print("  images:",[(i["id"],i["width"],"x",i["height"]) for i in p["images"]])'; })"

← f917d990 Add ALLOW_LIVE-gated live push script for Boost infinite ove  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-24T08:25:44 (2 files) — shopify/theme-inf 2cbf5664 →