[object Object]

← back to Secrets Manager

auto-save: 2026-07-22T09:12:53 (5 files) — coordonne-collab-audit/coco-create-result.json _shopify_channel_fill.py coordonne-collab-audit/check-membership.js coordonne-collab-audit/coco-create-mural.json coordonne-collab-audit/create-coco-mural.js

b45ce415b2bbcd6a82c0f4ee6fb55dc0275f50a3 · 2026-07-22 09:12:54 -0700 · Steve Abrams

Files touched

Diff

commit b45ce415b2bbcd6a82c0f4ee6fb55dc0275f50a3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 22 09:12:54 2026 -0700

    auto-save: 2026-07-22T09:12:53 (5 files) — coordonne-collab-audit/coco-create-result.json _shopify_channel_fill.py coordonne-collab-audit/check-membership.js coordonne-collab-audit/coco-create-mural.json coordonne-collab-audit/create-coco-mural.js
---
 _shopify_channel_fill.py                       | 113 ++++++++++++++
 coordonne-collab-audit/check-membership.js     |  20 +++
 coordonne-collab-audit/coco-create-mural.json  | 146 ++++++++++++++++++
 coordonne-collab-audit/coco-create-result.json | 199 ++++++++++++++++++++++++-
 coordonne-collab-audit/create-coco-mural.js    | 142 ++++++++++++++++++
 5 files changed, 612 insertions(+), 8 deletions(-)

diff --git a/_shopify_channel_fill.py b/_shopify_channel_fill.py
new file mode 100644
index 0000000..a3cdd19
--- /dev/null
+++ b/_shopify_channel_fill.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+"""Publish active products to all reachable sales-channel publications.
+Excludes peel-and-stick lines (Surface Stick, PS Removable Wallpaper) per Steve.
+Modes: --test N  (dry-ish: publish only N products, verbose)
+       --run      (full run, cursor pagination, log progress)
+"""
+import os, sys, json, time, urllib.request
+
+STORE = "designer-laboratory-sandbox"
+API = f"https://{STORE}.myshopify.com/admin/api/2024-10/graphql.json"
+EXCLUDE_VENDORS = {"Surface Stick", "PS Removable Wallpaper"}
+
+def token():
+    with open(os.path.expanduser("~/Projects/secrets-manager/.env")) as f:
+        for line in f:
+            if line.startswith("SHOPIFY_ADMIN_TOKEN="):
+                return line.split("=",1)[1].strip().strip('"').strip("'")
+    raise SystemExit("no token")
+
+TOKEN = token()
+
+def gql(query, variables=None):
+    body = json.dumps({"query": query, "variables": variables or {}}).encode()
+    req = urllib.request.Request(API, data=body, headers={
+        "X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"})
+    for attempt in range(6):
+        try:
+            with urllib.request.urlopen(req, timeout=60) as r:
+                d = json.loads(r.read())
+            if "errors" in d and any("throttled" in json.dumps(e).lower() for e in d.get("errors",[])):
+                time.sleep(2 + attempt*2); continue
+            return d
+        except Exception as e:
+            time.sleep(2 + attempt*2)
+    return {"errors":"exhausted"}
+
+def get_publications():
+    d = gql("{ publications(first:50){ edges{ node{ id name } } } }")
+    return [(e["node"]["id"], e["node"]["name"]) for e in d["data"]["publications"]["edges"]]
+
+PUBS = get_publications()
+PUB_IDS = [p[0] for p in PUBS]
+
+PAGE_Q = """
+query($cursor:String){
+  products(first:60, after:$cursor, query:"status:active"){
+    pageInfo{ hasNextPage endCursor }
+    edges{ node{ id vendor
+      resourcePublications(first:30){ edges{ node{ publication{ id } } } }
+    } }
+  }
+}"""
+
+PUBLISH_M = """
+mutation($id:ID!, $pubs:[PublicationInput!]!){
+  publishablePublish(id:$id, input:$pubs){ userErrors{ field message } }
+}"""
+
+def missing_pubs(node):
+    on = {e["node"]["publication"]["id"] for e in node["resourcePublications"]["edges"]}
+    return [pid for pid in PUB_IDS if pid not in on]
+
+def run(limit=None, test=False):
+    print(f"Publications reachable: {len(PUBS)}")
+    for pid,name in PUBS: print(f"  - {name}")
+    print(f"Excluding vendors: {EXCLUDE_VENDORS}\n")
+    cursor=None; seen=0; skipped_ps=0; already=0; published=0; errs=0; pages=0
+    t0=time.time()
+    while True:
+        d = gql(PAGE_Q, {"cursor": cursor})
+        try:
+            conn = d["data"]["products"]
+        except Exception:
+            print("PAGE ERROR:", json.dumps(d)[:300]); break
+        pages+=1
+        for e in conn["edges"]:
+            n=e["node"]; seen+=1
+            if n["vendor"] in EXCLUDE_VENDORS:
+                skipped_ps+=1; continue
+            miss = missing_pubs(n)
+            if not miss:
+                already+=1; continue
+            res = gql(PUBLISH_M, {"id": n["id"], "pubs":[{"publicationId":p} for p in miss]})
+            ue = res.get("data",{}).get("publishablePublish",{}).get("userErrors") if res.get("data") else None
+            if ue:
+                errs+=1
+                if errs<=10: print(f"  userErr {n['id']}: {ue[:1]}")
+            else:
+                published+=1
+                if test: print(f"  published {n['id']} (+{len(miss)} chans)")
+            if limit and (published+already+skipped_ps)>=limit and test:
+                print("\nTEST LIMIT reached"); _summary(seen,skipped_ps,already,published,errs,pages,t0); return
+            time.sleep(0.08)
+        if seen % 600 == 0 or test:
+            print(f"[{seen} seen | pub {published} | already {already} | ps-skip {skipped_ps} | err {errs} | {int(time.time()-t0)}s]")
+        if not conn["pageInfo"]["hasNextPage"]:
+            break
+        cursor = conn["pageInfo"]["endCursor"]
+        if limit and test and seen>=limit: break
+    _summary(seen,skipped_ps,already,published,errs,pages,t0)
+
+def _summary(seen,ps,already,pub,errs,pages,t0):
+    print(f"\nSUMMARY seen={seen} ps_skipped={ps} already_full={already} published={pub} errors={errs} pages={pages} secs={int(time.time()-t0)}")
+    print("DONE")
+
+if __name__=="__main__":
+    if "--test" in sys.argv:
+        i=sys.argv.index("--test"); N=int(sys.argv[i+1]) if len(sys.argv)>i+1 else 5
+        run(limit=N, test=True)
+    elif "--run" in sys.argv:
+        run()
+    else:
+        print("usage: --test N | --run")
diff --git a/coordonne-collab-audit/check-membership.js b/coordonne-collab-audit/check-membership.js
new file mode 100644
index 0000000..9e09b90
--- /dev/null
+++ b/coordonne-collab-audit/check-membership.js
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+// Verify REAL collection membership by paging products (not the lagging productsCount cache).
+const fs=require('fs'),path=require('path');
+const ENV=fs.readFileSync(path.join(__dirname,'..','.env'),'utf8');
+const TOKEN=(ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1];
+const DOMAIN='designer-laboratory-sandbox.myshopify.com';
+const handle=process.argv[2];
+(async()=>{
+  const cr=await fetch(`https://${DOMAIN}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:`{collections(first:1,query:"handle:${handle}"){nodes{id title productsCount{count}}}}`})});
+  const cj=await cr.json(); const c=cj.data.collections.nodes[0];
+  if(!c){console.log('NO COLLECTION for handle',handle);return;}
+  let after=null,total=0;
+  while(true){
+    const q=`query($id:ID!,$after:String){ collection(id:$id){ products(first:250, after:$after){ pageInfo{hasNextPage endCursor} nodes{ id } } } }`;
+    const r=await fetch(`https://${DOMAIN}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:{id:c.id,after}})});
+    const j=await r.json(); const p=j.data.collection.products; total+=p.nodes.length;
+    if(!p.pageInfo.hasNextPage) break; after=p.pageInfo.endCursor;
+  }
+  console.log(`${handle}: REAL membership=${total} | cache productsCount=${c.productsCount.count}`);
+})();
diff --git a/coordonne-collab-audit/coco-create-mural.json b/coordonne-collab-audit/coco-create-mural.json
new file mode 100644
index 0000000..8ab97be
--- /dev/null
+++ b/coordonne-collab-audit/coco-create-mural.json
@@ -0,0 +1,146 @@
+[
+  {
+    "dw_sku": "DWCO2-670247",
+    "pattern_name": "Apreski Blue",
+    "real_sku": "C00139",
+    "description": "Coordonné's Apreski Blue is a Landscape design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/apreski-blue-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670248",
+    "pattern_name": "Apreski Pink",
+    "real_sku": "C00140",
+    "description": "Coordonné's Apreski Pink is a Landscape design in Pink/Lilac from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/apreski-pink-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670249",
+    "pattern_name": "Apreski Cobalt",
+    "real_sku": "C00141",
+    "description": "Coordonné's Apreski Cobalt is a Landscape design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/apreski-cobalt-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670250",
+    "pattern_name": "Apreski Sunset",
+    "real_sku": "C00142",
+    "description": "Coordonné's Apreski Sunset is a Landscape design in Yellow/Orange from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/apreski-sunset-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670251",
+    "pattern_name": "Maravillas Pink",
+    "real_sku": "C00143",
+    "description": "Coordonné's Maravillas Pink is a Vegetal design in Pink/Lilac from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/maravillas-pink-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670252",
+    "pattern_name": "Maravillas Golden",
+    "real_sku": "C00144",
+    "description": "Coordonné's Maravillas Golden is a Vegetal design in Yellow/Orange from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/maravillas-golden-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670253",
+    "pattern_name": "Maravillas Blue",
+    "real_sku": "C00145",
+    "description": "Coordonné's Maravillas Blue is a Vegetal design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/maravillas-blue-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670254",
+    "pattern_name": "Equilibrio Blue",
+    "real_sku": "C00146",
+    "description": "Coordonné's Equilibrio Blue is a People design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/equilibrio-blue-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670255",
+    "pattern_name": "Equilibrio Golden",
+    "real_sku": "C00147",
+    "description": "Coordonné's Equilibrio Golden is a People design in Yellow/Orange from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/equilibrio-golden-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670256",
+    "pattern_name": "Equilibrio Cobalt",
+    "real_sku": "C00148",
+    "description": "Coordonné's Equilibrio Cobalt is a People design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/equilibrio-cobalt-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670257",
+    "pattern_name": "Equilibrio Pistachio",
+    "real_sku": "C00149",
+    "description": "Coordonné's Equilibrio Pistachio is a People design in Green from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/equilibrio-pistachio-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670258",
+    "pattern_name": "Escapada Blue",
+    "real_sku": "C00150",
+    "description": "Coordonné's Escapada Blue is a Landscapes design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/escapada-blue-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670259",
+    "pattern_name": "Escapada Natural",
+    "real_sku": "C00151",
+    "description": "Coordonné's Escapada Natural is a Landscape design in Brown from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/escapada-natural-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670260",
+    "pattern_name": "Escapada Cobalt",
+    "real_sku": "C00152",
+    "description": "Coordonné's Escapada Cobalt is a Landscape design in Pink/Lilac from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/escapada-cobalt-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670261",
+    "pattern_name": "Escapada Golden",
+    "real_sku": "C00153",
+    "description": "Coordonné's Escapada Golden is a Landscape design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/escapada-golden-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  },
+  {
+    "dw_sku": "DWCO2-670262",
+    "pattern_name": "Escapada Hummingbird",
+    "real_sku": "C00154",
+    "description": "Coordonné's Escapada Hummingbird is a Landscape design in Blue/Navy from the Coco Dávez Colortherapills collection.",
+    "material": "Non-woven / Vinyl",
+    "image_url": "https://coordonne.com/wp-content/uploads/2025/08/escapada-hummingbird-repeated-patterns-coordonne.jpg",
+    "width": "157.5\" (400 cm)"
+  }
+]
\ No newline at end of file
diff --git a/coordonne-collab-audit/coco-create-result.json b/coordonne-collab-audit/coco-create-result.json
index 9ef9720..7961f38 100644
--- a/coordonne-collab-audit/coco-create-result.json
+++ b/coordonne-collab-audit/coco-create-result.json
@@ -1,17 +1,200 @@
 {
-  "created": 1,
+  "created": 0,
   "err": 0,
-  "total": 1,
+  "total": 16,
   "collection_tag": "Coco D Vez Colortherapills",
   "results": [
     {
-      "dwSku": "DWCO2-670208",
-      "shopify_id": "gid://shopify/Product/7896276664371",
-      "handle": "picnic-blue-coordonne",
+      "dwSku": "DWCO2-670247",
+      "title": "Apreski Blue | Coordonné",
+      "realSku": "C00139",
       "status": "ACTIVE",
-      "title": "Picnic Blue | Coordonné",
-      "realSku": "C00101",
-      "variantErrors": []
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Apreski Blue"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670248",
+      "title": "Apreski Pink | Coordonné",
+      "realSku": "C00140",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Apreski Pink"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670249",
+      "title": "Apreski Cobalt | Coordonné",
+      "realSku": "C00141",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Apreski Cobalt"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670250",
+      "title": "Apreski Sunset | Coordonné",
+      "realSku": "C00142",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Apreski Sunset"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670251",
+      "title": "Maravillas Pink | Coordonné",
+      "realSku": "C00143",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Maravillas Pink"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670252",
+      "title": "Maravillas Golden | Coordonné",
+      "realSku": "C00144",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Maravillas Golden"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670253",
+      "title": "Maravillas Blue | Coordonné",
+      "realSku": "C00145",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Maravillas Blue"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670254",
+      "title": "Equilibrio Blue | Coordonné",
+      "realSku": "C00146",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Equilibrio Blue"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670255",
+      "title": "Equilibrio Golden | Coordonné",
+      "realSku": "C00147",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Equilibrio Golden"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670256",
+      "title": "Equilibrio Cobalt | Coordonné",
+      "realSku": "C00148",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Equilibrio Cobalt"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670257",
+      "title": "Equilibrio Pistachio | Coordonné",
+      "realSku": "C00149",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Equilibrio Pistachio"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670258",
+      "title": "Escapada Blue | Coordonné",
+      "realSku": "C00150",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Escapada Blue"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670259",
+      "title": "Escapada Natural | Coordonné",
+      "realSku": "C00151",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Escapada Natural"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670260",
+      "title": "Escapada Cobalt | Coordonné",
+      "realSku": "C00152",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Escapada Cobalt"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670261",
+      "title": "Escapada Golden | Coordonné",
+      "realSku": "C00153",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Escapada Golden"
+      ]
+    },
+    {
+      "dwSku": "DWCO2-670262",
+      "title": "Escapada Hummingbird | Coordonné",
+      "realSku": "C00154",
+      "status": "ACTIVE",
+      "tags": [
+        "Coordonné",
+        "Coco D Vez Colortherapills",
+        "Wallcovering",
+        "Escapada Hummingbird"
+      ]
     }
   ]
 }
\ No newline at end of file
diff --git a/coordonne-collab-audit/create-coco-mural.js b/coordonne-collab-audit/create-coco-mural.js
new file mode 100644
index 0000000..d225fbb
--- /dev/null
+++ b/coordonne-collab-audit/create-coco-mural.js
@@ -0,0 +1,142 @@
+#!/usr/bin/env node
+// PG-first-then-Shopify creation of the 16 Coco Dávez $113.62 mural products (DTD Option C).
+// Per DTD verdict C: murals held. Per DW rules:
+//  - PostgreSQL (coordonne_catalog staging) updated FIRST with real_sku + description,
+//    then Shopify create (authoritative), then shopify_product_id written back.
+//  - Title = "<pattern_name> | Coordonné" (pattern_name already = Pattern + real colorway).
+//  - Sample variant {DW_SKU}-Sample @ $4.25, inventory NOT tracked.
+//  - Sellable roll variant {DW_SKU} @ $169.
+//  - Featured image, description, vendor Coordonné, real mfr SKU (C-code) on the roll variant.
+//  - Tags >= 2 incl EXACT smart-collection rule tag "Coco D Vez Colortherapills" (accent-normalized).
+//  - Word "Wallpaper" BANNED. product_type = Wallcovering.
+//  - NEVER ACTIVE without image AND width metafield -> created ACTIVE only when both present,
+//    else DRAFT + Needs-Image / Needs-Width tag.
+const fs = require('fs'), path = require('path');
+const { execSync } = require('child_process');
+const ENV = fs.readFileSync(path.join(__dirname, '..', '.env'), 'utf8');
+const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1];
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com', VER = '2024-10';
+if (!TOKEN) { console.error('no token'); process.exit(1); }
+
+const COLLECTION_TAG = 'Coco D Vez Colortherapills'; // EXACT accent-normalized smart-collection rule
+const INFILE = process.env.INFILE || 'coco-create-mural.json';
+const rows = JSON.parse(fs.readFileSync(path.join(__dirname, INFILE), 'utf8'));
+const LIMIT = parseInt(process.env.LIMIT || '0', 10) || rows.length;
+const DRY = process.env.DRY === '1';
+
+function pg(sql) {
+  return execSync(`psql "host=/tmp dbname=dw_unified" -tA -c ${JSON.stringify(sql)}`, { encoding: 'utf8' }).trim();
+}
+async function gql(query, variables) {
+  const r = await fetch(`https://${DOMAIN}/admin/api/${VER}/graphql.json`, {
+    method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+    body: JSON.stringify({ query, variables }),
+  });
+  return r.json();
+}
+
+const M_CREATE = `mutation($input: ProductInput!, $media: [CreateMediaInput!]) {
+  productCreate(input: $input, media: $media) {
+    product { id handle status title
+      variants(first:5){ nodes{ id title sku } }
+      media(first:1){ nodes{ ... on MediaImage { id } } }
+    }
+    userErrors { field message }
+  }
+}`;
+const M_VARIANTS_SET = `mutation($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
+  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
+    productVariants { id sku price }
+    userErrors { field message }
+  }
+}`;
+const M_VARIANTS_CREATE = `mutation($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
+  productVariantsBulkCreate(productId: $productId, variants: $variants) {
+    productVariants { id sku price selectedOptions{name value} }
+    userErrors { field message }
+  }
+}`;
+
+function esc(s) { return String(s || '').replace(/'/g, "''"); }
+
+(async () => {
+  const results = [];
+  let created = 0, err = 0;
+  for (const row of rows.slice(0, LIMIT)) {
+    const dwSku = row.dw_sku;
+    const title = `${row.pattern_name} | Coordonné`;
+    const realSku = row.real_sku;           // C-code
+    const desc = row.description;
+    const material = row.material || row.material;
+    const img = row.image_url;
+    const width = row.width && row.width.trim() ? row.width.trim() : null;
+    const hasImg = !!img;
+    const hasWidth = !!width;
+    const activateOK = hasImg && hasWidth;
+    const status = activateOK ? 'ACTIVE' : 'DRAFT';
+    const tags = ['Coordonné', COLLECTION_TAG, 'Wallcovering', row.pattern_name];
+    if (!hasImg) tags.push('Needs-Image');
+    if (!hasWidth) tags.push('Needs-Width');
+
+    if (DRY) { results.push({ dwSku, title, realSku, status, tags }); process.stderr.write(`[DRY] ${dwSku} ${title} status=${status}\n`); continue; }
+
+    // 1) PG-FIRST: stamp staging with cleaned description ONLY.
+    //    Do NOT overwrite mfr_sku with the real C-code: the Coco Dávez row keeps its
+    //    internal COORD- code, and the real C-code already lives on a parallel
+    //    generic-collection row (mfr_sku is UNIQUE, overwriting would collide).
+    //    The real C-code is carried customer-facing on the Shopify manufacturer_sku metafield.
+    pg(`UPDATE coordonne_catalog SET about_vendor='${esc(desc)}', updated_at=now() WHERE dw_sku='${esc(dwSku)}'`);
+
+    // 2) Shopify create — product shell + featured image + description + metafields
+    const input = {
+      title,
+      descriptionHtml: `<p>${desc}</p>`,
+      vendor: 'Coordonné',
+      productType: 'Wallcovering',
+      status,
+      tags,
+      metafields: [
+        { namespace: 'custom', key: 'manufacturer_sku', type: 'single_line_text_field', value: realSku },
+        { namespace: 'custom', key: 'pattern_name', type: 'single_line_text_field', value: row.pattern_name },
+        { namespace: 'custom', key: 'brand', type: 'single_line_text_field', value: 'Coordonné' },
+        { namespace: 'custom', key: 'material', type: 'multi_line_text_field', value: material },
+      ].concat(width ? [{ namespace: 'custom', key: 'width', type: 'single_line_text_field', value: width }] : []),
+      productOptions: [{ name: 'Size', values: [{ name: 'Sample' }] }],
+    };
+    const media = hasImg ? [{ originalSource: img, mediaContentType: 'IMAGE', alt: title }] : null;
+    const cr = await gql(M_CREATE, { input, media });
+    const ue = cr?.data?.productCreate?.userErrors;
+    if (ue && ue.length) { err++; results.push({ dwSku, status: 'error', errors: ue }); process.stderr.write(`ERR ${dwSku}: ${JSON.stringify(ue)}\n`); continue; }
+    const prod = cr?.data?.productCreate?.product;
+    if (!prod) { err++; results.push({ dwSku, status: 'gql-error', errors: cr.errors }); process.stderr.write(`GQLERR ${dwSku}: ${JSON.stringify(cr.errors)}\n`); continue; }
+    const pid = prod.id;
+
+    // 3a) productCreate made ONE default variant (Size=Sample). Update it to the
+    //     $4.25 sample memo variant, sku {DW_SKU}-Sample, inventory NOT tracked.
+    const sampleV = prod.variants.nodes[0];
+    const vr1 = await gql(M_VARIANTS_SET, { productId: pid, variants: [
+      { id: sampleV.id, price: '4.25', inventoryItem: { sku: `${dwSku}-Sample`, tracked: false } },
+    ] });
+    const vue1 = vr1?.data?.productVariantsBulkUpdate?.userErrors || [];
+    if (vue1.length) process.stderr.write(`VARERR-sample ${dwSku}: ${JSON.stringify(vue1)}\n`);
+
+    // 3b) Create the sellable Roll variant @ $169, sku {DW_SKU}, inventory NOT tracked.
+    const vr2 = await gql(M_VARIANTS_CREATE, { productId: pid, variants: [
+      { optionValues: [{ optionName: 'Size', name: 'Roll' }], price: '113.62', inventoryItem: { sku: dwSku, tracked: false } },
+    ] });
+    const vue2 = vr2?.data?.productVariantsBulkCreate?.userErrors || [];
+    if (vue2.length) process.stderr.write(`VARERR-roll ${dwSku}: ${JSON.stringify(vue2)}\n`);
+    const vue = [...vue1, ...vue2];
+
+    // 4) PG write-back: shopify_product_id
+    const numId = pid.split('/').pop();
+    pg(`UPDATE coordonne_catalog SET shopify_product_id='${numId}', updated_at=now() WHERE dw_sku='${esc(dwSku)}'`);
+
+    created++;
+    results.push({ dwSku, shopify_id: pid, handle: prod.handle, status: prod.status, title, realSku, variantErrors: vue || [] });
+    process.stderr.write(`OK ${dwSku} -> ${prod.handle} [${prod.status}]\n`);
+    await new Promise(r => setTimeout(r, 700)); // ~1.4/s, under limits
+  }
+  fs.writeFileSync(path.join(__dirname, process.env.OUTFILE || 'coco-create-result.json'), JSON.stringify({ created, err, total: Math.min(LIMIT, rows.length), collection_tag: COLLECTION_TAG, results }, null, 2));
+  console.log(JSON.stringify({ created, err, total: Math.min(LIMIT, rows.length) }, null, 2));
+})();

← 4614978 Coco Dávez canary: create 38 $169 wallcoverings PG-first→Sho  ·  back to Secrets Manager  ·  Coordonné Coco Dávez: create 16 held murals @ $113.62 (DTD O 2930b39 →