← back to Rebel Walls Push
scripts/_shop.js
68 lines
'use strict';
/* Shared Shopify GraphQL helper for Rebel Walls activation + push.
* Reusable module: gql(), getToken(), constants, pg(). */
const https = require('https');
const { execFileSync } = require('child_process');
const fs = require('fs');
const SECRETS_ENV = '/Users/macstudio3/Projects/secrets-manager/.env';
const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
const API_VERSION = '2024-10';
const PG = { host: '127.0.0.1', user: 'dw_admin', db: 'dw_unified', pass: process.env.PG_DW_ADMIN_PASSWORD || process.env.DW_ADMIN_DB_PASSWORD || '' };
function getToken() {
const env = fs.readFileSync(SECRETS_ENV, 'utf8');
const m = env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m);
if (!m) throw new Error('SHOPIFY_ADMIN_TOKEN not found in secrets .env');
return m[1].trim();
}
const TOKEN = getToken();
function gql(query, variables) {
const body = JSON.stringify({ query, variables: variables || {} });
return new Promise((resolve, reject) => {
const req = https.request({
hostname: DOMAIN, path: `/admin/api/${API_VERSION}/graphql.json`, method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': TOKEN,
'Content-Length': Buffer.byteLength(body) },
}, res => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try { resolve({ status: res.statusCode, headers: res.headers, json: JSON.parse(data) }); }
catch (e) { reject(new Error(`bad JSON (${res.statusCode}): ${data.slice(0,300)}`)); }
});
});
req.on('error', reject);
req.write(body); req.end();
});
}
// Retry wrapper handling 429 / Throttled / transient 5xx with backoff.
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function gqlRetry(query, variables, label) {
for (let attempt = 1; attempt <= 6; attempt++) {
let r;
try { r = await gql(query, variables); }
catch (e) { if (attempt === 6) throw e; await sleep(1500 * attempt); continue; }
const throttled = r.status === 429 ||
(r.json && r.json.errors && JSON.stringify(r.json.errors).includes('Throttled'));
if (throttled) { await sleep(2500 * attempt); continue; }
if (r.status >= 500) { if (attempt === 6) throw new Error(`${label||''} HTTP ${r.status}`); await sleep(1500 * attempt); continue; }
// cost-aware: if bucket low, breathe
const ext = r.json && r.json.extensions && r.json.extensions.cost && r.json.extensions.cost.throttleStatus;
if (ext && ext.currentlyAvailable < 300) await sleep(1500);
return r;
}
throw new Error(`${label||''} exhausted retries`);
}
function psql(sql) {
const out = execFileSync('psql', [
'-h', PG.host, '-U', PG.user, '-d', PG.db, '-At', '-F', '\t', '-c', sql,
], { env: { ...process.env, PGPASSWORD: PG.pass }, maxBuffer: 1 << 28 });
return out.toString();
}
module.exports = { gql, gqlRetry, psql, sleep, TOKEN, DOMAIN, API_VERSION, PG };