[object Object]

← back to Dw Yolo Loop

Color-SEO landings: SEO title + meta description + editorial intro on all 17 live color collections (council idea #2 'Wallcoverings by Color'); GraphQL collectionUpdate via write_products

69673157a15907d13fa0fb98277345b55ffd7dcb · 2026-06-15 08:03:49 -0700 · Steve Abrams

Files touched

Diff

commit 69673157a15907d13fa0fb98277345b55ffd7dcb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 15 08:03:49 2026 -0700

    Color-SEO landings: SEO title + meta description + editorial intro on all 17 live color collections (council idea #2 'Wallcoverings by Color'); GraphQL collectionUpdate via write_products
---
 scripts/color-seo/push-color-seo.cjs | 57 ++++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/scripts/color-seo/push-color-seo.cjs b/scripts/color-seo/push-color-seo.cjs
new file mode 100644
index 0000000..71ebc8f
--- /dev/null
+++ b/scripts/color-seo/push-color-seo.cjs
@@ -0,0 +1,57 @@
+const fs=require('fs');
+const env=fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8');
+const get=k=>{const m=env.match(new RegExp('^'+k+'=(.*)$','m'));return m?m[1].trim():'';};
+const TOKEN=get('SHOPIFY_ADMIN_TOKEN');           // write_products covers collections
+const HOST='designer-laboratory-sandbox.myshopify.com';
+const GQL=`https://${HOST}/admin/api/2025-01/graphql.json`;
+const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
+
+// 17 live color collections + a per-color editorial note (keeps each landing unique — no dup-content)
+const COLORS=[
+ ['black','Black','for dramatic, high-contrast interiors'],
+ ['white-wallcoverings','White','for light, gallery-clean walls'],
+ ['gray-wallcoverings','Gray','the versatile neutral that anchors any palette'],
+ ['grey-wallcovering','Grey','the versatile neutral that anchors any palette'],
+ ['blue-wallpaper','Blue','from serene powder to deep, inky tones'],
+ ['navy-wallcoverings','Navy','rich, sophisticated, and timeless'],
+ ['green-wallcoverings','Green','from restful sage to jewel-box emerald'],
+ ['teal-wallcoverings','Teal','a confident blend of blue and green'],
+ ['red-wallcoverings','Red','bold, warm, and unmistakably luxe'],
+ ['pink-wallcoverings','Pink','from soft blush to vivid fuchsia'],
+ ['purple','Purple','regal, moody, and modern'],
+ ['yellow-wallcoverings','Yellow','sunlit and optimistic'],
+ ['gold-wallcoverings','Gold','metallic warmth and quiet glamour'],
+ ['orange','Orange','spice-warm and energizing'],
+ ['brown-wallcoverings','Brown','earthy, grounding, and tactile'],
+ ['beige-wallcoverings','Beige','the quiet-luxury neutral'],
+ ['silver-wallcoverings','Silver','cool metallic shimmer'],
+];
+const BANNED=/wallquest|chesapeake|brewster|nextwall|seabrook|nicolette/i;
+
+function copy(color,note){
+  const title=`${color} Wallcoverings & Wallpaper | Designer Wallcoverings`;
+  const desc=`Shop ${color.toLowerCase()} wallcoverings & wallpaper at Designer Wallcoverings — ${note}. Premier design houses, to the trade & retail.`.slice(0,160);
+  const body=`<p>${color} wallcoverings — ${note}. Designer Wallcoverings curates ${color.toLowerCase()} wallpaper and wallcoverings from the world's premier design houses: grasscloths, silks, flocks, metallics, and printed papers across every shade of ${color.toLowerCase()}. Order memo samples online; available to the trade and retail.</p>`;
+  return {title,desc,body};
+}
+
+async function gql(query,variables){const r=await fetch(GQL,{method:'POST',headers:H,body:JSON.stringify({query,variables})});return r.json();}
+
+const ONLY=(process.argv.find(a=>a.startsWith('--only='))||'').split('=')[1];
+(async()=>{
+  const targets=ONLY?COLORS.filter(c=>c[0]===ONLY):COLORS;
+  let ok=0,err=0;
+  for(const [handle,color,note] of targets){
+    const c=copy(color,note);
+    if(BANNED.test(c.title+c.desc+c.body)){console.log('ABORT leak',handle);continue;}
+    const find=await gql(`query($h:String!){collectionByHandle(handle:$h){id title}}`,{h:handle});
+    const id=find.data&&find.data.collectionByHandle&&find.data.collectionByHandle.id;
+    if(!id){console.log('  ?? no collection',handle,JSON.stringify(find.errors||'').slice(0,80));err++;continue;}
+    const m=await gql(`mutation($in:CollectionInput!){collectionUpdate(input:$in){collection{id handle} userErrors{field message}}}`,
+      {in:{id, descriptionHtml:c.body, seo:{title:c.title, description:c.desc}}});
+    const ue=m.data&&m.data.collectionUpdate&&m.data.collectionUpdate.userErrors;
+    if(ue&&ue.length){console.log('  ERR',handle,JSON.stringify(ue).slice(0,120));err++;}
+    else{console.log('  ✓',handle,'→ SEO title+desc+intro set');ok++;}
+  }
+  console.log(`\n[color-seo] updated=${ok} err=${err} of ${targets.length}`);
+})();

← 451c923 brands page: consolidate Scalamandré to single /collections  ·  back to Dw Yolo Loop  ·  Google feed scoping: live-Shopify-driven eligibility gate + 5de6726 →