← back to Majilite Onboard
Publish 153/160 Majilite $4.25 samples to live store (active, unpublished-to-channel); 7 handle-collisions + channel-publish blocked on token read_products/read_publications merchant approval
001730f47c3da800519cbf8494403a5bb96cba8e · 2026-08-10 10:27:35 -0700 · Steve Abrams
Files touched
A data/published.jsonA scripts/probe7.jsA scripts/publish_shopify.jsA scripts/reconcile.js
Diff
commit 001730f47c3da800519cbf8494403a5bb96cba8e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 10 10:27:35 2026 -0700
Publish 153/160 Majilite $4.25 samples to live store (active, unpublished-to-channel); 7 handle-collisions + channel-publish blocked on token read_products/read_publications merchant approval
---
data/published.json | 1106 ++++++++++++++++++++++++++++++++++++++++++++
scripts/probe7.js | 17 +
scripts/publish_shopify.js | 153 ++++++
scripts/reconcile.js | 42 ++
4 files changed, 1318 insertions(+)
diff --git a/data/published.json b/data/published.json
new file mode 100644
index 0000000..7fb1f2b
--- /dev/null
+++ b/data/published.json
@@ -0,0 +1,1106 @@
+{
+ "DWMJ-600001": {
+ "productId": "gid://shopify/Product/7916891701299",
+ "handle": "majilite-stature-storm",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600001-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:05.079Z"
+ },
+ "DWMJ-600002": {
+ "productId": "gid://shopify/Product/7916892946483",
+ "handle": "majilite-apollo-silver-icon",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600002-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:45.573Z"
+ },
+ "DWMJ-600003": {
+ "productId": "gid://shopify/Product/7916893077555",
+ "handle": "majilite-chinchilla-ice-floe",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600003-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:47.721Z"
+ },
+ "DWMJ-600004": {
+ "productId": "gid://shopify/Product/7916893175859",
+ "handle": "majilite-metallic-paragon-silver",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600004-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:49.860Z"
+ },
+ "DWMJ-600005": {
+ "productId": "gid://shopify/Product/7916893274163",
+ "handle": "majilite-metallic-raindrop-stainless-steel",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600005-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:52.367Z"
+ },
+ "DWMJ-600006": {
+ "productId": "gid://shopify/Product/7916893339699",
+ "handle": "majilite-brushed-finesse-pewter",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600006-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:54.504Z"
+ },
+ "DWMJ-600007": {
+ "productId": "gid://shopify/Product/7916893438003",
+ "handle": "majilite-reflection-aluminum",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600007-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:56.708Z"
+ },
+ "DWMJ-600008": {
+ "productId": "gid://shopify/Product/7916893536307",
+ "handle": "majilite-reflection-aluminum-icon",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600008-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:14:58.848Z"
+ },
+ "DWMJ-600009": {
+ "productId": "gid://shopify/Product/7916893601843",
+ "handle": "majilite-legacy-pearl-black-icon-gold-glitter",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600009-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:01.063Z"
+ },
+ "DWMJ-600010": {
+ "productId": "gid://shopify/Product/7916893667379",
+ "handle": "majilite-radiance-dove",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600010-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:03.446Z"
+ },
+ "DWMJ-600011": {
+ "productId": "gid://shopify/Product/7916893732915",
+ "handle": "majilite-finesse-mercury",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600011-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:05.526Z"
+ },
+ "DWMJ-600012": {
+ "productId": "gid://shopify/Product/7916893831219",
+ "handle": "majilite-shimmer-grey-pearl",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600012-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:07.689Z"
+ },
+ "DWMJ-600013": {
+ "productId": "gid://shopify/Product/7916893896755",
+ "handle": "majilite-infinity-onyx",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600013-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:09.939Z"
+ },
+ "DWMJ-600014": {
+ "productId": "gid://shopify/Product/7916893995059",
+ "handle": "majilite-legacy-pearl-black",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600014-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:12.157Z"
+ },
+ "DWMJ-600015": {
+ "productId": "gid://shopify/Product/7916894093363",
+ "handle": "majilite-stature-granite-icon",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600015-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:14.274Z"
+ },
+ "DWMJ-600016": {
+ "productId": "gid://shopify/Product/7916894158899",
+ "handle": "majilite-eclipse-fx-mercury",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600016-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:16.455Z"
+ },
+ "DWMJ-600017": {
+ "productId": "gid://shopify/Product/7916894257203",
+ "handle": "majilite-drizzle-black",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600017-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:18.520Z"
+ },
+ "DWMJ-600018": {
+ "productId": "gid://shopify/Product/7916894322739",
+ "handle": "majilite-techno-black",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600018-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:20.740Z"
+ },
+ "DWMJ-600019": {
+ "productId": "gid://shopify/Product/7916894421043",
+ "handle": "majilite-finesse-pearl-black",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600020": {
+ "productId": "gid://shopify/Product/7916894486579",
+ "handle": "majilite-presto-silver",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600021": {
+ "productId": "gid://shopify/Product/7916894584883",
+ "handle": "majilite-finesse-fx-silver",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600021-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:27.005Z"
+ },
+ "DWMJ-600022": {
+ "productId": "gid://shopify/Product/7916894650419",
+ "handle": "majilite-infinity-iceberg",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600023": {
+ "productId": "gid://shopify/Product/7916894715955",
+ "handle": "majilite-infinity-silver",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600023-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:29.804Z"
+ },
+ "DWMJ-600024": {
+ "productId": "gid://shopify/Product/7916894781491",
+ "handle": "majilite-legacy-mercury",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600025": {
+ "productId": "gid://shopify/Product/7916894847027",
+ "handle": "majilite-pony-moonbeam",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600025-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:32.664Z"
+ },
+ "DWMJ-600026": {
+ "productId": "gid://shopify/Product/7916894912563",
+ "handle": "majilite-eclipse-fx-silver",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600027": {
+ "productId": "gid://shopify/Product/7916894978099",
+ "handle": "majilite-pony-granite-grey",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600027-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:35.518Z"
+ },
+ "DWMJ-600028": {
+ "productId": "gid://shopify/Product/7916895043635",
+ "handle": "majilite-drizzle-black-silver",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600029": {
+ "productId": "gid://shopify/Product/7916895141939",
+ "handle": "majilite-echo-silver-black",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600029-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:38.104Z"
+ },
+ "DWMJ-600030": {
+ "productId": "gid://shopify/Product/7916895174707",
+ "handle": "majilite-shimmer-mercury",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600031": {
+ "productId": "gid://shopify/Product/7916895207475",
+ "handle": "majilite-starlite-stainless-steel",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600031-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:40.857Z"
+ },
+ "DWMJ-600032": {
+ "productId": "gid://shopify/Product/7916895273011",
+ "handle": "majilite-eclipse-fx-carbon",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600033": {
+ "productId": "gid://shopify/Product/7916895305779",
+ "handle": "majilite-nuance-nickel",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600033-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:43.602Z"
+ },
+ "DWMJ-600034": {
+ "productId": "gid://shopify/Product/7916895371315",
+ "handle": "majilite-status-silver-black",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600035": {
+ "productId": "gid://shopify/Product/7916895469619",
+ "handle": "majilite-burnished-metal-pewter",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600035-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:46.487Z"
+ },
+ "DWMJ-600036": {
+ "productId": "gid://shopify/Product/7916895535155",
+ "handle": "majilite-brushed-finesse-stainless-steel",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600037": {
+ "productId": "gid://shopify/Product/7916895633459",
+ "handle": "majilite-shimmer-charcoal",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600037-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:49.085Z"
+ },
+ "DWMJ-600038": {
+ "productId": "gid://shopify/Product/7916895666227",
+ "handle": "majilite-pony-black",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600039": {
+ "productId": "gid://shopify/Product/7916895731763",
+ "handle": "majilite-apollo-black-icon",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600039-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:51.814Z"
+ },
+ "DWMJ-600040": {
+ "productId": "gid://shopify/Product/7916895830067",
+ "handle": "majilite-chinchilla-black",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600041": {
+ "productId": "gid://shopify/Product/7916895895603",
+ "handle": "majilite-charm-white",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600041-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:54.390Z"
+ },
+ "DWMJ-600043": {
+ "productId": "gid://shopify/Product/7916896026675",
+ "handle": "majilite-capricorn-bright-star",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600043-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:57.185Z"
+ },
+ "DWMJ-600044": {
+ "productId": "gid://shopify/Product/7916896092211",
+ "handle": "majilite-farro-pearl-white-gold-stardust",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600045": {
+ "productId": "gid://shopify/Product/7916896190515",
+ "handle": "majilite-shimmer-polar-white",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600045-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:15:59.917Z"
+ },
+ "DWMJ-600046": {
+ "productId": "gid://shopify/Product/7916896256051",
+ "handle": "majilite-apollo-snow",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600047": {
+ "productId": "gid://shopify/Product/7916896321587",
+ "handle": "majilite-chinchilla-snow",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600047-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:02.594Z"
+ },
+ "DWMJ-600049": {
+ "productId": "gid://shopify/Product/7916896452659",
+ "handle": "majilite-shimmer-polar-white-gab-paraweave",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600049-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:05.501Z"
+ },
+ "DWMJ-600050": {
+ "productId": "gid://shopify/Product/7916896518195",
+ "handle": "majilite-finesse-pearl-white",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600051": {
+ "productId": "gid://shopify/Product/7916896583731",
+ "handle": "majilite-shimmer-ivory",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600051-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:08.117Z"
+ },
+ "DWMJ-600052": {
+ "productId": "gid://shopify/Product/7916896649267",
+ "handle": "majilite-legacy-white",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600053": {
+ "productId": "gid://shopify/Product/7916896714803",
+ "handle": "majilite-nuance-glacier",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600053-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:11.038Z"
+ },
+ "DWMJ-600054": {
+ "productId": "gid://shopify/Product/7916896813107",
+ "handle": "majilite-eclipse-hx-snow-white",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600055": {
+ "productId": "gid://shopify/Product/7916896911411",
+ "handle": "majilite-nuance-silver",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600055-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:13.650Z"
+ },
+ "DWMJ-600056": {
+ "productId": "gid://shopify/Product/7916896976947",
+ "handle": "majilite-nuance-snow",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600057": {
+ "productId": "gid://shopify/Product/7916897042483",
+ "handle": "majilite-finesse-hx-pure-white-silver-saffa",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600057-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:16.417Z"
+ },
+ "DWMJ-600058": {
+ "productId": "gid://shopify/Product/7916897140787",
+ "handle": "majilite-legacy-white-gabardine-black-saffa",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600059": {
+ "productId": "gid://shopify/Product/7916897206323",
+ "handle": "majilite-nuance-platinum",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600059-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:19.173Z"
+ },
+ "DWMJ-600060": {
+ "productId": "gid://shopify/Product/7916897271859",
+ "handle": "majilite-palisades-igloo",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600061": {
+ "productId": "gid://shopify/Product/7916897337395",
+ "handle": "majilite-finesse-pearl-gelato",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600061-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:21.783Z"
+ },
+ "DWMJ-600063": {
+ "productId": "gid://shopify/Product/7916897501235",
+ "handle": "majilite-pony-pearl-gelato",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600063-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:24.790Z"
+ },
+ "DWMJ-600064": {
+ "productId": "gid://shopify/Product/7916897632307",
+ "handle": "majilite-palisades-salt-1",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600064-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:27.038Z"
+ },
+ "DWMJ-600066": {
+ "productId": "gid://shopify/Product/7916897763379",
+ "handle": "majilite-charm-featherbed",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600066-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:30.263Z"
+ },
+ "DWMJ-600067": {
+ "productId": "gid://shopify/Product/7916897828915",
+ "handle": "majilite-sandwash-salt",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600068": {
+ "productId": "gid://shopify/Product/7916897927219",
+ "handle": "majilite-starlite-pearl-gelato",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600068-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:33.291Z"
+ },
+ "DWMJ-600069": {
+ "productId": "gid://shopify/Product/7916897959987",
+ "handle": "majilite-classic-platinum",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600070": {
+ "productId": "gid://shopify/Product/7916898058291",
+ "handle": "majilite-legacy-platinum",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600070-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:35.954Z"
+ },
+ "DWMJ-600071": {
+ "productId": "gid://shopify/Product/7916898123827",
+ "handle": "majilite-nuance-white-gold",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600072": {
+ "productId": "gid://shopify/Product/7916898189363",
+ "handle": "majilite-brushed-finesse-pearl-gelato",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600072-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:38.566Z"
+ },
+ "DWMJ-600073": {
+ "productId": "gid://shopify/Product/7916898222131",
+ "handle": "majilite-nuance-gold-leaf",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600074": {
+ "productId": "gid://shopify/Product/7916898287667",
+ "handle": "majilite-legacy-pearl-gelato",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600074-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:41.576Z"
+ },
+ "DWMJ-600075": {
+ "productId": "gid://shopify/Product/7916898385971",
+ "handle": "majilite-charm-ii-platinum",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600076": {
+ "productId": "gid://shopify/Product/7916898451507",
+ "handle": "majilite-hammered-metal-parchment",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600076-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:44.261Z"
+ },
+ "DWMJ-600077": {
+ "productId": "gid://shopify/Product/7916898517043",
+ "handle": "majilite-beton-platinum",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600078": {
+ "productId": "gid://shopify/Product/7916898582579",
+ "handle": "majilite-stature-icicle",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600078-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:47.030Z"
+ },
+ "DWMJ-600079": {
+ "productId": "gid://shopify/Product/7916898648115",
+ "handle": "majilite-eclipse-fx-icicle",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600080": {
+ "productId": "gid://shopify/Product/7916898713651",
+ "handle": "majilite-charm-pashmina",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600080-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:49.702Z"
+ },
+ "DWMJ-600081": {
+ "productId": "gid://shopify/Product/7916898779187",
+ "handle": "majilite-chinchilla-fawn",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600082": {
+ "productId": "gid://shopify/Product/7916898844723",
+ "handle": "majilite-charm-pearl-gelato",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600082-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:52.499Z"
+ },
+ "DWMJ-600083": {
+ "productId": "gid://shopify/Product/7916898877491",
+ "handle": "majilite-finesse-pearl-eggshell-gab-paraweave",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600084": {
+ "productId": "gid://shopify/Product/7916898975795",
+ "handle": "majilite-legacy-pearl-gelato-icon-gold-glitter",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600084-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:55.021Z"
+ },
+ "DWMJ-600085": {
+ "productId": "gid://shopify/Product/7916899041331",
+ "handle": "majilite-radiance-sand",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600086": {
+ "productId": "gid://shopify/Product/7916899106867",
+ "handle": "majilite-brushed-finesse-fawn",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600086-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:57.652Z"
+ },
+ "DWMJ-600087": {
+ "productId": "gid://shopify/Product/7916899237939",
+ "handle": "majilite-chinchilla-gold-leaf-1",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600087-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:16:59.773Z"
+ },
+ "DWMJ-600089": {
+ "productId": "gid://shopify/Product/7916899401779",
+ "handle": "majilite-linea-ash",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600089-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:02.696Z"
+ },
+ "DWMJ-600091": {
+ "productId": "gid://shopify/Product/7916899565619",
+ "handle": "majilite-charm-silver",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600091-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:05.710Z"
+ },
+ "DWMJ-600092": {
+ "productId": "gid://shopify/Product/7916899663923",
+ "handle": "majilite-nuance-fawn",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600093": {
+ "productId": "gid://shopify/Product/7916899729459",
+ "handle": "majilite-cross-hatch-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600093-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:08.295Z"
+ },
+ "DWMJ-600094": {
+ "productId": "gid://shopify/Product/7916899762227",
+ "handle": "majilite-infinity-pebble",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600095": {
+ "productId": "gid://shopify/Product/7916899794995",
+ "handle": "majilite-starlite-fawn",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600095-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:10.937Z"
+ },
+ "DWMJ-600096": {
+ "productId": "gid://shopify/Product/7916899827763",
+ "handle": "majilite-finesse-pearl-cr-me",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600097": {
+ "productId": "gid://shopify/Product/7916899860531",
+ "handle": "majilite-shimmer-mist-pique",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600097-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:13.666Z"
+ },
+ "DWMJ-600098": {
+ "productId": "gid://shopify/Product/7916899958835",
+ "handle": "majilite-splendor-sand",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600099": {
+ "productId": "gid://shopify/Product/7916899991603",
+ "handle": "majilite-sandwash-cream-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600099-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:16.602Z"
+ },
+ "DWMJ-600100": {
+ "productId": "gid://shopify/Product/7916900122675",
+ "handle": "majilite-metallic-raindrop-fawn",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600101": {
+ "productId": "gid://shopify/Product/7916900188211",
+ "handle": "majilite-shimmer-wheat",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600101-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:19.118Z"
+ },
+ "DWMJ-600103": {
+ "productId": "gid://shopify/Product/7916900286515",
+ "handle": "majilite-finesse-pearl-eggshell",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600103-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:22.156Z"
+ },
+ "DWMJ-600104": {
+ "productId": "gid://shopify/Product/7916900384819",
+ "handle": "majilite-eclipse-fx-wheat",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600105": {
+ "productId": "gid://shopify/Product/7916900450355",
+ "handle": "majilite-legacy-beige",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600105-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:24.797Z"
+ },
+ "DWMJ-600106": {
+ "productId": "gid://shopify/Product/7916900548659",
+ "handle": "majilite-legacy-natural",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600107": {
+ "productId": "gid://shopify/Product/7916900614195",
+ "handle": "majilite-pony-pearl-buff",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600107-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:27.659Z"
+ },
+ "DWMJ-600108": {
+ "productId": "gid://shopify/Product/7916900679731",
+ "handle": "majilite-apollo-chardonnay",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600109": {
+ "productId": "gid://shopify/Product/7916900745267",
+ "handle": "majilite-vibes-eggshell-gold-stardust",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600109-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:30.236Z"
+ },
+ "DWMJ-600110": {
+ "productId": "gid://shopify/Product/7916900810803",
+ "handle": "majilite-drizzle-buff",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600111": {
+ "productId": "gid://shopify/Product/7916900876339",
+ "handle": "majilite-hive-antique-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600111-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:32.847Z"
+ },
+ "DWMJ-600112": {
+ "productId": "gid://shopify/Product/7916900941875",
+ "handle": "majilite-orion-gold-coin",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600113": {
+ "productId": "gid://shopify/Product/7916901040179",
+ "handle": "majilite-chinchilla-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600113-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:35.534Z"
+ },
+ "DWMJ-600114": {
+ "productId": "gid://shopify/Product/7916901105715",
+ "handle": "majilite-finesse-gold",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600115": {
+ "productId": "gid://shopify/Product/7916901171251",
+ "handle": "majilite-legacy-sand",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600115-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:38.426Z"
+ },
+ "DWMJ-600116": {
+ "productId": "gid://shopify/Product/7916901204019",
+ "handle": "majilite-nuance-sand",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600117": {
+ "productId": "gid://shopify/Product/7916901302323",
+ "handle": "majilite-eclipse-hx-mystic-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600117-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:41.145Z"
+ },
+ "DWMJ-600118": {
+ "productId": "gid://shopify/Product/7916901367859",
+ "handle": "majilite-legacy-tan",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600119": {
+ "productId": "gid://shopify/Product/7916901498931",
+ "handle": "majilite-echo-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600119-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:44.268Z"
+ },
+ "DWMJ-600120": {
+ "productId": "gid://shopify/Product/7916901564467",
+ "handle": "majilite-fortuna-white-gold",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600121": {
+ "productId": "gid://shopify/Product/7916901630003",
+ "handle": "majilite-nuance-petal",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600121-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:46.965Z"
+ },
+ "DWMJ-600122": {
+ "productId": "gid://shopify/Product/7916901761075",
+ "handle": "majilite-palisades-pearl-eggshell",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600123": {
+ "productId": "gid://shopify/Product/7916901826611",
+ "handle": "majilite-stature-rose-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600123-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:49.712Z"
+ },
+ "DWMJ-600124": {
+ "productId": "gid://shopify/Product/7916901892147",
+ "handle": "majilite-chinchilla-rose-gold",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600125": {
+ "productId": "gid://shopify/Product/7916901957683",
+ "handle": "majilite-bijoux-pink",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600125-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:52.390Z"
+ },
+ "DWMJ-600126": {
+ "productId": "gid://shopify/Product/7916902023219",
+ "handle": "majilite-finesse-pearl-dark-powder",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600127": {
+ "productId": "gid://shopify/Product/7916902088755",
+ "handle": "majilite-chinchilla-blossom",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600127-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:54.948Z"
+ },
+ "DWMJ-600128": {
+ "productId": "gid://shopify/Product/7916902154291",
+ "handle": "majilite-finesse-metallic-rose",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600129": {
+ "productId": "gid://shopify/Product/7916902252595",
+ "handle": "majilite-infinity-white-zinfandel",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600129-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:17:57.719Z"
+ },
+ "DWMJ-600130": {
+ "productId": "gid://shopify/Product/7916902318131",
+ "handle": "majilite-brushed-finesse-champagne",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600131": {
+ "productId": "gid://shopify/Product/7916902383667",
+ "handle": "majilite-chinchilla-wheat",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600131-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:00.632Z"
+ },
+ "DWMJ-600132": {
+ "productId": "gid://shopify/Product/7916902449203",
+ "handle": "majilite-infinity-sand-icon-silver-glitter",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600133": {
+ "productId": "gid://shopify/Product/7916902514739",
+ "handle": "majilite-capricorn-gold-leaf",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600133-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:03.312Z"
+ },
+ "DWMJ-600134": {
+ "productId": "gid://shopify/Product/7916902580275",
+ "handle": "majilite-presto-fawn",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600135": {
+ "productId": "gid://shopify/Product/7916902613043",
+ "handle": "majilite-infinity-sand",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600135-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:05.985Z"
+ },
+ "DWMJ-600136": {
+ "productId": "gid://shopify/Product/7916902678579",
+ "handle": "majilite-heirloom-soft-gold",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600137": {
+ "productId": "gid://shopify/Product/7916902744115",
+ "handle": "majilite-metallic-paragon-ivory",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600137-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:08.841Z"
+ },
+ "DWMJ-600138": {
+ "productId": "gid://shopify/Product/7916902809651",
+ "handle": "majilite-burnished-metal-penny",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600139": {
+ "productId": "gid://shopify/Product/7916902842419",
+ "handle": "majilite-legacy-cameo",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600139-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:11.413Z"
+ },
+ "DWMJ-600140": {
+ "productId": "gid://shopify/Product/7916902907955",
+ "handle": "majilite-shimmer-mist-legend",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600141": {
+ "productId": "gid://shopify/Product/7916902973491",
+ "handle": "majilite-echo-white-silver",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600141-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:14.392Z"
+ },
+ "DWMJ-600142": {
+ "productId": "gid://shopify/Product/7916903039027",
+ "handle": "majilite-eclipse-fx-diamond",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600143": {
+ "productId": "gid://shopify/Product/7916903137331",
+ "handle": "majilite-palisades-metallic-taupe",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600143-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:17.293Z"
+ },
+ "DWMJ-600144": {
+ "productId": "gid://shopify/Product/7916903202867",
+ "handle": "majilite-infinity-mist",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600145": {
+ "productId": "gid://shopify/Product/7916903268403",
+ "handle": "majilite-distinction-grey",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600145-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:19.897Z"
+ },
+ "DWMJ-600146": {
+ "productId": "gid://shopify/Product/7916903333939",
+ "handle": "majilite-nuance-grey",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600147": {
+ "productId": "gid://shopify/Product/7916903432243",
+ "handle": "majilite-burnished-metal-mercury",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600147-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:22.589Z"
+ },
+ "DWMJ-600148": {
+ "productId": "gid://shopify/Product/7916903497779",
+ "handle": "majilite-nuance-pewter",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600149": {
+ "productId": "gid://shopify/Product/7916903563315",
+ "handle": "majilite-charm-metallic-taupe",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600149-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:25.167Z"
+ },
+ "DWMJ-600150": {
+ "productId": "gid://shopify/Product/7916903661619",
+ "handle": "majilite-shimmer-slate-1",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600150-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:27.316Z"
+ },
+ "DWMJ-600151": {
+ "productId": "gid://shopify/Product/7916903759923",
+ "handle": "majilite-finesse-pearl-platinum-1",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600151-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:29.368Z"
+ },
+ "DWMJ-600152": {
+ "productId": "gid://shopify/Product/7916903858227",
+ "handle": "majilite-charm-antique-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600152-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:31.848Z"
+ },
+ "DWMJ-600153": {
+ "productId": "gid://shopify/Product/7916903956531",
+ "handle": "majilite-eclipse-fx-metallic-taupe",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600153-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:34.012Z"
+ },
+ "DWMJ-600154": {
+ "productId": "gid://shopify/Product/7916904087603",
+ "handle": "majilite-infinity-grey",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600154-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:36.142Z"
+ },
+ "DWMJ-600155": {
+ "productId": "gid://shopify/Product/7916904218675",
+ "handle": "majilite-metallic-paragon-slate",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600155-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:38.240Z"
+ },
+ "DWMJ-600156": {
+ "productId": "gid://shopify/Product/7916904316979",
+ "handle": "majilite-attache-rose-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600156-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:40.404Z"
+ },
+ "DWMJ-600157": {
+ "productId": "gid://shopify/Product/7916904382515",
+ "handle": "majilite-shimmer-pewter",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600158": {
+ "productId": "gid://shopify/Product/7916904415283",
+ "handle": "majilite-stature-gold",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600158-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:43.284Z"
+ },
+ "DWMJ-600159": {
+ "productId": "gid://shopify/Product/7916904513587",
+ "handle": "majilite-finesse-pearl-pewter",
+ "status": "ACTIVE",
+ "note": "pre-existing"
+ },
+ "DWMJ-600160": {
+ "productId": "gid://shopify/Product/7916904611891",
+ "handle": "majilite-hammered-metal-midnight",
+ "status": "ACTIVE",
+ "sample_sku": "DWMJ-600160-Sample",
+ "image": "ok",
+ "at": "2026-08-10T17:18:46.291Z"
+ }
+}
\ No newline at end of file
diff --git a/scripts/probe7.js b/scripts/probe7.js
new file mode 100644
index 0000000..ab65314
--- /dev/null
+++ b/scripts/probe7.js
@@ -0,0 +1,17 @@
+const fs=require('fs'),path=require('path');
+const STORE='designer-laboratory-sandbox.myshopify.com';
+const TOKEN=(fs.readFileSync(path.join(process.env.HOME,'Projects/secrets-manager/.env'),'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1].replace(/["']/g,'').trim();
+const GQL=`https://${STORE}/admin/api/2024-10/graphql.json`;
+const skus=['DWMJ-600042','DWMJ-600048','DWMJ-600062','DWMJ-600065','DWMJ-600088','DWMJ-600090','DWMJ-600102'];
+async function gql(q,v){const r=await fetch(GQL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});return r.json();}
+(async()=>{
+ for(const base of skus){
+ const sku=base+'-Sample';
+ const d=await gql(`query($q:String!){ productVariants(first:5, query:$q){ nodes{ sku product{ id handle vendor status featuredMedia{id} } } } }`,{q:`sku:${sku}`});
+ if(d.errors){console.log(base,'ERR',JSON.stringify(d.errors).slice(0,90));continue;}
+ const n=d.data.productVariants.nodes;
+ if(!n.length){console.log(base,'→ NO variant with that sku (product at handle is broken/foreign)');}
+ else n.forEach(x=>console.log(base,'→ FOUND', x.product.handle, x.product.vendor, x.product.status,'img:'+(x.product.featuredMedia?'y':'n'), x.product.id));
+ await new Promise(r=>setTimeout(r,150));
+ }
+})();
diff --git a/scripts/publish_shopify.js b/scripts/publish_shopify.js
new file mode 100644
index 0000000..015a861
--- /dev/null
+++ b/scripts/publish_shopify.js
@@ -0,0 +1,153 @@
+#!/usr/bin/env node
+/**
+ * Publish Majilite Metallic Specialties I as $4.25 SAMPLE products on the live
+ * DW Shopify store (designer-laboratory-sandbox = production). Mirrors the DW
+ * house structure: one "Sample" variant @ $4.25, dwc + custom spec metafields,
+ * display_variant tag, local swatch image via staged upload.
+ *
+ * Idempotent: skips a SKU whose "<dw_sku>-Sample" variant already exists.
+ * PG-first-then-Shopify: majilite_catalog is already staged; this creates the
+ * authoritative Shopify product and records the id back to data/published.json.
+ *
+ * Usage:
+ * node scripts/publish_shopify.js --canary 1 # first N only
+ * node scripts/publish_shopify.js --from 2 --to 160 # range
+ * node scripts/publish_shopify.js --all # everything (skips done)
+ * node scripts/publish_shopify.js --status draft # default: active
+ * node scripts/publish_shopify.js --dry # no writes, plan only
+ */
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const API = '2024-10';
+const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || (() => {
+ const env = fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8');
+ const m = env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m); return m ? m[1].replace(/["']/g, '').trim() : '';
+})();
+const GQL = `https://${STORE}/admin/api/${API}/graphql.json`;
+
+const args = process.argv.slice(2);
+const opt = (k, d) => { const i = args.indexOf('--' + k); return i >= 0 ? (args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : true) : d; };
+const DRY = !!opt('dry', false);
+const STATUS = (opt('status', 'active') === 'draft') ? 'DRAFT' : 'ACTIVE';
+const products = JSON.parse(fs.readFileSync(path.join(ROOT, 'data/products.json'), 'utf8'));
+let range;
+if (opt('canary')) range = products.slice(0, Number(opt('canary')));
+else if (opt('all')) range = products;
+else { const f = Number(opt('from', 1)), t = Number(opt('to', products.length)); range = products.filter(p => p.seq >= f && p.seq <= t); }
+
+const PUB_PATH = path.join(ROOT, 'data/published.json');
+const published = fs.existsSync(PUB_PATH) ? JSON.parse(fs.readFileSync(PUB_PATH, 'utf8')) : {};
+const savePub = () => fs.writeFileSync(PUB_PATH, JSON.stringify(published, null, 2));
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+async function gql(query, variables) {
+ for (let attempt = 0; attempt < 5; attempt++) {
+ const res = await fetch(GQL, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }) });
+ const j = await res.json();
+ if (j.errors) {
+ const throttled = JSON.stringify(j.errors).includes('THROTTLED');
+ if (throttled) { await sleep(1500 * (attempt + 1)); continue; }
+ throw new Error('GQL errors: ' + JSON.stringify(j.errors).slice(0, 300));
+ }
+ return j.data;
+ }
+ throw new Error('GQL throttled after retries');
+}
+
+const slugify = s => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
+
+async function existingVariant(sku) {
+ const d = await gql(`query($q:String!){ productVariants(first:1, query:$q){ nodes{ id product{ id handle status } } } }`, { q: `sku:${sku}` });
+ return d.productVariants.nodes[0] || null;
+}
+
+async function uploadImage(filePath, filename) {
+ const d = await gql(`mutation($input:[StagedUploadInput!]!){ stagedUploadsCreate(input:$input){ stagedTargets{ url resourceUrl parameters{ name value } } userErrors{ field message } } }`,
+ { input: [{ filename, mimeType: 'image/png', resource: 'IMAGE', httpMethod: 'POST' }] });
+ const err = d.stagedUploadsCreate.userErrors; if (err && err.length) throw new Error('stagedUpload: ' + JSON.stringify(err));
+ const t = d.stagedUploadsCreate.stagedTargets[0];
+ const form = new FormData();
+ for (const p of t.parameters) form.append(p.name, p.value);
+ form.append('file', new Blob([fs.readFileSync(filePath)], { type: 'image/png' }), filename);
+ const up = await fetch(t.url, { method: 'POST', body: form });
+ if (!(up.status >= 200 && up.status < 300)) throw new Error('staged POST ' + up.status);
+ return t.resourceUrl;
+}
+
+function metafields(p) {
+ const S = (ns, key, value, type = 'single_line_text_field') => ({ namespace: ns, key, type, value: String(value) });
+ // Only keys proven to exist as single_line_text_field on this store (from a live
+ // DWMA product). Full spec (weight/thickness/durability/FR) lives in descriptionHtml
+ // to avoid colliding with typed metafield definitions (e.g. custom.weight = weight type).
+ return [
+ S('dwc', 'brand', p.vendor), S('dwc', 'pattern_name', p.pattern), S('dwc', 'color', p.color),
+ S('dwc', 'collection', p.collection), S('dwc', 'manufacturer_sku', p.mfr_sku), S('dwc', 'width', p.spec.width),
+ S('custom', 'brand', p.vendor), S('custom', 'pattern_name', p.pattern), S('custom', 'color', p.color),
+ S('custom', 'collection_name', p.collection), S('custom', 'manufacturer_sku', p.mfr_sku),
+ S('custom', 'width', p.spec.width), S('custom', 'color_hex', p.color_hex),
+ S('custom', 'product_class', 'Wallcovering'), S('custom', 'material_category', 'Faux Leather (Nytek)'),
+ ];
+}
+
+function bodyHtml(p) {
+ const s = p.spec;
+ return `<p><strong>${p.pattern} — ${p.color}</strong>. ${p.vendor} ${p.brand}® faux leather from the ${p.collection} collection — a specially engineered Nylon Fiber Matrix, luxurious, breathable and durable, ideal for wall coverings, seating, flat surfaces and displays.</p>`
+ + `<ul><li>Width: ${s.width}</li><li>Weight: ${s.weight}</li><li>Thickness: ${s.thickness}</li>`
+ + `<li>Durability: ${s.durability}</li><li>Flammability: ${s.flammability_inherent}</li>`
+ + `<li>Cleanability: ${s.cleanability_code} · Crocking ${s.crocking}</li>`
+ + `<li>PFAS-, PVC- and plasticizer-free · no off-gassing</li></ul>`
+ + `<p><em>Sample swatch. Sold by the yard (54" wide) — contact us for yardage pricing.</em></p>`;
+}
+
+async function publishOne(p) {
+ const sampleSku = `${p.dw_sku}-Sample`;
+ if (published[p.dw_sku] && published[p.dw_sku].productId) return { skip: 'recorded' };
+ const exists = await existingVariant(sampleSku);
+ if (exists) { published[p.dw_sku] = { productId: exists.product.id, handle: exists.product.handle, status: exists.product.status, note: 'pre-existing' }; savePub(); return { skip: 'exists' }; }
+ if (DRY) return { dry: true, title: p.title, sku: sampleSku };
+
+ const handle = `majilite-${slugify(p.pattern + '-' + p.color)}`;
+ const tags = Array.from(new Set([...p.tags, p.dw_sku, 'display_variant', 'Sample'])).join(', ');
+
+ const input = {
+ handle, title: p.title, vendor: p.vendor, productType: p.product_type, status: STATUS,
+ descriptionHtml: bodyHtml(p), tags: tags.split(', '),
+ productOptions: [{ name: 'Type', values: [{ name: 'Sample' }] }],
+ variants: [{ optionValues: [{ optionName: 'Type', name: 'Sample' }], price: '4.25', sku: sampleSku, inventoryPolicy: 'CONTINUE', taxable: true }],
+ metafields: metafields(p),
+ };
+ const d = await gql(`mutation($input:ProductSetInput!){ productSet(synchronous:true, input:$input){ product{ id handle status } userErrors{ field message } } }`, { input });
+ const ue = d.productSet.userErrors; if (ue && ue.length) throw new Error('productSet: ' + JSON.stringify(ue));
+ const prod = d.productSet.product;
+
+ // attach swatch image
+ let media = 'none';
+ try {
+ const resourceUrl = await uploadImage(path.join(ROOT, 'public', p.image), `${p.dw_sku}.png`);
+ const m = await gql(`mutation($id:ID!,$media:[CreateMediaInput!]!){ productCreateMedia(productId:$id, media:$media){ media{ status } mediaUserErrors{ field message } } }`,
+ { id: prod.id, media: [{ originalSource: resourceUrl, alt: `${p.title} — ${p.vendor} ${p.brand}`, mediaContentType: 'IMAGE' }] });
+ const me = m.productCreateMedia.mediaUserErrors; media = (me && me.length) ? 'err:' + JSON.stringify(me) : 'ok';
+ } catch (e) { media = 'img-fail:' + e.message.slice(0, 80); }
+
+ published[p.dw_sku] = { productId: prod.id, handle: prod.handle, status: prod.status, sample_sku: sampleSku, image: media, at: new Date().toISOString() };
+ savePub();
+ return { created: prod.id, handle: prod.handle, image: media };
+}
+
+(async () => {
+ console.log(`Publish → ${STORE} status=${STATUS} ${DRY ? '(DRY)' : ''} targets=${range.length}`);
+ let created = 0, skipped = 0, failed = 0;
+ for (const p of range) {
+ try {
+ const r = await publishOne(p);
+ if (r.skip) { skipped++; process.stdout.write(`· ${p.dw_sku} skip(${r.skip})\n`); }
+ else if (r.dry) { process.stdout.write(`plan ${p.dw_sku} ${p.title} → ${r.sku}\n`); }
+ else { created++; process.stdout.write(`✓ ${p.dw_sku} ${p.title} → ${r.handle} [img:${r.image}] (${created})\n`); }
+ } catch (e) { failed++; process.stdout.write(`✗ ${p.dw_sku} ${p.title} — ${e.message.slice(0, 160)}\n`); }
+ await sleep(350);
+ }
+ console.log(`\nDone. created=${created} skipped=${skipped} failed=${failed} | published.json has ${Object.keys(published).length} records`);
+})();
diff --git a/scripts/reconcile.js b/scripts/reconcile.js
new file mode 100644
index 0000000..f33b8bb
--- /dev/null
+++ b/scripts/reconcile.js
@@ -0,0 +1,42 @@
+#!/usr/bin/env node
+/**
+ * Reconcile the live store against products.json using productByHandle (strongly
+ * consistent, unlike variant-sku search which lags). For each of the 160:
+ * - already recorded in published.json -> OK
+ * - handle resolves to OUR product (vendor Majilite, DWMJ sample sku) -> record it
+ * - handle resolves to a DIFFERENT product -> report collision (needs unique handle)
+ * - handle missing -> report as genuinely absent (to be (re)published)
+ * Read-only: only records confirmed-ours into published.json; never creates/deletes.
+ */
+const fs = require('fs'); const path = require('path');
+const ROOT = path.resolve(__dirname, '..');
+const API = '2024-10'; const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = (fs.readFileSync(path.join(process.env.HOME,'Projects/secrets-manager/.env'),'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1].replace(/["']/g,'').trim();
+const GQL = `https://${STORE}/admin/api/${API}/graphql.json`;
+const products = JSON.parse(fs.readFileSync(path.join(ROOT,'data/products.json'),'utf8'));
+const PUB = path.join(ROOT,'data/published.json');
+const pub = fs.existsSync(PUB) ? JSON.parse(fs.readFileSync(PUB,'utf8')) : {};
+const slug = s => s.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+async function gql(q,v){ for(let a=0;a<5;a++){ const r=await fetch(GQL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})}); const j=await r.json(); if(j.errors){ if(JSON.stringify(j.errors).includes('THROTTLED')){await sleep(1500*(a+1));continue;} throw new Error(JSON.stringify(j.errors).slice(0,200)); } return j.data; } throw new Error('throttled'); }
+
+(async()=>{
+ let ok=0, recorded=0, collided=[], missing=[], noimg=[];
+ for(const p of products){
+ if(pub[p.dw_sku] && pub[p.dw_sku].productId){ ok++; continue; }
+ const handle = `majilite-${slug(p.pattern+'-'+p.color)}`;
+ const d = await gql(`query($h:String!){ productByHandle(handle:$h){ id handle status vendor variants(first:1){nodes{sku price}} featuredMedia{id} } }`, {h:handle});
+ const prod = d.productByHandle;
+ if(!prod){ missing.push(p.dw_sku); continue; }
+ const v = prod.variants.nodes[0]||{};
+ const ours = prod.vendor==='Majilite' && v.sku===`${p.dw_sku}-Sample`;
+ if(ours){ pub[p.dw_sku]={productId:prod.id,handle:prod.handle,status:prod.status,sample_sku:v.sku,image:prod.featuredMedia?'ok':'none',reconciled:true}; recorded++; if(!prod.featuredMedia) noimg.push(p.dw_sku); }
+ else collided.push({sku:p.dw_sku, handle, owner_vendor:prod.vendor, owner_variant:v.sku});
+ await sleep(120);
+ }
+ fs.writeFileSync(PUB, JSON.stringify(pub,null,2));
+ console.log(`already_ok=${ok} newly_recorded=${recorded} total_published=${Object.keys(pub).length}`);
+ console.log(`genuinely_missing=${missing.length}${missing.length?': '+missing.join(','):''}`);
+ console.log(`collisions_with_other_products=${collided.length}`); if(collided.length) console.log(JSON.stringify(collided,null,1));
+ console.log(`recorded_without_image=${noimg.length}${noimg.length?': '+noimg.join(','):''}`);
+})();
← 63a1a27 Renumber to DWMJ-600000 block; onboard all 160 as sellable $
·
back to Majilite Onboard
·
auto-data-snapshot: 2026-08-10T10:27:30 (1 data files) — tas bfc60b4 →