Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2670727cfa | ||
|
|
3147cafaff | ||
|
|
cb5a08e7cc | ||
|
|
c0f7c8ef90 | ||
|
|
78432cd479 | ||
|
|
9a8e5d2b04 | ||
|
|
1bcb6911db | ||
|
|
04a0abb155 | ||
|
|
2693d05d07 | ||
|
|
dd50489718 | ||
|
|
3577a70f78 | ||
|
|
3b0be2bbdd | ||
|
|
014b4f88a3 | ||
|
|
3fefd44c49 |
No files matched your search
@@ -122,11 +122,12 @@ jobs:
|
||||
needs: gitleaks
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 30
|
||||
|
||||
env:
|
||||
APP_DIR: workloads/ecommerce
|
||||
MANIFEST_FILE: workloads/ecommerce/frontend/patches/deployment.yaml
|
||||
KUSTOMIZE_DIR: workloads/ecommerce/frontend
|
||||
IMAGE_NAME: gitea.cruzcloud.net/devops/ecommerce-frontend
|
||||
|
||||
steps:
|
||||
@@ -276,6 +277,32 @@ jobs:
|
||||
chmod +x /tmp/trivy
|
||||
/tmp/trivy version
|
||||
|
||||
- name: Instalar kubectl
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KUBECTL_VERSION="v1.31.2"
|
||||
curl -sSfL \
|
||||
"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" \
|
||||
-o /tmp/kubectl
|
||||
chmod +x /tmp/kubectl
|
||||
/tmp/kubectl version --client
|
||||
|
||||
# ${MANIFEST_FILE} es solo el patch de Kustomize del Deployment, no el
|
||||
# manifiesto que de verdad corre en el clúster -- escanearlo directo
|
||||
# generaba falsos positivos en todo lo que hereda del base remoto de
|
||||
# platform-templates (runAsNonRoot, seccompProfile), porque ese patch
|
||||
# nunca repite lo que ya trae el base (visto en vivo 2026-08-24:
|
||||
# comparando el patch aislado contra el output real de
|
||||
# "kubectl kustomize", 2 de 5 hallazgos desaparecían). Renderizamos el
|
||||
# kustomization completo (${KUSTOMIZE_DIR}, que sí trae el base vía
|
||||
# git remoto) y escaneamos eso.
|
||||
- name: Renderizar manifiesto Kustomize
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
/tmp/kubectl kustomize "${KUSTOMIZE_DIR}" > /tmp/frontend-rendered.yaml
|
||||
|
||||
# Escanea el manifiesto Kubernetes real del frontend (no la imagen):
|
||||
# contenedores como root, falta de resource limits, falta de
|
||||
# readiness/liveness probes, etc. Informativo por ahora — no bloquea
|
||||
@@ -287,7 +314,7 @@ jobs:
|
||||
/tmp/trivy config \
|
||||
--severity CRITICAL,HIGH,MEDIUM \
|
||||
--exit-code 0 \
|
||||
"${MANIFEST_FILE}"
|
||||
/tmp/frontend-rendered.yaml
|
||||
|
||||
# Instalado directo con pip (no como container aparte): el runner de
|
||||
# Gitea ejecuta cada job ya dentro de un contenedor propio que habla
|
||||
@@ -367,6 +394,12 @@ jobs:
|
||||
|
||||
# CRITICAL bloquea el pipeline: no se sube una imagen con una CVE
|
||||
# crítica conocida y con fix disponible.
|
||||
# --timeout 15m0s: el default de Trivy (5m) dejó de alcanzar también
|
||||
# para el frontend (visto en vivo: "context deadline exceeded" a los
|
||||
# ~4m45s, en un run justo después de terminar el docker build, con
|
||||
# el host bajo la misma inestabilidad de nodo vista ese día). Mismo
|
||||
# fix ya aplicado en build-medusa.yaml el 2026-08-16 -- el supuesto
|
||||
# de que el frontend era más liviano y no lo necesitaba ya no vale.
|
||||
- name: Escanear imagen (Trivy) — CRITICAL bloquea
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -375,6 +408,7 @@ jobs:
|
||||
--severity CRITICAL \
|
||||
--exit-code 1 \
|
||||
--ignore-unfixed \
|
||||
--timeout 15m0s \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
# HIGH solo informa por ahora — no bloquea mientras aprendemos a leer
|
||||
@@ -387,6 +421,7 @@ jobs:
|
||||
--severity HIGH \
|
||||
--exit-code 0 \
|
||||
--ignore-unfixed \
|
||||
--timeout 15m0s \
|
||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||
|
||||
# SBOM de la imagen que ya pasó el gate de CRITICAL — describe
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
|
||||
// GET /store/products descarta en silencio la relacion `categories`
|
||||
// aunque `/admin/products` si la expone -- confirmado en vivo en la
|
||||
// auditoria de Fase 5 (ver docs/decisiones/0004-evaluacion-stack-multi-categoria.md).
|
||||
// Este es el patron oficial de Medusa v2 para ampliar los campos
|
||||
// permitidos de una ruta core restringida (middleware global sobre
|
||||
// req.allowed), no un query-config custom:
|
||||
// https://docs.medusajs.com/learn/fundamentals/api-routes/retrieve-custom-links
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/products",
|
||||
middlewares: [
|
||||
(req, res, next) => {
|
||||
;(req.allowed ??= []).push("categories")
|
||||
next()
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
runAsNonRoot: false
|
||||
containers:
|
||||
- name: app
|
||||
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.123
|
||||
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.125
|
||||
# El base saca TODAS las capabilities. Probado en vivo y
|
||||
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
|
||||
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al
|
||||
|
||||
@@ -65,7 +65,7 @@ spec:
|
||||
memory: 64Mi
|
||||
|
||||
- name: migrations
|
||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.121
|
||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.127
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- node
|
||||
@@ -86,7 +86,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: app
|
||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.121
|
||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.127
|
||||
imagePullPolicy: IfNotPresent
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
runAsUser: 1001
|
||||
containers:
|
||||
- name: app
|
||||
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.122
|
||||
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.133
|
||||
env:
|
||||
- name: HEADLESS_PROVIDER
|
||||
value: medusa
|
||||
@@ -64,9 +64,20 @@ spec:
|
||||
# binario de node para esto, pero eso solo funciona si el
|
||||
# bounding set del contenedor lo permite -- drop: ALL del base
|
||||
# lo bloquea igual, root o no root. Mismo motivo que docs-portal.
|
||||
#
|
||||
# readOnlyRootFilesystem: true -- revisado en código (2026-08-24,
|
||||
# aún no probado en vivo con este cambio) que esta imagen sí
|
||||
# escribe a disco en runtime, no solo en build: el servidor
|
||||
# standalone de Next.js cachea a ./.next/cache tanto el
|
||||
# optimizador de imágenes (next.config.ts trae remotePatterns,
|
||||
# sin "unoptimized: true") como el revalidateTag("medusa-products")
|
||||
# de app/api/revalidate/route.ts (cache handler por default, sin
|
||||
# uno custom tipo Redis). De ahí los dos volumeMounts de abajo:
|
||||
# sin ellos, con root filesystem de solo lectura, ambas rutas
|
||||
# fallarían al intentar escribir.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -79,3 +90,13 @@ spec:
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 768Mi
|
||||
volumeMounts:
|
||||
- name: next-cache
|
||||
mountPath: /app/.next/cache
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: next-cache
|
||||
emptyDir: {}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
@@ -66,190 +66,6 @@ type MedusaProductsResponse = {
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
type LegacyMeta = {
|
||||
category: string;
|
||||
collection: string;
|
||||
brand: string;
|
||||
ageRange: string;
|
||||
featured: boolean;
|
||||
isNew: boolean;
|
||||
fallbackImage: string;
|
||||
fallbackThumbnail: string;
|
||||
};
|
||||
|
||||
const legacyProductMeta: Record<string, LegacyMeta> = {
|
||||
"toy-story-5-rex": {
|
||||
"category": "Figuras",
|
||||
"collection": "Toy Story",
|
||||
"brand": "Disney Pixar",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": true,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-001-toy-story-5-rex.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-001-toy-story-5-rex.webp"
|
||||
},
|
||||
"toy-story-5-buzz-lightyear": {
|
||||
"category": "Figuras",
|
||||
"collection": "Toy Story",
|
||||
"brand": "Disney Pixar",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": true,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-002-toy-story-5-buzz-lightyear.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-002-toy-story-5-buzz-lightyear.webp"
|
||||
},
|
||||
"toy-story-5-jessie": {
|
||||
"category": "Figuras",
|
||||
"collection": "Toy Story",
|
||||
"brand": "Disney Pixar",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": true,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-003-toy-story-5-jessie.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-003-toy-story-5-jessie.webp"
|
||||
},
|
||||
"toy-story-5-woody": {
|
||||
"category": "Figuras",
|
||||
"collection": "Toy Story",
|
||||
"brand": "Disney Pixar",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": true,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-004-toy-story-5-woody.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-004-toy-story-5-woody.webp"
|
||||
},
|
||||
"intensamente-furia": {
|
||||
"category": "Peluches",
|
||||
"collection": "Intensamente",
|
||||
"brand": "Disney",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": true,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-005-intensamente-furia.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-005-intensamente-furia.webp"
|
||||
},
|
||||
"intensamente-tristeza": {
|
||||
"category": "Peluches",
|
||||
"collection": "Intensamente",
|
||||
"brand": "Disney",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": true,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-006-intensamente-tristeza.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-006-intensamente-tristeza.webp"
|
||||
},
|
||||
"minnie-y-mickey-mouse": {
|
||||
"category": "Peluches",
|
||||
"collection": "Mickey & Minnie",
|
||||
"brand": "Disney",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-007-minnie-y-mickey-mouse.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-007-minnie-y-mickey-mouse.webp"
|
||||
},
|
||||
"angel-lilo-y-stitch": {
|
||||
"category": "Peluches",
|
||||
"collection": "Lilo & Stitch",
|
||||
"brand": "Disney",
|
||||
"ageRange": "3+",
|
||||
"featured": true,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-008-angel-lilo-y-stitch.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-008-angel-lilo-y-stitch.webp"
|
||||
},
|
||||
"mochilas-disney-coleccion-marvel": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Marvel",
|
||||
"brand": "Marvel",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-009-mochilas-disney-coleccion-marvel.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-009-mochilas-disney-coleccion-marvel.webp"
|
||||
},
|
||||
"mochila-ligera-y-resistente-al-agua-de-disney-frozen-mochila-escolar-y-de-viaje-con-multiples-compa": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Frozen",
|
||||
"brand": "Disney",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-010-mochila-ligera-y-resistente-al-agua-de-d.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-010-mochila-ligera-y-resistente-al-agua-de-d.webp"
|
||||
},
|
||||
"mochila-escolar-de-spider-man-de-disney-moda-estudiantil-libro-de-gran-capacidad-mochila-ligera": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Marvel",
|
||||
"brand": "Marvel",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-011-mochila-escolar-de-spider-man-de-disney-.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-011-mochila-escolar-de-spider-man-de-disney-.webp"
|
||||
},
|
||||
"disney-mochila-de-poliester-ultraligera-y-transpirable-de-gran-tamano-y-multiples-colores-dimension": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Disney",
|
||||
"brand": "Disney",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-012-disney-mochila-de-poliester-ultraligera-.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-012-disney-mochila-de-poliester-ultraligera-.webp"
|
||||
},
|
||||
"mochila-pequena-bob-esponja": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Bob Esponja",
|
||||
"brand": "Disney",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-013-mochila-pequena-bob-esponja.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-013-mochila-pequena-bob-esponja.webp"
|
||||
},
|
||||
"mochila-casual-de-gran-capacidad-con-estampado-de-bob-esponja-unisex-bolsa-escolar-de-dibujos-anim": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Bob Esponja",
|
||||
"brand": "Disney",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-014-mochila-casual-de-gran-capacidad-con-est.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-014-mochila-casual-de-gran-capacidad-con-est.webp"
|
||||
},
|
||||
"mochila-escolar-estilo-britanico-de-disney-con-proteccion-de-la-columna-vertebral-nueva-mochila-de": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Disney",
|
||||
"brand": "Disney",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-015-mochila-escolar-estilo-britanico-de-disn.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-015-mochila-escolar-estilo-britanico-de-disn.webp"
|
||||
},
|
||||
"disney-mochila-iron-man-de-poliester-ultraligera-y-transpirable-de-gran-tamano-dimensiones-46-cm-x": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Marvel",
|
||||
"brand": "Marvel",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-016-disney-mochila-iron-man-de-poliester-ult.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-016-disney-mochila-iron-man-de-poliester-ult.webp"
|
||||
},
|
||||
"mochila-de-disney-marvel-capitan-america-de-gran-capacidad-ligera-y-resistente-a-salpicaduras-par": {
|
||||
"category": "Mochilas",
|
||||
"collection": "Marvel",
|
||||
"brand": "Marvel",
|
||||
"ageRange": "6+",
|
||||
"featured": false,
|
||||
"isNew": false,
|
||||
"fallbackImage": "https://shop.cruzcloud.net/images/products/ari-017-mochila-de-disney-marvel-capitan-america.webp",
|
||||
"fallbackThumbnail": "https://shop.cruzcloud.net/images/thumbnails/ari-017-mochila-de-disney-marvel-capitan-america.webp"
|
||||
}
|
||||
};
|
||||
|
||||
function backendUrl(): string {
|
||||
const value = process.env.MEDUSA_BACKEND_URL;
|
||||
if (!value) {
|
||||
@@ -338,7 +154,6 @@ function mapVariant(variant: MedusaVariant): ProductVariant {
|
||||
|
||||
function mapProduct(product: MedusaProduct): Product {
|
||||
const slug = product.handle || product.id;
|
||||
const legacy = legacyProductMeta[slug];
|
||||
|
||||
const variant = product.variants?.[0];
|
||||
const calculated = variant?.calculated_price;
|
||||
@@ -346,24 +161,16 @@ function mapProduct(product: MedusaProduct): Product {
|
||||
const category =
|
||||
product.categories?.[0]?.name ||
|
||||
stringMetadata(product.metadata, "category") ||
|
||||
legacy?.category ||
|
||||
"Variedades";
|
||||
|
||||
const collection =
|
||||
product.collection?.title ||
|
||||
stringMetadata(product.metadata, "collection") ||
|
||||
legacy?.collection ||
|
||||
"";
|
||||
|
||||
const brand =
|
||||
stringMetadata(product.metadata, "brand") ||
|
||||
legacy?.brand ||
|
||||
"ARI Shopping";
|
||||
const brand = stringMetadata(product.metadata, "brand") || "ARI Shopping";
|
||||
|
||||
const ageRange =
|
||||
stringMetadata(product.metadata, "ageRange") ||
|
||||
legacy?.ageRange ||
|
||||
"Todas";
|
||||
const ageRange = stringMetadata(product.metadata, "ageRange") || "Todas";
|
||||
|
||||
const images = [
|
||||
...(product.images || [])
|
||||
@@ -371,15 +178,8 @@ function mapProduct(product: MedusaProduct): Product {
|
||||
.filter(Boolean),
|
||||
];
|
||||
|
||||
if (images.length === 0 && legacy?.fallbackImage) {
|
||||
images.push(legacy.fallbackImage);
|
||||
}
|
||||
|
||||
const thumbnail =
|
||||
product.thumbnail ||
|
||||
images[0] ||
|
||||
legacy?.fallbackThumbnail ||
|
||||
"/images/brand/logo-ari.png";
|
||||
product.thumbnail || images[0] || "/images/brand/logo-ari.png";
|
||||
|
||||
const price =
|
||||
typeof calculated?.calculated_amount === "number"
|
||||
@@ -426,14 +226,8 @@ function mapProduct(product: MedusaProduct): Product {
|
||||
currency: normalizeCurrency(calculated?.currency_code),
|
||||
priceLabel: price === null ? "Consultar disponibilidad" : "",
|
||||
available,
|
||||
featured:
|
||||
booleanMetadata(product.metadata, "featured") ||
|
||||
legacy?.featured ||
|
||||
false,
|
||||
isNew:
|
||||
booleanMetadata(product.metadata, "isNew") ||
|
||||
legacy?.isNew ||
|
||||
false,
|
||||
featured: booleanMetadata(product.metadata, "featured"),
|
||||
isNew: booleanMetadata(product.metadata, "isNew"),
|
||||
image: images[0] || thumbnail,
|
||||
thumbnail,
|
||||
images: images.length > 0 ? images : [thumbnail],
|
||||
@@ -528,7 +322,7 @@ async function fetchProducts(
|
||||
// region_id.
|
||||
region_id: regionId(),
|
||||
fields:
|
||||
"+metadata,+images,+tags,+categories,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
||||
"+metadata,+images,+tags,+categories.*,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
||||
});
|
||||
|
||||
if (filters?.query) {
|
||||
@@ -559,7 +353,7 @@ export const medusaCatalogProvider: CatalogProvider = {
|
||||
// region_id.
|
||||
region_id: regionId(),
|
||||
fields:
|
||||
"+metadata,+images,+tags,+categories,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
||||
"+metadata,+images,+tags,+categories.*,+collection,+variants.inventory_quantity,+variants.calculated_price,+variants.options",
|
||||
});
|
||||
|
||||
const response = await medusaFetch<MedusaProductsResponse>(
|
||||
|
||||
Reference in new issue
Block a user