fix: fetchMetafieldDefinitions + ensureMetafieldDefinition via GraphQL (REST n'expose pas cet endpoint)

This commit is contained in:
2026-06-09 10:46:34 +02:00
parent bb596c7e32
commit 8d591bb769
+79 -24
View File
@@ -43,43 +43,98 @@ export function matchMetafields(columnHeaders: string[], definitions: MetafieldD
return { matched, unmatched }
}
/**
* Récupère toutes les définitions de métachamps produit via l'API GraphQL Admin.
* L'API REST ne dispose pas de cet endpoint — seul GraphQL l'expose.
*/
export async function fetchMetafieldDefinitions(): Promise<MetafieldDefinition[]> {
const { baseUrl, headers } = getConfig()
const res = await fetch(`${baseUrl}/metafield_definitions.json?owner_type=PRODUCT&limit=250`, { headers })
if (!res.ok) throw new Error(`Shopify metafield definitions error: ${res.status}`)
const domain = process.env.SHOPIFY_STORE_DOMAIN
const token = process.env.SHOPIFY_ADMIN_API_TOKEN
if (!domain || !token) throw new Error('SHOPIFY_STORE_DOMAIN et SHOPIFY_ADMIN_API_TOKEN requis')
const query = `{
metafieldDefinitions(ownerType: PRODUCT, first: 250) {
edges {
node {
id
namespace
key
name
type { name }
}
}
}
}`
const res = await fetch(`https://${domain}/admin/api/2024-01/graphql.json`, {
method: 'POST',
headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
})
if (!res.ok) throw new Error(`Shopify GraphQL error: ${res.status}`)
const data = await res.json()
return (data.metafield_definitions ?? []).map((d: {
id: string | number; namespace: string; key: string; name: string; type: { name: string } | string
if (data.errors) {
// Si les définitions ne sont pas accessibles, on retourne une liste vide
// plutôt que de bloquer — l'utilisateur verra toutes les colonnes comme "non matchées"
console.warn('[shopifyMetafields] GraphQL errors:', data.errors)
return []
}
return (data.data?.metafieldDefinitions?.edges ?? []).map((edge: {
node: { id: string; namespace: string; key: string; name: string; type: { name: string } }
}) => ({
id: String(d.id),
namespace: d.namespace,
key: d.key,
name: d.name,
type: typeof d.type === 'string' ? d.type : d.type?.name ?? 'single_line_text_field',
id: edge.node.id,
namespace: edge.node.namespace,
key: edge.node.key,
name: edge.node.name,
type: edge.node.type?.name ?? 'single_line_text_field',
}))
}
/**
* Crée une définition de métachamp produit via GraphQL si elle n'existe pas encore.
*/
export async function ensureMetafieldDefinition(resolution: MetafieldResolution): Promise<void> {
if (resolution.action !== 'create') return
const { baseUrl, headers } = getConfig()
const res = await fetch(`${baseUrl}/metafield_definitions.json`, {
const domain = process.env.SHOPIFY_STORE_DOMAIN
const token = process.env.SHOPIFY_ADMIN_API_TOKEN
if (!domain || !token) throw new Error('SHOPIFY_STORE_DOMAIN et SHOPIFY_ADMIN_API_TOKEN requis')
const mutation = `mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
metafieldDefinitionCreate(definition: $definition) {
createdDefinition { id key namespace }
userErrors { field message }
}
}`
const res = await fetch(`https://${domain}/admin/api/2024-01/graphql.json`, {
method: 'POST',
headers,
headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' },
body: JSON.stringify({
metafield_definition: {
namespace: resolution.namespace,
key: resolution.key,
name: resolution.columnHeader,
type: 'single_line_text_field',
owner_type: 'PRODUCT',
query: mutation,
variables: {
definition: {
namespace: resolution.namespace,
key: resolution.key,
name: resolution.columnHeader,
type: 'single_line_text_field',
ownerType: 'PRODUCT',
},
},
}),
})
if (!res.ok) {
const err = await res.text()
if (!err.includes('taken') && !err.includes('already')) {
throw new Error(`Shopify create metafield definition error: ${res.status}${err}`)
}
if (!res.ok) throw new Error(`Shopify GraphQL metafield create error: ${res.status}`)
const data = await res.json()
const errors = data.data?.metafieldDefinitionCreate?.userErrors ?? []
const isAlreadyTaken = errors.some((e: { message: string }) =>
e.message.toLowerCase().includes('taken') || e.message.toLowerCase().includes('already'),
)
if (errors.length > 0 && !isAlreadyTaken) {
throw new Error(`Shopify metafield definition error: ${errors.map((e: { message: string }) => e.message).join(', ')}`)
}
}