mcpField
S
storefront-builder

storefront-builder

github.com/saleor开发2384 次安装12 周前更新
代码仓库

关于 storefront-builder

storefront-builder 是指导 AI Agent 分步构建 Saleor 店面的技能,涵盖项目连接、视觉设计和商品目录。它强调遵循项目现有约定,并仅执行用户指定的步骤。

主要功能

  1. 01连接 Saleor GraphQL API
  2. 02配置代码生成与客户端
  3. 03定义店面设计令牌
  4. 04构建商品列表和详情页
  5. 05处理价格、图片与库存数据

适用场景

  1. 01将现有项目接入 Saleor
  2. 02为店面确定配色与字体
  3. 03创建商品目录和变体选择页面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/saleor/agent-skills --skill storefront-builder

安装后在 Claude Code 中输入/storefront-builder确认已加载。

详细介绍

Saleor Storefront 操作指南

此技能负责 Saleor 数据契约和 UX/数据层行为。它不负责框架脚手架、CSS 配置或环境变量加载细节——Agent 会从本地项目中了解这些内容。

解析 $ARGUMENTS,确定要运行的步骤。

步骤路由

将 $ARGUMENTS 的第一个单词视为步骤编号,并跳转到相应章节。只执行该步骤,然后停止并等待用户要求执行下一步。绝不自动连续执行多个步骤。

如果未提供步骤或步骤无法识别,请打印:

Saleor Storefront Builder

Usage: /storefront-builder <step>

Steps:
  1   Bootstrap — wire GraphQL client, codegen, Saleor API connection
  2   Design & aesthetic — color palette, typography, accent color
  3   Catalog — product list page + product detail page with variant selection

Example: /storefront-builder 1

步骤 1:项目引导

将现有项目连接到 Saleor 的 GraphQL API,并正确区分客户端和配置代码生成。

0. 检查 Saleor 实例

询问用户:

“你是否已准备好 Saleor 实例?

  • 没有——请前往 https://cloud.saleor.io/ 创建一个实例(提供免费套餐),然后带着 API URL 返回。
  • 有——请粘贴你的 storefront/API URL,我们就可以开始。”

等待用户回复后再继续。如果用户还没有实例,请在此处停止,让他们先完成设置。如果他们提供了 URL,请记录下来,以便在步骤 6 中使用。

1. 检查项目

读取 package.json 和存在的框架配置文件(nuxt.config.ts、next.config.*、svelte.config.js、remix.config.js、vite.config.* 等),了解:

  • 框架及其版本
  • 使用的包管理器(检查锁文件:pnpm-lock.yaml、yarn.lock、package-lock.json)
  • 现有的 GraphQL 配置(如果有)
  • 导入别名约定(例如 @/、~/、#)
  • 源代码目录布局(src/、app/、根目录平铺布局)

不要询问上述信息——从项目中自行判断。只有在无法确定且继续操作又需要相关信息时,才进行询问。

2. 创建 AGENTS.md

如果仓库根目录下尚不存在 AGENTS.md,现在就创建它。此文件会将 Saleor 专属规则接入 AI 工具链,供今后在此仓库中的所有交互使用。

检查已安装的技能:

ls .agent-skills/saleor-storefront/AGENTS.md 2>/dev/null && echo "STOREFRONT" || echo ""
ls .agent-skills/saleor-configurator/AGENTS.md 2>/dev/null && echo "CONFIGURATOR" || echo ""

编写 AGENTS.md,只包含指向已安装技能的 @ 引用:

# Saleor Storefront

This is a Saleor-powered storefront.

## Workflow

When running `/storefront-builder`, execute only the requested step, then stop and wait for the user to ask for the next one. Never chain steps automatically.

## Saleor rules


@.agent-skills/saleor-storefront/AGENTS.md


@.agent-skills/saleor-configurator/AGENTS.md

如果 AGENTS.md 已存在,则完全跳过此步骤——不要覆盖它。

3. 安装 GraphQL 依赖项

使用步骤 1 中检测到的包管理器:

graphql-request graphql
@graphql-codegen/cli @graphql-codegen/client-preset  (dev)
4. 创建代码生成配置

在项目根目录创建代码生成配置文件(根据项目约定,文件名为 codegen.ts 或 codegen.js)。需要设置的关键值:

  • schema:Saleor GraphQL API URL,从项目使用的环境变量中读取(如果尚无约定,则使用 SALEOR_API_URL)
  • documents:指向项目 GraphQL 文件目录的 glob,遵循本地约定
  • generates:使用 client 预设,并设置 gqlTagName: "graphql"

在 package.json 中添加 codegen 脚本。

5. 创建 Saleor API 客户端

双客户端模式——这是 Saleor 的正确性要求,不可省略:

在符合项目库/工具函数约定的位置编写客户端模块,并导出两个客户端:

saleorClient      — anonymous, no auth headers — safe for RSC, SSG, public product queries
saleorAuthClient  — server-only, reads app token from env — NEVER use in browser bundles

**为什么双客户端很重要:**在公开/可缓存查询中传递应用令牌会泄露特权访问权限,并可能暴露客户数据。匿名查询必须保持匿名。

只有在设置了令牌环境变量时,认证客户端才应包含 Authorization 标头(使用条件判断,以免在缺少该变量的前端环境中导致模块抛出错误)。

6. 配置环境

根据项目确定环境变量命名约定(例如,Next.js 使用 NEXT_PUBLIC_* 表示可在浏览器访问的变量,Nuxt 使用 NUXT_PUBLIC_*,等等)。

必需变量:

  • [PUBLIC_PREFIX]_SALEOR_API_URL — Saleor GraphQL 端点
  • [PUBLIC_PREFIX]_SALEOR_CHANNEL — 默认渠道 slug
  • SALEOR_APP_TOKEN(无公开前缀——仅限服务端使用)

在项目的环境文件(.env.local、.env 等)中写入或更新占位值和注释。询问用户是否有 Saleor API URL 和渠道 slug,以便填写。

提示——使用 Configurator 检查现有商店 如果你可以访问现有的 Saleor 实例,但不确定配置了哪些渠道、类别或商品,请使用 Configurator CLI:

export SALEOR_URL=https://your-store.saleor.cloud/graphql/
export SALEOR_TOKEN=YOUR_TOKEN
pnpm dlx @saleor/configurator introspect

阅读生成的 config.yml,查找准确的渠道 slug、已发布商品和类别结构——在环境变量和查询中直接使用这些值。

7. 验证

如果已配置 API URL,请运行代码生成,以确认可以访问架构:

[package-manager] codegen 2>&1 | head -20

如果因网络错误而失败,请协助排查问题(URL 错误、缺少身份验证等)。

8. 总结
[✓/–] AGENTS.md: [created / already existed]
✓ Framework: [detected framework]
✓ Package manager: [pm]
✓ Deps: graphql-request, @graphql-codegen/cli, @graphql-codegen/client-preset
✓ Clients: [path] (public + authenticated)
✓ Codegen: codegen.ts
[✓/⚠] API URL: [set / not set]
[✓/⚠] Channel: [slug / placeholder]

Next: /storefront-builder 2

打印总结后即停止。除非用户明确提出要求,否则不要继续执行步骤 2。


步骤 2:设计与美感

在编写任何 UI 代码之前,定义店面的视觉形象。此步骤的输出是一个主题模块和设计令牌,后续所有步骤都会导入它们。确切的文件路径和令牌格式遵循项目现有约定。

1. 检查项目的样式配置

阅读项目以确定:

  • 使用的 CSS 框架(Tailwind、CSS Modules、styled-components、UnoCSS、原生 CSS 等)
  • 现有的设计令牌约定(CSS 自定义属性、theme.* 文件、Tailwind 配置等)
  • 共享样式所在的位置

不要假设项目使用 Tailwind 或任何特定的 CSS 方案——应根据项目实际情况判断。

2. 询问用户偏好的美感

在一条消息中向用户提出三个问题——采用对话式语气,而非表单:

“我们来确定一下店面的视觉风格。有几个简单的问题:

  1. 你有喜欢的参考吗?(某个品牌、URL、截图——也可以跳过)
  2. 整体感觉希望是什么样的?如果有帮助,可以从这些风格开始:简约明亮、暗调奢华、大胆多彩、柔和温暖、经典编辑风——也可以用自己的话描述。
  3. 你有想要的强调色吗?它会用于按钮和链接。可以提供十六进制值、颜色名称,或者交给我决定。”

如果用户提供的信息很少,继续之前先追问一次。

3. 确定令牌

确定以下值:背景、表面、边框、主文本、次级文本、强调色、悬停强调色、边框圆角、标题字体、正文字体。

4. 编写主题令牌

按照项目的现有约定,在相应位置编写主题模块。添加注释块,记录:

  • 样式预设名称
  • 参考来源(如有)
  • 强调色的选择理由
  • 字体选择

按照项目本地约定,将令牌接入样式系统:

  • Tailwind:使用令牌值扩展 tailwind.config.*
  • CSS 自定义属性:写入项目的全局 CSS 文件
  • 其他:遵循项目当前使用的方式

更新全局/基础 CSS,以设置默认背景和文本样式。

5. 总结
✓ Style: [preset name]
✓ Accent: [color]
✓ Typography: [font choice]
✓ Theme tokens: [path]
✓ Styling system updated: [tailwind.config / globals.css / etc.]

Next: /storefront-builder 3

打印总结后即停止。除非用户明确提出要求,否则不要继续执行步骤 3。


步骤 3:目录——商品列表 + PDP

构建商品列表页和带有变体选择功能的商品详情页。

前置条件检查

验证 Saleor 客户端模块是否存在(根据 Step 1 中的设置检查)。如果缺失,告知用户先运行 /storefront-builder 1。

检查项目的 env 文件中是否有频道 slug。如果缺失且未作为参数传入,请询问:

"你的 Saleor 频道 slug 是什么?(Saleor Dashboard → Channels,或按 Enter 使用 'default-channel')"

检查项目使用的框架和路由约定,以确定应将页面写入何处,以及数据获取的工作方式(服务端组件、getStaticProps、加载器、load 函数、asyncData 等)。

1. GraphQL 查询 — Saleor 数据契约

在项目的 GraphQL 文档目录中编写 products.graphql 文件。

ProductCard 片段

商品列表页面所需字段:

fragment ProductCard on Product {
  id
  name
  slug
  thumbnail {
    url
    alt
  }
  pricing {
    priceRange {
      start {
        gross {
          amount
          currency
        }
      }
    }
  }
  category {
    name
    slug
  }
}

这些字段的原因:

  • thumbnail 可为空 — 始终使用备用图片或占位图进行保护
  • pricing.priceRange.start 可为空 — 渲染价格前进行保护
  • category 可为空 — 渲染类别标签前进行保护
ProductDetails 片段

商品详情页所需字段:

fragment ProductDetails on Product {
  id
  name
  slug
  description
  thumbnail {
    url
    alt
  }
  media {
    url
    alt
    type
  }
  pricing {
    priceRange {
      start {
        gross {
          amount
          currency
        }
      }
    }
  }
  category {
    name
    slug
  }
  variants {
    id
    name
    sku
    pricing {
      price {
        gross {
          amount
          currency
        }
      }
      priceUndiscounted {
        gross {
          amount
          currency
        }
      }
    }
    selectionAttributes: attributes(variantSelection: VARIANT_SELECTION) {
      attribute {
        name
        slug
      }
      values {
        name
        slug
      }
    }
    quantityAvailable
  }
}

这些字段的原因:

  • PDP 上优先使用 media 数组而非 thumbnail — 当 media 为空时,使用 thumbnail 作为备用
  • variants.pricing 可为空 — 访问 amount 前进行保护
  • 匿名用户的 quantityAvailable 可为空 — 将 null 视为有库存(按库存数量为 1 处理)
  • selectionAttributes 使用 variantSelection: VARIANT_SELECTION 过滤器 — 仅返回区分变体的属性(尺码、颜色等),不返回商品级属性
查询
query ProductList($channel: String!, $first: Int = 20, $after: String) {
  products(channel: $channel, first: $first, after: $after) {
    edges {
      node {
        ...ProductCard
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

query ProductBySlug($slug: String!, $channel: String!) {
  product(slug: $slug, channel: $channel) {
    ...ProductDetails
  }
}

必须始终指定频道 — 不带 channel 的查询不会返回价格或可用性数据。

编写查询后运行 codegen。

2. Saleor 数据处理规则

实现页面和组件时应用以下规则:

描述解析

Saleor 将 description 存储为 EditorJS JSON。切勿直接渲染原始内容。请安全解析:

function extractDescriptionText(description: unknown): string {
  try {
    const parsed = typeof description === "string" ? JSON.parse(description) : description;
    return parsed?.blocks
      ?.map((b: { data?: { text?: string } }) => b.data?.text ?? "")
      .filter(Boolean)
      .join(" ") ?? "";
  } catch {
    return "";
  }
}
价格格式化

始终使用 Intl.NumberFormat 和响应中的货币 — 切勿硬编码货币符号:

function formatPrice(amount: number, currency: string) {
  return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(amount);
}

将区域设为 undefined,以遵循用户的浏览器区域设置(如果项目有区域设置系统,也可以传入区域)。

图片处理
  • 在 PDP 上:优先使用 product.media[0],而非 thumbnail;如果 media 为空,则回退到 thumbnail
  • 始终处理图片缺失的情况 — 渲染中性占位图,而不是损坏的 <img> 标签
  • 将 alt ?? product.name 用作备用替代文本
库存 / 可用性语义
  • quantityAvailable === null → 视为有货(匿名用户看不到库存)
  • quantityAvailable === 0 → 缺货 — 禁用选择并显示视觉指示(删除线或弱化显示)
  • quantityAvailable > 0 → 有货
变体选择体验
  • 显示所有变体;缺货变体应禁用,而非隐藏 — 让用户了解有哪些选项
  • 存在 selectionAttributes 时,用它们标注变体(例如“尺码:M”“颜色:红色”)
  • 如果商品只有一个变体且没有选择属性,则跳过选择器,直接显示“加入购物车”
  • 用户选择的变体的 pricing.price 会覆盖商品级的 pricing.priceRange — 选择变体时更新显示的价格
空状态 / 错误状态
  • 商品列表没有结果:显示清晰的消息并附带排查提示(频道 slug 错误或商品尚未发布)
  • 找不到商品(ProductBySlug 返回 null):使用框架的未找到/404 机制
  • 缺少价格:完全省略价格,不要显示 $0 或 NaN
3. 编写共享导航

在项目的组件目录中编写导航栏/页头组件,并遵循本地命名约定。导航应使用 Step 2 中确立的主题令牌(如果跳过 Step 2,则使用合理的中性色默认值)。

按照项目中检测到的框架约定,将导航接入根布局 / 应用外壳。

4. 编写商品列表页

按照项目的路由约定,在合适的路径编写商品列表页(例如 app/page.tsx、pages/index.tsx、pages/index.vue、src/routes/+page.svelte、app/routes/_index.tsx)。

数据获取方式:使用框架提供的方式(异步服务端组件、getStaticProps/ISR、load 函数、asyncData、Remix loader)。对于支持 SSG 的框架,请设置合理的重新验证间隔(例如 60s)。

应用第 2 节中的所有数据处理规则:防护可空值、正确格式化价格、显示空状态。

5. 编写 PDP

按照路由约定,在合适的路径编写 PDP(例如 app/p/[slug]/page.tsx、pages/p/[slug].tsx、pages/p/[slug].vue、src/routes/p/[slug]/+page.svelte)。

应用第 2 节中的所有数据处理规则。

6. 编写 VariantSelector 组件

在项目的组件目录中编写 VariantSelector 组件。它必须支持客户端交互(使用框架提供的任意交互原语,例如 React state、Vue ref、Svelte store 等)。

行为:

  • 显示所有变体;对缺货变体禁用(不要隐藏)
  • 突出显示已选变体
  • 选择变体后更新显示的价格(变体的 pricing.price 优先)
  • 在选择变体为必需项时,选中变体前禁用 Add to Cart 按钮
  • 单变体/无属性商品:跳过选择器,直接显示 Add to Cart
  • 此步骤中 Add to Cart 不具备实际功能——仅作占位,请在注释中明确说明
7. 运行并验证

使用项目的开发命令启动开发服务器。引导用户访问商品列表页和 PDP URL,以确认数据加载正常。

常见问题:

  • 列表为空:频道 slug 错误,或商品未在该频道发布——建议运行 configurator introspect 检查商店
  • Codegen 错误:API URL 未设置或无法访问
  • 所有 slug 都找不到商品:频道不匹配,或商品未在该频道发布
总结
✓ GraphQL queries: [path]/products.graphql
✓ Types generated
✓ Navigation: [path] (wired into root layout)
✓ Product list: [route]
✓ Product detail: [route]
✓ VariantSelector: [path]

Note: "Add to Cart" is present but non-functional — checkout is not covered by this skill

This is the last step currently available in this skill.

打印总结后,停止。


Saleor 正确性规则(始终适用)

这些规则适用于所有步骤以及后续的所有店面工作:

  1. 始终传入 channel——每个商品/价格/库存可用性查询都需要它;省略后将无法返回数据
  2. 安全解析 description——它是 EditorJS JSON,而非纯文本或 HTML
  3. 切勿向浏览器公开 SALEOR_APP_TOKEN——使用双客户端模式;认证客户端仅在服务端使用
  4. quantityAvailable 为 null = 有货——匿名用户不会收到库存数量;null 表示“不要阻止购买”
  5. 每一层的 pricing 都可能为 null——访问 amount 前,请检查 pricing、pricing.price、pricing.priceRange 和 gross
  6. 使用 Intl.NumberFormat 格式化价格——切勿硬编码货币符号或假设区域设置
  7. PDP 媒体优先级:media[0] → thumbnail → 占位图
  8. 禁用缺货变体,而不是隐藏它们——隐藏会让用户对商品提供哪些选项感到困惑

评论

支持 Markdown