← back to Newmor Onboard

scripts/shopify.mjs

38 lines

import fs from 'node:fs';
export const ROOT = '/Users/macstudio3/Projects/newmor-onboard';
export const SHOP = 'designer-laboratory-sandbox.myshopify.com';
export const API = '2026-07';
const env = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
const token = env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)?.[1].trim().replace(/^["']|["']$/g, '');
if (!token) throw new Error('Missing Shopify credential');
const themeToken = env.match(/^SHOPIFY_THEME_TOKEN=(.+)$/m)?.[1].trim().replace(/^["']|["']$/g, '');
export async function request(path, method = 'GET', body) {
  const r = await fetch(`https://${SHOP}/admin/api/${API}/${path}`, {
    method, headers: { 'X-Shopify-Access-Token': path.startsWith('themes') ? (themeToken || token) : token, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(60000),
  });
  const j = await r.json();
  if (!r.ok || j.errors) throw new Error(`${method} ${path}: ${r.status} ${JSON.stringify(j.errors || j)}`);
  return j;
}
export async function gql(query, variables = {}) {
  return (await request('graphql.json', 'POST', { query, variables })).data;
}
export const fields = `id handle title vendor status createdAt updatedAt onlineStoreUrl onlineStorePreviewUrl templateSuffix tags descriptionHtml
  showroom:metafield(namespace:"custom",key:"showroom_line"){id type value}
  mf:metafields(first:100){nodes{id namespace key type value} pageInfo{hasNextPage}}
  variants(first:100){nodes{id title sku price availableForSale inventoryPolicy inventoryItem{id tracked}} pageInfo{hasNextPage}}
  images(first:100){nodes{id url altText} pageInfo{hasNextPage}}
  resourcePublications(first:50){nodes{isPublished publication{id name}} pageInfo{hasNextPage}}
  configuredPublications:resourcePublicationsV2(first:50,onlyPublished:false){nodes{isPublished publishDate publication{id name}}pageInfo{hasNextPage}}`;
export function complete(p) {
  for (const k of ['mf','variants','images','resourcePublications','configuredPublications']) if (p[k].pageInfo.hasNextPage) throw new Error(`Incomplete ${p.id} ${k}`);
  return p;
}
export async function product(id) { return complete((await gql(`query($id:ID!){product(id:$id){${fields}}}`,{id})).product); }
export const read = p => JSON.parse(fs.readFileSync(`${ROOT}/${p}`, 'utf8'));
export function save(p, value) {
  fs.mkdirSync(`${ROOT}/${p.substring(0,p.lastIndexOf('/'))}`, {recursive:true});
  fs.writeFileSync(`${ROOT}/${p}`, JSON.stringify(value,(key,v)=>key==='onlineStorePreviewUrl'?undefined:v,2)+'\n');
}