← back to Greenland Onboard
Greenland cork go-live: publish the 1 active-unpublished SKU (Montauk-Bison Gold)
664f66c8e42774aea72a0e25b8d698803b130a95 · 2026-07-15 14:21:14 -0700 · Steve
Reality check via live Shopify (snapshot was stale): cork line is ALREADY 195/196
active SKUs published. Published the 1 remaining active-unpublished (verified live,
storefront 200). publish-online.mjs guards: Online Store only, Phillipe-Romano-name /
no-Greenland / no-Wallpaper rails, and status===ACTIVE (never resurrect the 15 ARCHIVED
= discontinued SKUs). Canary correctly no-op'd on archived (no harm).
Files touched
A data/published-online.ndjsonA scripts/publish-online.mjs
Diff
commit 664f66c8e42774aea72a0e25b8d698803b130a95
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 15 14:21:14 2026 -0700
Greenland cork go-live: publish the 1 active-unpublished SKU (Montauk-Bison Gold)
Reality check via live Shopify (snapshot was stale): cork line is ALREADY 195/196
active SKUs published. Published the 1 remaining active-unpublished (verified live,
storefront 200). publish-online.mjs guards: Online Store only, Phillipe-Romano-name /
no-Greenland / no-Wallpaper rails, and status===ACTIVE (never resurrect the 15 ARCHIVED
= discontinued SKUs). Canary correctly no-op'd on archived (no harm).
---
data/published-online.ndjson | 19 +++++++++++
scripts/publish-online.mjs | 80 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 99 insertions(+)
diff --git a/data/published-online.ndjson b/data/published-online.ndjson
new file mode 100644
index 0000000..5d8a378
--- /dev/null
+++ b/data/published-online.ndjson
@@ -0,0 +1,19 @@
+{"ts":"2026-07-15T21:15:49.133Z","pid":"7880972369971","title":"Southampton-Sharkskin Cork Wallcovering | Phillipe Romano","handle":"southampton-cork-wallcovering-sharkskin","action":"published"}
+{"ts":"2026-07-15T21:15:50.102Z","pid":"7880972402739","title":"Southampton-Eden Cork Wallcovering | Phillipe Romano","handle":"southampton-cork-wallcovering-eden","action":"published"}
+{"ts":"2026-07-15T21:15:51.006Z","pid":"7880972533811","title":"Southampton-Deep Taupe Cork Wallcovering | Phillipe Romano","handle":"southampton-cork-wallcovering-deep-taupe","action":"published"}
+{"ts":"2026-07-15T21:19:41.359Z","pid":"7880972369971","title":"Southampton-Sharkskin Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:41.553Z","pid":"7880972402739","title":"Southampton-Eden Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:41.754Z","pid":"7880972533811","title":"Southampton-Deep Taupe Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:41.970Z","pid":"7880972468275","title":"Southampton-Coffee Bean Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:42.179Z","pid":"7880972435507","title":"Southampton-Argan Oil Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:42.387Z","pid":"7880972501043","title":"Southampton-Winter Bloom Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:42.588Z","pid":"7880972140595","title":"Palm Beach-White Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:42.814Z","pid":"7880972075059","title":"Palm Beach-Eiffel Tower Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:43.005Z","pid":"7880972173363","title":"Palm Beach-Amphora Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:43.219Z","pid":"7880972206131","title":"Palm Beach-Rhododendron Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:43.436Z","pid":"7880972238899","title":"Palm Beach-Picante Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:43.632Z","pid":"7880972271667","title":"Palm Beach-Taupe Gray Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:43.850Z","pid":"7880972337203","title":"Palm Beach-Java Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:44.064Z","pid":"7880972304435","title":"Palm Beach-Blue Nights Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:19:44.263Z","pid":"7880972107827","title":"Palm Beach-Turkish Tile Cork Wallcovering | Phillipe Romano","status":"ARCHIVED","action":"skip-not-active"}
+{"ts":"2026-07-15T21:20:11.024Z","pid":"7880989343795","title":"Montauk-Bison Gold Cork Wallcovering | Phillipe Romano","handle":"montauk-cork-wallcovering-bison-gold","action":"published"}
diff --git a/scripts/publish-online.mjs b/scripts/publish-online.mjs
new file mode 100644
index 0000000..ce5f185
--- /dev/null
+++ b/scripts/publish-online.mjs
@@ -0,0 +1,80 @@
+#!/usr/bin/env node
+// Publish existing (active) Greenland cork products to the ONLINE STORE channel — go-live.
+// Customer-facing. Safe by design:
+// • publishes ONLY to Online Store (never Google/other channels)
+// • per-SKU guard: title must contain "Phillipe Romano" and must NOT contain
+// "Greenland" or "Wallpaper" — else SKIP + flag (private-label + banned-word rails)
+// • skips products already published to Online Store (idempotent re-run)
+// • ledger to data/published-online.ndjson; throttled + 429 retry
+// Usage: node scripts/publish-online.mjs <limit> [startIndex] [--dry] (limit=0 → all)
+import { readFileSync, appendFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+
+const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
+const env = readFileSync(`${process.env.HOME}/Projects/secrets-manager/.env`, 'utf8');
+const pick = (k) => (env.match(new RegExp(`^${k}=(.*)$`, 'm'))?.[1] || '').replace(/^['"]|['"]$/g, '').trim();
+const STORE = pick('SHOPIFY_STORE') || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = pick('SHOPIFY_ADMIN_TOKEN');
+const API = `https://${STORE}/admin/api/2024-10/graphql.json`;
+const ONLINE_STORE = 'gid://shopify/Publication/22208643184';
+const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };
+const LIMIT = Number(process.argv[2] || 0);
+const START = Number(process.argv[3] || 0);
+const DRY = process.argv.includes('--dry');
+const LEDGER = join(ROOT, 'data', 'published-online.ndjson');
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function gql(query, variables) {
+ for (let i = 0; i < 5; i++) {
+ const r = await fetch(API, { method: 'POST', headers: H, body: JSON.stringify({ query, variables }) });
+ if (r.status === 429) { await sleep(2000 * (i + 1)); continue; }
+ const j = await r.json();
+ if (j.errors) throw new Error(JSON.stringify(j.errors).slice(0, 300));
+ return j.data;
+ }
+ throw new Error('429 exhausted');
+}
+
+const products = JSON.parse(readFileSync(join(ROOT, 'public', 'viewer.json'), 'utf8')).products
+ .filter(p => p.shopify_pid); // must have a real Shopify product id
+const slice = products.slice(START);
+// LIMIT = max REAL publishes (canary), not first-N-products (most are already live)
+console.log(`publish-online: scanning ${slice.length}${LIMIT ? `, stop after ${LIMIT} publish(es)` : ''}${DRY ? ' [DRY]' : ''} → Online Store`);
+
+let published = 0, already = 0, skipped = 0, failed = 0;
+for (const p of slice) {
+ const gid = `gid://shopify/Product/${p.shopify_pid}`;
+ const t = String(p.title || '');
+ // rails
+ if (!/phillipe romano/i.test(t) || /greenland/i.test(t) || /\bwallpaper\b/i.test(t)) {
+ console.log(` SKIP(rails) ${p.shopify_pid} :: ${t}`); skipped++;
+ appendFileSync(LEDGER, JSON.stringify({ ts: new Date().toISOString(), pid: p.shopify_pid, title: t, action: 'skip-rails' }) + '\n'); continue;
+ }
+ // live state
+ let d;
+ try {
+ d = await gql(`query($id:ID!){ product(id:$id){ id title status handle publishedOnPublication(publicationId:"${ONLINE_STORE}") } }`, { id: gid });
+ } catch (e) { console.log(` FAIL(read) ${p.shopify_pid} :: ${e.message}`); failed++; continue; }
+ const prod = d.product;
+ if (!prod) { console.log(` FAIL(missing) ${p.shopify_pid}`); failed++; continue; }
+ if (prod.status !== 'ACTIVE') { // ARCHIVED = discontinued (hard rule) — NEVER publish/resurrect
+ skipped++;
+ appendFileSync(LEDGER, JSON.stringify({ ts: new Date().toISOString(), pid: p.shopify_pid, title: prod.title, status: prod.status, action: 'skip-not-active' }) + '\n');
+ continue;
+ }
+ if (prod.publishedOnPublication) { already++; continue; } // idempotent
+ if (DRY) { console.log(` WOULD publish ${p.shopify_pid} :: ${prod.title}`); published++; continue; }
+ try {
+ const r = await gql(
+ `mutation($id:ID!,$input:[PublicationInput!]!){ publishablePublish(id:$id,input:$input){ userErrors{field message} } }`,
+ { id: gid, input: [{ publicationId: ONLINE_STORE }] });
+ const errs = r.publishablePublish.userErrors;
+ if (errs.length) { console.log(` ERR ${p.shopify_pid} :: ${JSON.stringify(errs)}`); failed++; continue; }
+ published++; console.log(` ✓ ${p.shopify_pid} :: ${prod.title}`);
+ appendFileSync(LEDGER, JSON.stringify({ ts: new Date().toISOString(), pid: p.shopify_pid, title: prod.title, handle: prod.handle, action: 'published' }) + '\n');
+ } catch (e) { console.log(` FAIL(pub) ${p.shopify_pid} :: ${e.message}`); failed++; }
+ await sleep(350);
+ if (LIMIT && published >= LIMIT) break; // canary cap on REAL publishes
+}
+console.log(`\nDONE — published:${published} already:${already} skipped(rails):${skipped} failed:${failed}`);
← 8e47620 Greenland cork disco: archive 15 Phillipe Romano cork SKUs (
·
back to Greenland Onboard
·
greenland-onboard: add Memo/Stock/Price chip actions via sha 33f8806 →