[object Object]

← back to Secrets Manager

auto-save: 2026-07-21T18:09:04 (3 files) — hero-image-fallback.py hero-polish.py set-categories.py

3d0b1dfecfc7009e845f06f0845b328136872163 · 2026-07-21 18:09:05 -0700 · Steve Abrams

Files touched

Diff

commit 3d0b1dfecfc7009e845f06f0845b328136872163
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 21 18:09:05 2026 -0700

    auto-save: 2026-07-21T18:09:04 (3 files) — hero-image-fallback.py hero-polish.py set-categories.py
---
 hero-image-fallback.py | 41 +++++++++++++++++++++++++++++++++++++++++
 hero-polish.py         | 30 ++++++++++++++++++++++++++++++
 set-categories.py      | 41 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 112 insertions(+)

diff --git a/hero-image-fallback.py b/hero-image-fallback.py
new file mode 100644
index 0000000..c57d424
--- /dev/null
+++ b/hero-image-fallback.py
@@ -0,0 +1,41 @@
+import re,json,sys,urllib.request
+env=open('/Users/macstudio3/Projects/secrets-manager/.env').read()
+tok=re.search(r'^SHOPIFY_THEME_TOKEN=(.+)$',env,re.M).group(1).strip().strip('"').strip("'")
+S="designer-laboratory-sandbox.myshopify.com";A="2024-10";TID=144396058675;KEY="sections/dw-collection-hero.liquid"
+APPLY='--apply' in sys.argv
+def g():
+    r=urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json?asset[key]={KEY}",headers={"X-Shopify-Access-Token":tok})
+    return json.load(urllib.request.urlopen(r,timeout=30))['asset']['value']
+def p(v):
+    b=json.dumps({"asset":{"key":KEY,"value":v}}).encode()
+    r=urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json",data=b,method="PUT",headers={"X-Shopify-Access-Token":tok,"Content-Type":"application/json"})
+    return json.load(urllib.request.urlopen(r,timeout=30))
+v=g()
+old_block="""{%- liquid
+  assign dw_has_hero = false
+  if collection.image
+    assign dw_has_hero = true
+  endif
+-%}"""
+new_block="""{%- liquid
+  assign dw_hero_img = collection.image
+  unless dw_hero_img
+    assign dw_hero_img = collection.products.first.featured_image
+  endunless
+  assign dw_has_hero = false
+  if dw_hero_img
+    assign dw_has_hero = true
+  endif
+-%}"""
+old_bg="{% if dw_has_hero %}style=\"background-image:url({{ collection.image | img_url: '2048x' }});\"{% endif %}"
+new_bg="{% if dw_has_hero %}style=\"background-image:url({{ dw_hero_img | img_url: '2048x' }});\"{% endif %}"
+if 'dw_hero_img' in v:
+    print("fallback already applied — no change"); sys.exit(0)
+reps=[(old_block,new_block),(old_bg,new_bg)]
+miss=[o[:40] for o,_ in reps if o not in v]
+if miss: print("target(s) not found:",miss); sys.exit(1)
+if not APPLY:
+    print("DRY: 2 edits located ✓ — hero falls back to first product image (fixes 85 gaps)"); sys.exit(0)
+nv=v
+for o,n in reps: nv=nv.replace(o,n,1)
+print("PUT:", "OK" if 'asset' in p(nv) else "FAIL")
diff --git a/hero-polish.py b/hero-polish.py
new file mode 100644
index 0000000..c0ec917
--- /dev/null
+++ b/hero-polish.py
@@ -0,0 +1,30 @@
+import re,json,sys,urllib.request
+env=open('/Users/macstudio3/Projects/secrets-manager/.env').read()
+tok=re.search(r'^SHOPIFY_THEME_TOKEN=(.+)$',env,re.M).group(1).strip().strip('"').strip("'")
+S="designer-laboratory-sandbox.myshopify.com";A="2024-10";TID=144396058675;KEY="sections/dw-collection-hero.liquid"
+APPLY='--apply' in sys.argv
+def g():
+    r=urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json?asset[key]={KEY}",headers={"X-Shopify-Access-Token":tok})
+    return json.load(urllib.request.urlopen(r,timeout=30))['asset']['value']
+def p(v):
+    b=json.dumps({"asset":{"key":KEY,"value":v}}).encode()
+    r=urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json",data=b,method="PUT",headers={"X-Shopify-Access-Token":tok,"Content-Type":"application/json"})
+    return json.load(urllib.request.urlopen(r,timeout=30))
+v=g()
+reps=[
+ # 1 · trim intro to <=120 chars
+ ("for the interior design trade — {{ collection.products_count }} designs, with samples and trade pricing on request.",
+  "for the design trade — {{ collection.products_count }} designs, samples and trade pricing on request."),
+ # 2 · centered, visible "Learn about" pill
+ ('<details class="dw-collection-faq" style="max-width:900px;margin:.75rem auto 0;padding:0 1rem;"><summary style="cursor:pointer;font:500 .95rem/1.4 inherit;letter-spacing:.02em;opacity:.75;">',
+  '<details class="dw-collection-faq" style="max-width:900px;margin:1rem auto .5rem;text-align:center;"><summary style="cursor:pointer;display:inline-block;list-style:none;font:600 .8rem/1.5 inherit;letter-spacing:.08em;text-transform:uppercase;padding:.55rem 1.5rem;border:1px solid currentColor;border-radius:2px;opacity:.9;">'),
+ ('<div style="padding:.5rem 0 .25rem;">{%- for item in aeo_faq -%}',
+  '<div style="text-align:left;max-width:760px;margin:.9rem auto 0;padding:0 1rem;">{%- for item in aeo_faq -%}'),
+]
+miss=[o[:45] for o,_ in reps if o not in v]
+if miss: print("already applied or not found:",miss); sys.exit(0 if len(miss)==len(reps) else 1)
+if not APPLY:
+    print(f"DRY: {len(reps)} edits located ✓  (intro trim + centered/visible pill)"); sys.exit(0)
+nv=v
+for o,n in reps: nv=nv.replace(o,n,1)
+print("PUT:", "OK" if 'asset' in p(nv) else "FAIL")
diff --git a/set-categories.py b/set-categories.py
new file mode 100644
index 0000000..de791cb
--- /dev/null
+++ b/set-categories.py
@@ -0,0 +1,41 @@
+import re,json,sys,urllib.request
+env=open('/Users/macstudio3/Projects/secrets-manager/.env').read()
+tok=re.search(r'^SHOPIFY_CONTENT_TOKEN=(.+)$',env,re.M).group(1).strip().strip('"').strip("'")
+S="designer-laboratory-sandbox.myshopify.com";A="2024-10"
+APPLY='--apply' in sys.argv; FORCE='--force' in sys.argv
+def api(p,method="GET",body=None):
+    d=json.dumps(body).encode() if body else None
+    r=urllib.request.Request(f"https://{S}/admin/api/{A}/{p}",data=d,method=method,headers={"X-Shopify-Access-Token":tok,"Content-Type":"application/json"})
+    return json.load(urllib.request.urlopen(r,timeout=30))
+LABEL={"grasscloth":"grasscloth","faux-leather":"faux leather","velvet":"flocked velvet","cork":"cork",
+       "silk":"silk","suede":"suede","metallic":"metallic","linen":"linen","vinyl":"vinyl","mural":"mural"}
+MATCH=[("faux-leather",r'faux.?leather|leather'),("velvet",r'flock|velvet'),("suede",r'suede'),("cork",r'\bcork'),
+       ("silk",r'silk'),("metallic",r'metallic|mylar|foil'),("linen",r'linen'),("mural",r'mural'),
+       ("grasscloth",r'grasscloth|sisal|jute|hemp|raffia|\bnatural'),("vinyl",r'vinyl')]
+cols=[]
+for kind in ("custom_collections","smart_collections"):
+    since=0
+    while True:
+        d=api(f"{kind}.json?limit=250&since_id={since}&fields=id,handle,title,body_html")[kind]
+        if not d: break
+        for c in d:
+            body=re.sub(r'<[^>]+>','',c.get('body_html') or '').strip()
+            cols.append({"id":c['id'],"h":c['handle'],"t":c['title'],"blen":len(body)})
+        since=d[-1]['id']
+plan=[]
+for c in cols:
+    if c['blen']<50: continue
+    hay=(c['h']+' '+c['t']).lower()
+    m=next((mm for mm,rx in MATCH if re.search(rx,hay)),None)
+    if m: plan.append((c,LABEL[m]))
+print(f"{len(plan)} collections would get custom.category")
+if not APPLY:
+    for c,lab in plan[:8]: print(f"  {lab:14} <- {c['h']}")
+    print(f"  (+{max(0,len(plan)-8)} more)  re-run --apply"); sys.exit(0)
+ch=sk=0
+for c,lab in plan:
+    ex=api(f"collections/{c['id']}/metafields.json?namespace=custom&key=category")['metafields']
+    if ex and not FORCE: sk+=1; continue
+    r=api(f"collections/{c['id']}/metafields.json","POST",{"metafield":{"namespace":"custom","key":"category","type":"single_line_text_field","value":lab}})
+    ch+= 'metafield' in r
+print(f"APPLIED — set={ch} skipped={sk} of {len(plan)}")

← e415f0e auto-save: 2026-07-21T17:38:50 (1 files) — deploy-faqs-bymat  ·  back to Secrets Manager  ·  auto-save: 2026-07-22T07:42:20 (1 files) — audits/ d09d321 →