← 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); });