[object Object]

← back to Designer Wallcoverings

Dev-theme push script for contact-for-price block (tag-gated, stub-guard, THEME_ID override)

8a734295ca8516344b5b416ef6c3e558cc95f27c · 2026-06-27 00:15:00 -0700 · Steve

Files touched

Diff

commit 8a734295ca8516344b5b416ef6c3e558cc95f27c
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jun 27 00:15:00 2026 -0700

    Dev-theme push script for contact-for-price block (tag-gated, stub-guard, THEME_ID override)
---
 shopify/push-contact-for-price-to-dev-theme.sh | 105 +++++++++++++++++++++++++
 1 file changed, 105 insertions(+)

diff --git a/shopify/push-contact-for-price-to-dev-theme.sh b/shopify/push-contact-for-price-to-dev-theme.sh
new file mode 100644
index 00000000..d4a23b8f
--- /dev/null
+++ b/shopify/push-contact-for-price-to-dev-theme.sh
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+# Stage the contact-for-price block on the DEV theme "Updated copy of NewWall
+# Template [Dev]" — drift-safe, idempotent, live/main theme NEVER touched.
+#
+# Injects shopify/sections/contact-for-price.liquid's inner content into
+# layout/theme.liquid, gated by `template contains 'product' and product.tags
+# contains 'contact-for-price'`, plus a JS relocator that moves the block into
+# the buy box. Backs up theme.liquid, pushes, verifies, prints a preview URL on
+# a tagged product. Re-runs UPDATE in place (strips the prior DW-CFP block first).
+#
+#   THEME_TOKEN=shpat_xxx bash shopify/push-contact-for-price-to-dev-theme.sh
+set -euo pipefail
+cd "$(dirname "$0")"
+
+STORE="designer-laboratory-sandbox.myshopify.com"
+API="2024-10"
+SECRETS="$HOME/Projects/secrets-manager/.env"
+TARGET_NAME="Updated copy of NewWall Template [Dev]"
+PREVIEW_HANDLE="capri-linen-seafoam-memo-sample-phillipe-romano"
+SECTION_FILE="sections/contact-for-price.liquid"
+
+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 shopify/push-contact-for-price-to-dev-theme.sh"; exit 1; }
+echo "token …${TOK: -4}"
+
+mkdir -p theme-backups
+python3 - "$STORE" "$API" "$TOK" "$TARGET_NAME" "theme-backups" "$SECTION_FILE" "$PREVIEW_HANDLE" "${THEME_ID:-}" <<'PY'
+import sys, json, re, urllib.request, urllib.parse, datetime
+store, api, tok, target_name, bkdir, section_file, preview, theme_id = sys.argv[1:9]
+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 theme_id:
+    match = [t for t in themes if str(t["id"]) == str(theme_id)]
+else:
+    match = [t for t in themes if t["name"] == target_name]
+match = [t for t in match if t["role"] != "main"]
+if not match: print(f'Target theme not found / would be main. Pass THEME_ID=<id>. Aborting — live untouched.'); sys.exit(1)
+theme = match[0]; tid = theme["id"]
+if theme["role"] == "main": print("REFUSING to write main theme."); sys.exit(1)
+print(f'=== target: {tid} "{theme["name"]}" (role={theme["role"]}) — live untouched ===')
+
+# the section is ALREADY tag-gated and renders nothing when `product` is nil
+# (off-PDP), so inject it as-is — no re-gating needed. We only splice the JS
+# relocator in just before its final {% endif %} so it loads only on tagged PDPs.
+section = open(section_file).read()
+
+RELOC = r"""
+<script>
+(function(){
+  if(location.pathname.indexOf('/products/')===-1) return;
+  function place(){
+    var sec=document.querySelector('.cfp-section'); if(!sec||sec.dataset.dwPlaced) return;
+    var atc=document.querySelector('button[name="add"],[type="submit"][name="add"]')||
+      [].slice.call(document.querySelectorAll('button,input[type=submit]')).filter(function(b){return /add to cart/i.test(b.textContent||b.value||'');})[0];
+    var anchor=atc||document.querySelector('form[action*="/cart"]'); if(!anchor) return;
+    sec.dataset.dwPlaced='1';
+    (anchor.parentElement||document.body).insertBefore(sec, anchor.nextSibling);
+  }
+  function init(){ place(); setTimeout(place,500); setTimeout(place,1400); }
+  if(document.readyState!=='loading') init(); else document.addEventListener('DOMContentLoaded',init);
+})();
+</script>
+"""
+
+# splice RELOC just before the section's final {% endif %} (stays inside the tag gate)
+section = re.sub(r"(\{%\s*endif\s*%\}\s*)$", RELOC + r"\1", section, count=1, flags=re.S)
+BLOCK = "\n<!-- DW-CFP: contact-for-price (2026-06-26) -->\n" + section + "\n<!-- /DW-CFP -->\n"
+
+key = "layout/theme.liquid"
+q = urllib.parse.urlencode({"asset[key]": key})
+cur = get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+if len(cur) < 500:
+    print(f"REFUSING: {key} is only {len(cur)} bytes — looks like a scratch/stub theme, not a real storefront. Pass THEME_ID=<a real dev copy>."); sys.exit(1)
+stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+bk = f"{bkdir}/theme.liquid.cfp-dev-{tid}.{stamp}.bak"
+open(bk, "w").write(cur); print(f"backup: {bk} ({len(cur)} bytes)")
+
+# idempotent: strip any prior DW-CFP block, then re-insert before </body> (or </head>)
+src = re.sub(r"\n*<!-- DW-CFP:.*?<!-- /DW-CFP -->\n*", "\n", cur, flags=re.S)
+if "</body>" in src:
+    src = src.replace("</body>", BLOCK + "</body>", 1); where="before </body>"
+elif "</head>" in src:
+    src = src.replace("</head>", BLOCK + "</head>", 1); where="before </head>"
+else:
+    src = src + BLOCK; where="appended"
+print("insert:", where)
+if src == cur: print("no change."); 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: DW-CFP marker x", ver.count("<!-- DW-CFP:"), "| cfp-section present:", "cfp-section" in ver)
+print(f"\nPREVIEW (tagged product on dev theme): https://www.designerwallcoverings.com/products/{preview}?preview_theme_id={tid}")
+print(f"revert:  PUT {bk} back to {key} on theme {tid}")
+PY

← 3e354e5e SKU-inquiry backend (George-routed, fail-open) + throttle on  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-27T00:36:29 (9 files) — pending-approval/ 8b3be675 →