type GraphqlResponse = { data?: T; errors?: unknown; }; type UserError = { field?: string[]; message: string; }; const shop = requiredEnv("SHOPIFY_SHOP_DOMAIN"); const token = requiredEnv("SHOPIFY_ADMIN_ACCESS_TOKEN"); const metaobjectType = process.env.METAOBJECT_TYPE || "$app:dreame_activity"; const metaobjectHandle = process.env.METAOBJECT_HANDLE || "dreame-activity-config"; const endpoint = `https://${shop}/admin/api/2026-01/graphql.json`; const metaobjectMutation = `#graphql mutation DreameMetaobjectUpsert($handle: MetaobjectHandleInput!, $metaobject: MetaobjectUpsertInput!) { metaobjectUpsert(handle: $handle, metaobject: $metaobject) { metaobject { id handle } userErrors { field message } } } `; async function main() { const result = await graphql<{ metaobjectUpsert?: { userErrors: UserError[]; }; }>(metaobjectMutation, { handle: { type: metaobjectType, handle: metaobjectHandle, }, metaobject: { fields: [ { key: "activity_id", value: "12" }, { key: "api_app_id", value: "mock-app-id" }, { key: "api_secret", value: "mock-secret" }, { key: "status_api_url", value: "https://third-party.example/status" }, { key: "push_api_url", value: "https://third-party.example/push" }, { key: "status_success_code", value: "0" }, { key: "status_active_required", value: "true" }, { key: "push_success_code", value: "0" }, { key: "redirect_url", value: "https://activity.example.com" }, { key: "button_text", value: JSON.stringify({ en: "Claim Your Reward", "zh-CN": "领取奖励" }) }, { key: "shop", value: shop }, ], }, }); const userErrors = result.data?.metaobjectUpsert?.userErrors || []; if (userErrors.length) { throw new Error(JSON.stringify(userErrors)); } } async function graphql(query: string, variables: Record): Promise> { const response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json", "X-Shopify-Access-Token": token, }, body: JSON.stringify({ query, variables }), }); const payload = (await response.json()) as GraphqlResponse; if (!response.ok || payload.errors) { throw new Error(JSON.stringify(payload.errors || payload)); } console.log(JSON.stringify(payload, null, 2)); return payload; } function requiredEnv(name: string): string { const value = process.env[name]; if (!value) { throw new Error(`${name} is required`); } return value; } main().catch((error) => { console.error(error); process.exit(1); });