← back to Designer Wallcoverings

mailers/assets/upload-logos.js

68 lines

// Upload /tmp/cs-logos assets to Shopify Files (stagedUploadsCreate -> POST -> fileCreate -> poll READY)
const fs = require('fs');
const path = require('path');
const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
const API = '2024-10';
const TOKEN = process.env.SHOPIFY_DRAFT_TOKEN;
if (!TOKEN) { console.error('Missing SHOPIFY_DRAFT_TOKEN'); process.exit(1); }
const DIR = '/tmp/cs-logos';
const FILES = [
  { file: 'china-seas-clean.png', mime: 'image/png' },
  { file: 'lee-jofa.png',         mime: 'image/png' },
  { file: 'brunschwig-fils.png',  mime: 'image/png' },
  { file: 'gp-j-baker.png',       mime: 'image/png' },
];
async function gql(query, variables) {
  const res = await fetch(`https://${DOMAIN}/admin/api/${API}/graphql.json`, {
    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) throw new Error('GraphQL errors: ' + JSON.stringify(j.errors));
  return j.data;
}
async function stagedUpload(filename, mime, sizeBytes) {
  const q = `mutation s($input:[StagedUploadInput!]!){stagedUploadsCreate(input:$input){stagedTargets{url resourceUrl parameters{name value}} userErrors{field message}}}`;
  const d = await gql(q, { input: [{ resource: 'FILE', filename, mimeType: mime, httpMethod: 'POST', fileSize: String(sizeBytes) }]});
  const ue = d.stagedUploadsCreate.userErrors; if (ue && ue.length) throw new Error(JSON.stringify(ue));
  return d.stagedUploadsCreate.stagedTargets[0];
}
async function putBytes(target, buf, mime) {
  const fd = new FormData();
  for (const p of target.parameters) fd.append(p.name, p.value);
  fd.append('file', new Blob([buf], { type: mime }));
  const res = await fetch(target.url, { method: 'POST', body: fd });
  if (!(res.status >= 200 && res.status < 300)) throw new Error(`PUT ${res.status}: ${(await res.text()).slice(0,200)}`);
}
async function fileCreate(resourceUrl, filename, alt) {
  const q = `mutation f($files:[FileCreateInput!]!){fileCreate(files:$files){files{id fileStatus} userErrors{field message}}}`;
  const d = await gql(q, { files: [{ originalSource: resourceUrl, contentType: 'IMAGE', alt, filename }]});
  const ue = d.fileCreate.userErrors; if (ue && ue.length) throw new Error(JSON.stringify(ue));
  return d.fileCreate.files[0];
}
async function poll(id) {
  const q = `query($id:ID!){node(id:$id){... on MediaImage{id fileStatus image{url}}}}`;
  for (let i = 0; i < 30; i++) {
    const d = await gql(q, { id }); const n = d.node;
    if (n && n.fileStatus === 'READY' && n.image && n.image.url) return n.image.url;
    if (n && n.fileStatus === 'FAILED') throw new Error('FAILED ' + id);
    await new Promise(r => setTimeout(r, 2000));
  }
  throw new Error('timeout ' + id);
}
(async () => {
  const out = {};
  for (const { file, mime } of FILES) {
    const buf = fs.readFileSync(path.join(DIR, file));
    const alt = file.replace(/\.[a-z]+$/i,'').replace(/-/g,' ') + ' logo';
    process.stdout.write(`Uploading ${file} (${buf.length}B)... `);
    const t = await stagedUpload(file, mime, buf.length);
    await putBytes(t, buf, mime);
    const c = await fileCreate(t.resourceUrl, file, alt);
    out[file] = await poll(c.id);
    console.log('READY');
  }
  console.log('\nRESULT_JSON ' + JSON.stringify(out, null, 2));
})().catch(e => { console.error('FATAL', e.message); process.exit(1); });