← back to Designer Wallcoverings
Add drift-proof patcher to push the PDP spec .value fix to dev/live theme
e21ad3eed938ac19bf111fcaca6bf485f1524944 · 2026-06-24 15:10:56 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A shopify/push-spec-metafield-value-fix.sh
Diff
commit e21ad3eed938ac19bf111fcaca6bf485f1524944
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jun 24 15:10:56 2026 -0700
Add drift-proof patcher to push the PDP spec .value fix to dev/live theme
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
shopify/push-spec-metafield-value-fix.sh | 86 ++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/shopify/push-spec-metafield-value-fix.sh b/shopify/push-spec-metafield-value-fix.sh
new file mode 100755
index 00000000..0ea5fe25
--- /dev/null
+++ b/shopify/push-spec-metafield-value-fix.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+# Fix the PDP spec table rendering the raw metafield envelope
+# ({"type":"single_line_text_field","value":"Nonwoven"}) instead of the value.
+#
+# Pulls snippets/product-description-meta.liquid from the TARGET theme, appends
+# .value to every bare `product.metafields.<ns>.<key>` / `.<ns>['key']` ref
+# (idempotent — skips refs that already have .value), backs up, PUTs, verifies.
+# Drift-proof: patches whatever is actually on the theme, not a local copy.
+#
+# Default target = DEV theme "Updated copy of NewWall Template [Dev]" (safe).
+# To target the LIVE/main theme, pass LIVE=1 explicitly (Steve-gated):
+# THEME_TOKEN=shpat_xxx LIVE=1 bash shopify/push-spec-metafield-value-fix.sh
+set -euo pipefail
+cd "$(dirname "$0")"
+
+STORE="designer-laboratory-sandbox.myshopify.com"
+API="2024-10"
+SECRETS="$HOME/Projects/secrets-manager/.env"
+DEV_NAME="Updated copy of NewWall Template [Dev]"
+LIVE="${LIVE:-0}"
+
+TOK="${THEME_TOKEN:-$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"' ')}"
+[ -z "$TOK" ] && { echo "No theme token. Run: THEME_TOKEN=shpat_xxx bash $0"; exit 1; }
+echo "token …${TOK: -4} | LIVE=$LIVE"
+
+mkdir -p theme-backups
+LIVE="$LIVE" python3 - "$STORE" "$API" "$TOK" "$DEV_NAME" "theme-backups" <<'PY'
+import sys, os, re, json, urllib.request, urllib.error, urllib.parse, datetime
+store, api, tok, dev_name, bkdir = sys.argv[1:6]
+live = os.environ.get("LIVE","0") == "1"
+H = {"X-Shopify-Access-Token": tok, "Content-Type": "application/json"}
+def get(url): return json.load(urllib.request.urlopen(urllib.request.Request(url, headers=H)))
+
+themes = get(f"https://{store}/admin/api/{api}/themes.json")["themes"]
+print("themes:")
+for t in themes: print(" ", t["id"], t["role"], "|", t["name"])
+
+if live:
+ match = [t for t in themes if t["role"] == "main"]
+ label = "LIVE/main"
+else:
+ match = [t for t in themes if t["name"] == dev_name and t["role"] != "main"] \
+ or [t for t in themes if t["role"] == "development"]
+ label = "DEV"
+if not match:
+ print(f"No {label} theme found."); sys.exit(1)
+theme = match[0]; tid = theme["id"]
+print(f'=== target: {tid} "{theme["name"]}" (role={theme["role"]}) [{label}] ===')
+
+key = "snippets/product-description-meta.liquid"
+q = urllib.parse.urlencode({"asset[key]": key})
+try:
+ cur = get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+except urllib.error.HTTPError as e:
+ print("GET snippet FAILED HTTP", e.code, e.read().decode()[:300]); sys.exit(1)
+
+stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+bk = f"{bkdir}/product-description-meta.{label}.{tid}.{stamp}.bak"
+open(bk, "w").write(cur); print(f"backup: {bk} ({len(cur)} bytes)")
+
+# idempotent: append .value to bare metafield refs (dotted + bracket keys)
+pat = re.compile(r"(product\.metafields\.\w+(?:\.\w+|\['[^']*'\]))(?!\.value)")
+before = len(pat.findall(cur))
+src = pat.sub(r"\1.value", cur)
+after_bare = len(pat.findall(src))
+print(f"refs patched: {before} | bare-remaining (true): {after_bare}")
+assert ".value.value" not in src, "double .value detected — aborting"
+
+if src == cur:
+ print("already fixed — no change needed."); sys.exit(0)
+
+body = json.dumps({"asset": {"key": key, "value": src}}).encode()
+try:
+ r = urllib.request.urlopen(urllib.request.Request(
+ f"https://{store}/admin/api/{api}/themes/{tid}/assets.json", data=body, method="PUT", headers=H))
+ print("PUT HTTP", r.status, "OK", f"({len(src)} bytes)")
+except urllib.error.HTTPError as e:
+ print("PUT FAILED HTTP", e.code, e.read().decode()[:300]); sys.exit(1)
+
+ver = get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+print("verify: .value count =", ver.count(".value"), "| envelope-prone bare refs =",
+ len(re.findall(r"product\.metafields\.\w+(?:\.\w+|\['[^']*'\])(?!\.value)", ver)))
+if not live:
+ print(f"\nPREVIEW: https://www.designerwallcoverings.com/products/cirrus-jade-dwdx-220230?preview_theme_id={tid}")
+print(f"revert: PUT {bk} back to {key} on theme {tid}")
+PY
← aa0b60a0 Fix PDP spec rows rendering raw metafield envelope: read .va
·
back to Designer Wallcoverings
·
China Seas mailer: make all CTA buttons pill-shaped (border- 33c3f530 →