@aurelienbn/oxlint-plugin-shopify-app (英语)
27 oxlint规则为 Shopify apps和扩展:App Store的静态切片,为 Shopify所建,Admin API,以及Polaris App Home的要求.
[尖叫声] Passing lint 并不能让 App Store 或为 Shopify 所建的应用程序符合条件 . 这些规则只执行静态可核查部件.
pnpm add -D @aurelienbbn/oxlint-plugin-shopify-app oxlint # oxlint >=1.82.0 <2.0.0
没有预设, 无自定义:每个更改都意味着选择一个API,内容,布局,或运行时间策略.
27个规则,4个范围
server baseline ███████ 7 every app, server + route source
App Home recipe █████████████ 13 + 14 jsx-a11y, App Home paths only
category opt-in ████ 4 overrides on the paths the BFS category covers
other opt-in ███ 3 Function source · Polaris manifest · 1 candidate
规则从来不推断应用类别或扩展目标. 你决定,然后范围与 overrides。 。 。 .
服务器基线:每个应用程序,不需要决定
{
"jsPlugins": ["@aurelienbbn/oxlint-plugin-shopify-app"],
"rules": {
"shopify-app/no-script-tag-api": "error",
"shopify-app/require-mutation-user-errors": "error",
"shopify-app/require-idempotent-mutations": "error",
"shopify-app/no-swallowed-auth-response": "error",
"shopify-app/no-hardcoded-billing-test-mode": "error",
"shopify-app/webhook-hmac-verification-shape": "error",
"shopify-app/no-router-redirect-in-embedded-route": "warn"
}
}
`mutation($id: ID!, $tags: [String!]!) { tagsAdd(id: $id, tags: $tags) { node { id } } }`; // ❌ no userErrors
`mutation { tagsAdd(id: 1, tags: []) { node { id } userErrors { field message } } }`; // ✅
`mutation($input: RefundInput!) { refundCreate(input: $input) { refund { id } userErrors { message } } }`; // ❌ missingIdempotent
`mutation($input: RefundInput!) { refundCreate(input: $input) @idempotent(key: "4f5b6ebf-143c-4da5-8d0f-fb8553bfd85d") { refund { id } } }`; // ❌ literalIdempotencyKey
`mutation($input: RefundInput!, $key: String!) { refundCreate(input: $input) @idempotent(key: $key) { refund { id } } }`; // ✅
try {
const { admin } = await authenticate.admin(request);
return admin;
} catch (error) {
return data({ error: "Something went wrong" }, { status: 500 });
} // ❌ swallows the Response
try {
await authenticate.admin(request);
} catch (error) {
if (error instanceof Response) throw error;
return null;
} // ✅
await billing.require({ plans: [PRO], isTest: true, onFailure }); // ❌ never bills
同上:规则来源中的一个日期表. 发布2025-12-12,审查2026-09-20,要求行政API 2026-04脚本标签 停止运行于2027-03-01: 基线,不是BFS选入.
精确触发、 静态大小写、 选项
| 规则 | * 开火 |
|---|---|
no-script-tag-api | scriptTagCreate 编号 : scriptTagUpdate 变种, REST script_tags 请求,生成脚本标签文档导入 |
require-mutation-user-errors | 图形QL 字符串或模板中未选中的顶级突变字段 userErrors 编号 : *UserErrors 字段 |
require-idempotent-mutations | 17起退款、库存和地点变异之一 偶数变化日志 无 @idempotent (单位:千美元)missingIdempotent),或带有字符串-升键(literalIdempotencyKey(中文(简体) ) |
no-swallowed-auth-response | authenticate.admin/webhook/flow/fulfillmentService/pos, (中文(简体) ), authenticate.public.*, (中文(简体) ), billing.require/request/cancel/updateUsageCappedAmount, (中文(简体) ), scopes.request,或一个 redirect 从 authenticate.admin内 try 职务 catch 没有 throw 也没有 instanceof Response 测试。 这些掷出 Response 作为控制流 |
no-hardcoded-billing-test-mode | 字义 isTest: true 在第一个论点中 billing.require/request/check/cancel/createUsageRecord,或字义 test: true 打开 appSubscriptionCreate 编号 : appPurchaseOneTimeCreate |
webhook-hmac-verification-shape | 包含此选项的文件 X-Shopify-Hmac-SHA256 只有页眉名称 : .update(JSON.stringify(...)) 编号 createHmac 链; createHmac 没有 timingSafeEqual 编号 : safeCompare 标识符 |
no-router-redirect-in-embedded-route ⚠️ warn | redirect 从 react-router 或者说 @remix-run/{node,server-runtime,cloudflare} 调用的模块中 authenticate.admin,或任何带有 https:// 或者说 shopify:// 目标。 相对重定向通常仍然有效,因此 warn |
| 规则 | 安静 | 选项( 默认) |
|---|---|---|
require-mutation-user-errors | 未知的片段,字段内的插值,无法解析的文本. 同一文件片段被跟踪 | ignoreMutations (单位:千美元)[]:没有球场的有效载荷. 排除非Shopify GraphQL 客户端通过 overrides |
require-idempotent-mutations | —— 说 | since (未设定): 管理员 API 版本,您标注, YYYY-01/04/07/10下图 2026-04 安静 missingIdempotent 仅限 |
no-hardcoded-billing-test-mode | 测试和固定文件 | —— 说 |
webhook-hmac-verification-shape | 认证到另一个文件; OAuth hmac 查询检查 | safeCompareNames (单位:千美元)["timingSafeEqual", "safeCompare"]页:1 |
需要17起变异 @idempotent数字 : refundCreate • 妇女 inventoryShipmentReceive • 妇女 inventoryAdjustQuantities • 妇女 inventoryMoveQuantities • 妇女 inventorySetQuantities • 妇女 inventorySetOnHandQuantities • 妇女 inventoryShipmentCreateInTransit • 妇女 inventoryShipmentCreate • 妇女 inventoryTransferCreate • 妇女 inventoryTransferCreateAsReadyToShip • 妇女 inventoryTransferDuplicate • 妇女 inventoryTransferSetItems • 妇女 inventorySetScheduledChanges • 妇女 inventoryActivate • 妇女 inventoryShipmentAddItems • 妇女 locationActivate • 妇女 locationDeactivate
App 家食谱:Polaris + 原生 a11y
复制已测试的 应用 家食谱在 App Home 源码上运行 :
oxlint --config app-home.oxlintrc.json app
app-home.oxlintrc.json
├── jsx-a11y (14 upstream rules) native HTML accessibility
└── shopify-app (13 rules) Polaris App Home v1.0 contracts
no-nav-emoji · no-viewport-zoom-disable · s-action-slot-contract
s-button-accessible-name · s-button-submit-no-navigation · s-clickable-accessible-name
s-form-control-label-required · s-modal-actions-use-slots · s-modal-heading-required
s-money-field-no-currency-symbol (warn) · s-page-aside-visible
s-spinner-accessible-label · s-tooltip-no-interactive-content
把这个食谱放在App Home上. 离职和客户账户部分有不同的合同.
<s-button icon="edit" /> // ❌ s-button-accessible-name
<s-button icon="edit" accessibilityLabel="Edit product" /> // ✅
<s-text-field labelAccessibilityVisibility="exclusive" /> // ❌ hiding the label doesn't remove the need for one
<s-search-field label="Search" labelAccessibilityVisibility="exclusive" /> // ✅
<s-money-field label="Price ($)" /> // ❌ currency formatting is component-owned
仅静态:静态诊断不能证明已形成状态.
每项规则合同和哪些静态检查缺失
| 规则 | 合同 |
|---|---|
s-action-slot-contract | 无条件直接页面/模式儿童,包括碎片. primary-action页:1 s-button 与 variant=primary。二级槽:二级/自动按钮;页面也接受按钮组 |
s-modal-actions-use-slots | 模式动作按钮使用模式动作槽. 选项 components (单位:千美元)["s-modal"], (中文(简体) ), actionElements (单位:千美元)["s-button", "button"](中文(简体) ) |
s-modal-heading-required | 无空 heading 在模式上。 选项 components (单位:千美元)["s-modal"](中文(简体) ) |
s-page-aside-visible | 直接放在一页下,明确 inlineSize 大小( 不会渲染) |
s-button-submit-no-navigation | 静态 href 编号 : commandFor 覆盖提交/重置行为 |
s-button-accessible-name, (中文(简体) ), s-clickable-accessible-name | 直接内容, 包含片段; 图标元素和空字符不命名动作 |
s-form-control-label-required | 字段使用 label; 切换和投放区域可能使用其文档 accessibilityLabel 换句话说 已知 null,布尔值和空值被拒绝 |
s-spinner-accessible-label | 无空 accessibilityLabel |
s-tooltip-no-interactive-content | 无条件的固有后人; 在用户组件边界上停止 |
s-money-field-no-currency-symbol | 以字面标签/位置符表示的Unicode货币符号. 不检查翻译或财务算术 |
no-viewport-zoom-disable | maximum-scale 5岁以下, user-scalable=no 编号 : 0 |
no-nav-emoji | 导航标签中的emoji. 选项 navComponents (单位:千美元)["s-app-nav", "ui-nav-menu"](中文(简体) ) |
规则读取内在元素,静态字元,而内线对象以有效覆被相向相向相向相向.
| 已检查 | ⚠️仍然需要审查或进行检查 |
|---|---|
| 内在元素、静态文字、内向扩展 | 运行时表达式, 后期未知的扩展, 翻译字符串, 自定义组件输出 |
| 直接儿童和碎片 | 有条件渲染、包装、按钮组内容、其他嵌入元素、用户组件 |
类别选择:首先决定
| 规则 | 打开它 |
|---|---|
no-draft-order-custom-discounts | 在Shopify 5.5.2下建造的折扣应用自动化系统。 将商家驱动的草稿订单工作流程保留在外头 |
no-asset-api-theme-writes | 在审查了它的页面构建器、备份/存储器、 SIO、 内容锁定和开发者工具的例外之后, 创建Shopify 3.2.2下的应用程序代码 |
no-admin-rest-api | 在 GraphQL 政策下的新公共应用程序. 现有公共和定制应用程序:先作出明确的移民决定 |
require-fetch-abort-signal | 在请求取消政策时,明确退出扩展。 与运行时间截止时间证据对等: 信号不是超时 |
[尖叫声] 永远不要用被子瞄准
extensions/**数字 : 无法分辨出退出、客户账户、管理员、函数和主题.
管理扩展,允许 vs 报告, no-admin-rest-api 范围
保留 require-fetch-abort-signal 关闭管理员 UI 扩展名: 相对 fetch("/api/...") 给应用程序自己的域是他们的 文件自动认证路径;注销是一种离职和客户账户政策。 类别适用性和例外: 为商店化要求而建。 。 。 .
| 规则 | 允许 | 报告 | * 审查投入 |
|---|---|---|---|
no-draft-order-custom-discounts | 提及的答复选择 appliedDiscount (不是输入) | 应用自定义折扣的草稿-订单突变(平分突变字段) | 可变提供突变输入 |
no-asset-api-theme-writes | REST 资产 API GET | 已识别写入方法; 主题文件突变 | —— 说 |
require-fetch-abort-signal | 明确的可用信号形状; 公认的 Request-自有信号 | fetch (直接或 window 编号 : globalThis 编号 : self无 | 未知的选项变量和后起倒数的相差不能证明 |
no-admin-rest-api数字 :
| 报告 | 允许 | 需求审查 |
|---|---|---|
从 REST 资源导入 @shopify/shopify-api/rest/admin,动态导入,无阴影 require | 图QL, Storefront/Ajax API, 只类型导入 | SDK 实例, 包装 |
无阴影的全局 fetch 带有可识别的 REST 管理员 URL | 不相关的主机, 阴影函数 | 动态选择的 API 版本和资源名 |
其他选入
functions-no-unavailable-runtime-apis 打开 仅限 JS 函数来源。 。 。 . s-known-components 在App Home的源头, 除了食谱 no-session-or-token-logging 是一个候选人,默认关闭.
<s-stak gap="base" /> // ❌ unknown element
<s-button varient="primary">Save</s-button> // ❌ unknown attribute
<s-button variant="primary" onClick={save}>Save</s-button> // ✅ declared attribute and event
s-known-components 从您的项目中读取其词汇, 从此软件包中读取 : 自定义元素表单 customElements 已安装的最近字段 @shopify/polaris-types (1.0.7及后). 它跟随你安装的主线. 没有包裹,或者没有 customElements 字段:规则保持沉默. 明确 manifestPath 无法读取,或者一个没有元素的表, 报告每个文件一次.
触发和选项
| 规则 | 状态 | * 开火 | 选项( 默认) |
|---|---|---|---|
functions-no-unavailable-runtime-apis | 选入, 仅限 JS 函数来源; 所有其他表面合法使用这些 API | aync 函数, 顶级 await, (中文(简体) ), .then() 未显示的呼叫结果 Promise, (中文(简体) ), setTimeout, (中文(简体) ), setInterval, (中文(简体) ), fetch, (中文(简体) ), crypto, (中文(简体) ), URL, (中文(简体) ), URLSearchParams, (中文(简体) ), process, (中文(简体) ), Buffer, (中文(简体) ), require, (中文(简体) ), node: 进口, Date.now()零弧线 Date, (中文(简体) ), Math.random(), (中文(简体) ), performance.now() | —— 说 |
s-known-components | 选入, App 主机源 | s-* 元素 运算表不声明;非声明属性的属性,公有字段或 on<event> 处理者。 总是允许 : key, (中文(简体) ), ref, (中文(简体) ), slot, (中文(简体) ), children, (中文(简体) ), id, (中文(简体) ), data-*. 允许要素(App Bridge导航链路)的子女保留自己的合同 | manifestPath (发现), allowElements (单位:千美元)["s-app-nav", "s-app-window"]页:1, allowAttributes (单位:千美元)[](中文(简体) ) |
no-session-or-token-logging | 候选人,默认关闭 | console 编号 : logger 编号 : log 电话曝光 session, (中文(简体) ), accessToken, (中文(简体) ), access_token, (中文(简体) ), sessionToken, (中文(简体) ), idToken (识别器,简称,终端成员,分布,模板孔, JSON.stringify),或 a payload 从 authenticate.webhook | loggerObjects (单位:千美元)["console", "logger", "log"], (中文(简体) ), sensitiveNames (上文5段), webhookPayloadNames (单位:千美元)["payload"]页:1 |
no-session-or-token-logging 除网钩有效载荷钩外,是域中立的: 更喜欢核心机密规则 一旦存在.
超出范围,谁拥有它
| 关切 | 拥有者 |
|---|---|
| 本地 HTML 访问 | 上游 jsx-a11y 规则 |
| 极地财产联盟 | 商店组件类型 |
| 对比 注意 复制质量 键盘行为 响应布局 运行时间 API行为 | 语义审查或浏览器/运行时间证据 |
| 页面面包屑, 选择选项内容 | 没有总括规则:Shopify的指导冲突 |
| 脚本标记范围 | 导入范围为生成/admin源; 附带 URL 字符串不是 API 调用 |
每条规则独立执行 Shopify 文档概念; 源文件带有 greppable @attribution 标签; 没有执行或推销.
按审查日期分列的资料来源
| 已审查 | 来源 |
|---|---|
| 2026-09-05 · 极地人 | REST 行政移徙政策, (中文(简体) ), 按钮, (中文(简体) ), 可点击, (中文(简体) ), 旋转器, (中文(简体) ), 窗体组件, (中文(简体) ), 页次, (中文(简体) ), 模式, (中文(简体) ), 工具提示, (中文(简体) ), 钱田 |
| 2026-09-20- 服务器 & API | 管理图形QL 引用, (中文(简体) ), 偶数变化日志, (中文(简体) ), 一能指南,控制流量 shopify-app-js, (中文(简体) ), 反应路由器应用程序模板, (中文(简体) ), 收费 API 参考, (中文(简体) ), webhook HTTPS 传送, (中文(简体) ), 函数的 JavaScript, (中文(简体) ), 函数输入/产出限制, (中文(简体) ), 受保护的客户数据, (中文(简体) ), 脚本 标记折旧更改日志 |
| 2026-09-24-极地表显示 | 应用 家用网络组件,则 customElements 字段和图示 自定义元素外观 格式,通过 @shopify/polaris-types 1.1.0和2.0.0-rc.0(相同的标记和属性) |
登记的合同库存
从软件包导出 pnpm catalog. 规则特定选项和限制在上文和源测试中都有描述.
| 规则/检查 | 触发或审查范围 |
|---|---|
functions-no-unavailable-runtime-apis | 取消ASync 代码、定时器、获取、加密、 URL、 进程、 缓冲、 节点: 导入、 时钟或随机读取的 Shopify 函数源; 将其范围设定为带有超文本功能目录 . |
no-admin-rest-api | 取消商店设置 管理资源导入和未遮蔽的获取呼叫以识别 REST 管理端点; 启用新的公共应用程序 . |
no-asset-api-theme-writes | Disallow主题文件通过资产 API 或主题文件突变来写入. |
no-draft-order-custom-discounts | 取消使用自定义折扣的订单草稿突变. |
no-hardcoded-billing-test-mode | 贬低字数 isTest: true * 电话和文字 test: true 在应用程序订阅Create/appPurchase OneTimeCreate 外部测试文件上,因为测试收费从来不向商家收费. |
no-nav-emoji | APP导航标签内出问题 选项 navComponents 覆盖选中元素名称 . |
no-router-redirect-in-embedded-route | 更喜欢通过认证返回的重定向帮助器. 在认证管理员请求的模块中管理路由器的再定向,并始终为https://或shookify://godes提供. |
no-script-tag-api | Disallow Script Tag API,它停止了在2027-03-01上运行,有利于Web像素和主题应用扩展. |
no-session-or-token-logging | 拒绝通过会话对象、访问或会话符号,或认证。 webhook 有效载荷呼叫控制台/调度器 选项 loggerObjects, (中文(简体) ), sensitiveNames 财务报告和审定财务报表 webhookPayloadNames 覆盖匹配的名称 . |
no-swallowed-auth-response | Disallow 认证.*,计费. required/request/cancel/update Usage CappedAmount, 瞄准镜. request and independent direct helper in a trign understands who catch never redrows, 因为他们将响应作为控制流扔出. |
no-viewport-zoom-disable | 关闭阻断缩放的视图端口标记 . |
require-fetch-abort-signal | 需要获取呼叫( 直接或窗口/ globalThis/ 自定义) 才能通过中止信号 . |
require-idempotent-mutations | 在17个存货上要求@idempotent(钥匙:可变美元),退款及位置变异拒绝Admin API 2026-04的通话,以及不允许的字形密钥. 选项 since 命名工程指针的API版本. |
require-mutation-user-errors | 要求每个 GraphQL 变异字段选择用户错误(或一个 *UserErrors 字段). 选项 ignoreMutations 列出没有这种有效载荷域的变异名称. |
s-action-slot-contract | 验证Polaris的页面/模式动作插槽元素类型、变体和单一主要特性. |
s-button-accessible-name | 在静态无名的Polaris按钮上需要访问标签,包括只使用图标的按钮. |
s-button-submit-no-navigation | 取消静态螺旋或命令 对于超过 Polaris 按钮提交或重设行为的值 . |
s-clickable-accessible-name | 要求无障碍 标注在 Polaris 可点击组件上而不包含静态可读内容 . |
s-form-control-label-required | 要求在极地形成字段上设置标签;开关和投放区可以使用其已记录的可访问性Label替代. |
s-known-components | 否定 s-* 项目安装的元素和属性 @shopify/polaris-types 《自定义要素宣言》不宣布;在没有明确决心时保持沉默。 选项 manifestPath, (中文(简体) ), allowElements 财务报告和审定财务报表 allowAttributes。 。 。 . |
s-modal-actions-use-slots | 要求模式动作按钮使用模式动作槽. 选项 components 财务报告和审定财务报表 actionElements 覆盖选中元素名称 . |
s-modal-heading-required | 在模式元素上需要一个标题属性 。 选项 components 覆盖选中元素名称 . |
s-money-field-no-currency-symbol | 以Polaris货币域标签和占位符表示的否定字面货币符号,其货币格式化为组件自有. |
s-page-aside-visible | 当一个明显的小或大内线Size使其无法渲染时,将 s-page 忽略内容. |
s-spinner-accessible-label | 在极地自旋器上需要非空取Label. |
s-tooltip-no-interactive-content | 在Polaris工具提示中,断绝静态互动的内在后人;自定义组件和有条件输出需要审查. |
webhook-hmac-verification-shape | 在读取 X-Shopify-Hmac-SHA256 标题的文件中,不允许hashing JSON.stringfy 输出并需要时间安全比较. 选项 safeCompareNames 覆盖已接受的比较标识符. |
信用概念
- https://github.com/Shophify/shopify-app-js (中文(简体) ) (激励;独立执行)
- https://github.com/Shophify/shopify-app-template-resact-router 互联网档案馆的存檔,存档日期2013-12-22 (激励;独立执行)
- https://github.com/web组件/客户-要素-功能 (激励;独立执行)
- https://shopify.dev/changelog/making-idempotency-monor-for-infor-instory-调整-和 -- refund-muutations 互联网档案馆的存檔,存档日期2013-12-22 (激励;独立执行)
- https://shopify.dev/changelog/online-store-script-tags-deprencation 互联网档案馆的存檔,存档日期2014-12-22 (激励;独立执行)
- https://shopify.dev/docs/api/admin-graphql/latest (中文(简体) ) (激励;独立执行)
- 页:1 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/动作/按钮 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/动作/按钮#财产-财产-财产-细节-类型 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/动作/可点击 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/feedback-和-status-indicators/spinner 互联网档案馆的存檔,存档日期2013-12-02 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/形式/投放区 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/形式/钱田 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/形式/switch (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/形式/文本-字段 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/layout-和-structure/page#slots (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/layout-and-structure/page#slots-slotdetail-aside 页面存档备份,存于互联网档案馆 互联网档案馆 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/重叠/模式 (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/覆盖/模式#slots (激励;独立执行)
- https://shopify.dev/docs/api/app-home/latest/web-组件/打字法-和-content/tooltip (激励;独立执行)
- https://shopify.dev/docs/api/shopify-app-react-router/latest/apis/billing 互联网档案馆的存檔,存档日期2014-12-02 (激励;独立执行)
- https://shopify.dev/docs/api/usage/ 执行-意识形态 (激励;独立执行)
- https://shopify.dev/docs/apps/building/函数/输入-输出 (激励;独立执行)
- https://shopify.dev/docs/apps/building/函数/programming-languages/javascript- for-函数 (激励;独立执行)
- https://shopify.dev/docs/apps/building/webhooks/订阅/https (激励;独立执行)
- https://shopify.dev/docs/apps/launch/被保护客户数据 (激励;独立执行)