← 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
A _shopify_channel_fill.pyA coordonne-collab-audit/check-membership.jsA coordonne-collab-audit/coco-create-mural.jsonM coordonne-collab-audit/coco-create-result.jsonA coordonne-collab-audit/create-coco-mural.js
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 →