mcpField
A
add-native

add-native

github.com/microsoft开发1.1k131 次安装今天更新
代码仓库

关于 add-native

用于为 Power Apps Wrap 移动应用添加受支持原生功能的技能。它会检查已安装的包,并将特定流程交由专用辅助技能处理;其他符合条件的功能则生成类型化封装。

主要功能

  1. 01检查原生包及运行时限制
  2. 02在 src/native/ 生成类型化封装
  3. 03提供相机、PDF、笔迹和地理位置专用流程
  4. 04说明 Dataverse 文件和图像列的宿主控件用法

适用场景

  1. 01为移动应用添加符合条件的原生功能
  2. 02实现相机、PDF 或签名流程
  3. 03正确处理 Dataverse 文件或图像字段

安装

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

$npx skills add https://github.com/microsoft/power-platform-skills --skill add-native

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

详细介绍

插件检查:运行 node "${PLUGIN_ROOT}/scripts/check-version.js" —— 如果输出了消息,请在继续之前将其显示给用户。

📋 共享说明:shared-instructions.md —— 请先阅读。

工作目录: 在读取任何项目内容或运行任何命令之前,执行 native-artifact-compatibility.md 第 0 步。 在此处解析 --working-dir / 所有者上下文;对每次 shell 调用和文件工具调用,都使用解析出的绝对根目录,而不要使用之前某个 shell 的当前工作目录。

添加原生功能

入口路由: 请先按照下文工作流中的要求,使用共享的 App 功能入口点 进行预检。

此叶级流程会在 src/native/ 下为上游模板已提供的原生功能生成类型化封装。屏幕应导入封装,而不是直接调用 Expo 模块,从而确保判别联合结果契约保持一致。

严格规则 — 不得违反

  1. 绝不要为原生模块运行 npx expo install、npm install 或 yarn add。 package.json 中的原生模块集合由上游模板固定。添加新模块会破坏重新封装流程(客户的二进制文件是基于预构建的基础版本构建的,而不是根据其 package.json 构建的)。
  2. 绝不要编辑 app.config.js —— 包括插件、ios.infoPlist、android.permissions 或其他任何内容。模板提供的所有原生配置都有其设计意图并已获批准;在重新封装时无法接受任意添加的配置。
  3. 绝不要修改 package.json 中的原生模块 dependencies。 原生模块是指包含平台源代码/项目、podspec、codegen、Expo 模块/配置插件或 react-native.config.js 的包;仅凭包名的前缀无法证明它是原生模块。纯 JavaScript 依赖不属于 /add-native 的范围,应由 /create-mobile-app 或 /edit-app 根据已批准的 JavaScript Dependencies 计划安装。
  4. 如果 package.json 中实际上没有所请求的模块 — 停止。 这意味着上游模板尚未提供该模块;不要通过安装来绕过限制。

路由 — /add-native 是公开入口

某些功能有专用的实现辅助工具,能够完成超出普通封装的工作(camera 会写入扫描器/上传辅助逻辑;PDF 报告/查看器辅助工具会强制执行本地与 HTTPS 边界;pen 会进行针对原生控件的验证)。用户仍应对原生控件调用 /add-native <capability>。如果存在专用实现,请在内部运行,不要让用户直接运行该辅助工具。

**查找约定:**规范化功能名称后,首先检查下方的内部辅助工具映射。对于 camera、image-picker、barcode-scanner 和 qr-scanner,在此次 /add-native 调用中读取并执行 ${PLUGIN_ROOT}/skills/add-native/add-camera/SKILL.md。对于 pdf-report,读取并执行 ${PLUGIN_ROOT}/skills/add-native/add-pdf-report/SKILL.md。对于 pdf-viewer,读取并执行 ${PLUGIN_ROOT}/skills/add-native/add-pdf-viewer/SKILL.md。对于 pen-input,读取并执行 ${PLUGIN_ROOT}/skills/add-native/add-pen-input/SKILL.md。对于 geolocation,读取并执行 ${PLUGIN_ROOT}/skills/add-native/add-geolocation/SKILL.md。如果没有辅助工具,则继续执行本技能的内联封装流程。

当前专用实现:

功能专用技能专用原因
camera、take-photo、photo、expo-camera、image-picker、gallery、expo-image-picker、barcode-scanner、qr-scanner、scanneradd-camera 内部辅助工具负责照片拍摄、图库图片选择,以及由 expo-camera / expo-image-picker 支持的条码/QR 扫描器控件
pdf-report、pdf-export、generate-pdf、print-report、evidence-packetadd-pdf-report 内部辅助工具使用 expo-print 生成应用自有的本地 PDF,并且仅在存在 expo-sharing 时共享
pdf-viewer、native-pdf-viewer、pdf-control、open-pdf、@microsoft/power-apps-native-pdf-vieweradd-pdf-viewer 内部辅助工具强制要求查看器输入为 https:// / file://,并处理原生查看器结果
pen-input、signature、ink、draw、@microsoft/power-apps-native-pen-inputadd-pen-input 内部辅助工具捕获 PNG 数据 URI,并说明 Dataverse Image/File 持久化
geolocation、location-tracking、background-location、gps-tracking、geo-tracking、@microsoft/power-apps-native-bglocationadd-geolocation 内部辅助工具原生后台 GPS 跟踪,支持持久化存储和内联 Dataverse 同步;不同于一次性 expo-location

对于下方列出的其他所有功能,本技能会直接编写封装。

原生功能准入门槛

在添加任何原生控件或封装之前,必须通过所有准入门槛:对意图进行分类,根据实时的 package.json 确定确切的包或控件,确认其未被运行时禁用,确认输入、输出和存储限制,然后使用下方对应的路径。如果任何一项未通过,则此应用版本不支持该原生功能——不要安装包、编辑原生配置或创建虚假封装。

用户意图添加/使用所需包或控件不要使用/回退方案
绑定到 Dataverse File 列的表单字段在屏幕 JSX 中托管 <FilePicker>@microsoft/power-apps-native-host 托管控件不要为此字段生成文档选择器/文件系统/共享封装
绑定到 Dataverse Image 列的表单字段在屏幕 JSX 中托管 <ImagePicker>@microsoft/power-apps-native-host 托管控件对常规表单绑定的图像字段,不要使用相机/图像选择器封装
专用照片/图库/扫描器工作流/add-native camera、image-picker 或 barcode-scanner照片/图库使用 expo-image-picker;扫描器使用 expo-camera;组合请求需同时使用两者如果缺少相关包,请停止并说明所需包
选择/导入/上传用户选取的 PDF/文档/add-native document-picker;Dataverse File 字段使用托管的 <FilePicker>已安装 expo-document-picker,或使用托管的 File 控件不要将此功能当作 pdf-report 或原生 PDF 查看器
生成/导出/打印应用自有的报告 PDF/add-native pdf-report已安装 expo-print如果未安装 expo-print,不要添加 PDF 报告功能
共享已生成的本地 PDFpdfReport.ts 共享辅助程序已安装 expo-sharing如果未安装 expo-sharing,不要呈现共享界面
打开/预览 HTTPS 或本地文件 PDF/add-native pdf-viewer已安装 @microsoft/power-apps-native-pdf-viewer 0.2.9+,且输入为 https:// 或 file://不要将 content://、blob: 或 http:// URI 传给查看器
捕获签名、墨迹、绘图或签字确认/add-native pen-input已安装 @microsoft/power-apps-native-pen-input如果需要持久化,请先规划 Dataverse Image/File/子 Evidence 存储目标
持续/后台 GPS 跟踪并可靠上传到 Dataverse/add-native geolocation已安装 @microsoft/power-apps-native-bglocation后台跟踪不要使用一次性的 expo-location;不要使用 GeolocationExtension/HostingSDK 路径
按压、选择和操作结果的触觉反馈/add-native haptics已安装 expo-haptics使用类型化封装,并为每次触觉反馈配套可见的 UI 反馈
存储生成的 PDF/签名文件父行存在后使用生成的 Dataverse 服务存在 File/Image 列或子 Evidence/Attachment 表绝不要将 File 字节放入创建/更新 JSON
本表未列出的原生功能先根据 package.json 确定,然后仅在存在对应包且未被运行时禁用时添加内联封装package.json 中存在确切的相关包如果不存在相关包,或该包被运行时禁用,请添加说明情况的提示并停止

对多部分请求逐行处理。例如:“捕获签名并附上已签名的报告 PDF”需要 pen-input、pdf-report、Dataverse 文件存储,可能还需要 sharing;不要只添加原生功能,却不定义存储或屏幕状态。本表并非封闭清单:对于新发布的包,应根据功能语义确定用途,在安全的前提下使用直接匹配的包;仅当多个已安装的包都可能匹配时,才询问一次。

文件/图像选择器归属

此技能负责原生文件能力机制。屏幕构建器只负责最终的 JSX 放置位置。

有两种不同的情况:

情况正确实现归属
Dataverse File 列使用 @microsoft/power-apps-native-host 中的 <FilePicker>屏幕构建器 JSX 规则,本文档对此有说明
Dataverse Image 列使用 @microsoft/power-apps-native-host 中的 <ImagePicker>屏幕构建器 JSX 规则,本文档对此有说明
未绑定到 Dataverse 列的本地设备文档/文件工作流生成/使用 src/native/documentPicker.ts、src/native/fileSystem.ts 和/或 src/native/sharing.ts 包装器/add-native
应用生成的 PDF 报告工作流/add-native 在内部路由到 add-pdf-report;仅在存在时使用 expo-print,并可选使用 expo-sharing/add-native
相机拍摄、图库图像选择、条码扫描器或 QR 扫描器工作流/add-native 在内部路由到 add-camera/add-native

Dataverse File/Image 列使用宿主控件,而非原始 Expo 模块或应用专用原生包装器。 当表单字段绑定到 Dataverse File 列(FileAttributeMetadata)时,请使用 @microsoft/power-apps-native-host 中的 <FilePicker>。当字段绑定到 Dataverse Image 列(ImageAttributeMetadata)时,请使用 @microsoft/power-apps-native-host 中的 <ImagePicker>。这些控件会从 PowerAppsProvider / ThemeProvider 读取强调色、表面颜色和文本颜色,并生成兼容 Dataverse 的有效负载。

import { FilePicker, ImagePicker } from '@microsoft/power-apps-native-host';
import type { PickedFileInfo, PickedImageInfo } from '@microsoft/power-apps-native-host';

// Image column — seed preview from Dataverse bytes/base64 and capture upload-ready payload.
<ImagePicker
  label="Site Photo"
  initialBase64={imageBase64}
  onImageChange={(image: PickedImageInfo | null) => { /* store image for upload */ }}
/>

// File column — edit-only pick/replace.
<FilePicker
  label="Specification Sheet"
  fileName={record.cr123_specsheet_name}
  onChange={(file: PickedFileInfo) => { /* store file for save */ }}
/>

// File column — view + replace.
<FilePicker
  label="Specification Sheet"
  fileName={record.cr123_specsheet_name}
  onDownload={() => Cr123_inspectionService.downloadFile(id, 'cr123_specsheet')}
  onChange={(file: PickedFileInfo) => { /* store file for save */ }}
/>

宿主选择器绑定的 Dataverse 列的保存/读取模式:

// FILE/IMAGE columns: do not include picker bytes in the PATCH body.
// Save normal fields first, then upload through generated service helpers.
await Cr123_inspectionService.update(id, { cr123_name: name.trim() });
await Cr123_inspectionService.upload(id, 'cr123_specsheet', file.file, file.name);
await Cr123_inspectionService.upload(id, 'cr123_sitephoto', image.file, image.name);

// Download helpers are used for read/view scenarios.
const fileBytes = await Cr123_inspectionService.downloadFile(id, 'cr123_specsheet');
const imageBytes = await Cr123_inspectionService.downloadImage(id, 'cr123_sitephoto');

使用宿主 ImagePicker / FilePicker,并通过生成的 upload(...) 进行持久化,通过 downloadFile(...) / downloadImage(...) 进行读取/查看。请勿使用 Service.update(...{ <imageColumn>: base64 }) 持久化由选择器驱动的图像/文件。

请勿使用原始的 expo-document-picker、expo-image-picker、expo-file-system 或 expo-sharing 为 Dataverse File/Image 列构建自定义 UI。宿主 FilePicker 和 ImagePicker 已处理权限、读取、共享/下载功能以及兼容 Dataverse 的有效负载格式。

仅当工作流不涉及 Dataverse File/Image 列时,才使用 /add-native 包装器,例如导入本地 PDF 进行离线解析、导出生成的报告、保存本地草稿材料包,或打开共享面板以分享应用生成的摘要。

支持的能力

应用上述原生能力门控。此表是已知的能力与软件包映射,并不保证每个列出的软件包都存在于每个模板版本中。

能力模块要生成的封装备注
camera, take-photo, photo, expo-cameraexpo-image-pickersrc/native/camera.ts/add-native 会在内部将 photo 工件路由到 add-camera;expo-camera 是能力别名,而非必需的软件包
image-picker, gallery, expo-image-pickerexpo-image-pickersrc/native/camera.ts (pickImage)/add-native 会在内部路由到 add-camera;保留兼容的现有重新导出项
barcode-scanner, qr-scanner, scanner, barcode, qrexpo-camerasrc/native/barcodeScanner.tsx/add-native 会在内部路由到 add-camera
document-pickerexpo-document-pickersrc/native/documentPicker.ts从设备中选择/导入用户选定的文件(PDF、文档等)
pdf-viewer, native-pdf-viewer, pdf-control, open-pdf, @microsoft/power-apps-native-pdf-viewer@microsoft/power-apps-native-pdf-viewersrc/native/pdfViewer.ts/add-native 会在内部路由到 add-pdf-viewer;0.2.9+ 支持打开 HTTPS URL 和文件 URI
pdf-report, pdf-export, generate-pdf, print-report, evidence-packetexpo-print(+ 可选的 expo-sharing)src/native/pdfReport.ts/add-native 会在内部路由到 add-pdf-report;仅当存在 expo-sharing 时才会共享生成的本地文件,否则会将其上传到 Dataverse
pen-input, signature, ink, draw, @microsoft/power-apps-native-pen-input@microsoft/power-apps-native-pen-inputsrc/native/penInput.ts/add-native 会在内部路由到 add-pen-input;捕获 PNG 数据 URI
geolocation, location-tracking, background-location, gps-tracking, geo-tracking, @microsoft/power-apps-native-bglocation@microsoft/power-apps-native-bglocationsrc/native/geolocation.ts/add-native 会在内部路由到 add-geolocation;原生后台跟踪 + 持久化 Dataverse 同步。与下方的一次性 location 不同
secure-storeexpo-secure-storesrc/native/secureStore.ts
file-systemexpo-file-systemsrc/native/fileSystem.ts
sharingexpo-sharingsrc/native/sharing.ts
locationexpo-locationsrc/native/location.ts仅用于一次性/前台定位。若要进行持续的后台跟踪并同步到 Dataverse,请使用 geolocation(@microsoft/power-apps-native-bglocation)。仅当当前模板包包含 expo-location 时使用
biometrics, local-authenticationexpo-local-authenticationsrc/native/biometrics.ts仅当当前模板包包含 expo-local-authentication 时使用
clipboardexpo-clipboardsrc/native/clipboard.ts仅当当前模板包包含 expo-clipboard 时使用
mail-composer, email-draftexpo-mail-composersrc/native/mailComposer.ts如果存在该软件包,则打开原生邮件撰写界面;服务器端邮件发送仍由连接器负责
audioexpo-audiosrc/native/audio.ts仅当存在该软件包时用于音频录制/播放
videoexpo-videosrc/native/video.ts仅当存在该软件包时用于视频播放
sensorsexpo-sensorssrc/native/sensors.ts仅用于已安装软件包所提供的传感器 API
screen-orientationexpo-screen-orientationsrc/native/screenOrientation.ts仅当存在该软件包时使用;不要编辑原生配置
haptics, vibration-feedback, impact-feedback, selection-feedback, notification-feedbackexpo-hapticssrc/native/haptics.ts提供冲击、选择以及成功/警告/错误通知反馈
device-infoexpo-device / expo-application / expo-cellularsrc/native/deviceInfo.ts只读设备/应用/蜂窝网络元数据封装
date-time-picker@react-native-community/datetimepicker屏幕级组件用法根据屏幕构建器规则,直接在表单屏幕中使用;无需 /add-native 封装
对于 Dataverse 文件/图像表单字段之外的自定义工作流,用户选择照片和视频时,通过 /add-native image-picker 使用 image-picker 功能;选择文档和其他文件时,通过 /add-native document-picker 使用 document-picker 功能。这些封装使用受限的系统选择器流程。Dataverse 文件/图像表单字段仍是例外:应按上文所述使用宿主提供的 <FilePicker> / <ImagePicker> 控件,而不是 /add-native 封装或直接导入 Expo 模块。
PDF / 笔输入路由规则
  • 不要将所有 PDF 请求都视为 document-picker。
  • 用户要选择、导入或上传本地 PDF/文档时,使用 document-picker。此功能仍受支持,且在此用例中仍应使用。
  • 应用根据记录、证据、检查数据、证书、收据或报告生成 PDF 时,使用 pdf-report,但前提是 package.json 中存在 expo-print。
  • 应用使用 @microsoft/power-apps-native-pdf-viewer 0.2.9+ 打开/预览 HTTPS PDF URL 或本地 file:// URI 时,使用 native-pdf-viewer / pdf-control。不支持 content://、blob: 和 http:// URI。
  • 如果请求是“查看/打开 PDF”,但缺少 Power Apps 查看器包,则仅当应用要生成自己的报告且存在 expo-print 时,才回退到 pdf-report。不要声称 expo-print 支持通用 PDF 查看;它生成本地文件,并不查看任意 PDF。
  • 仅在使用 @microsoft/power-apps-native-pen-input 进行签名、绘制审批、墨迹笔记、草图和手写签收时,使用 pen-input。
  • 对于其他用例,使用 package.json 中已有的相关 Expo 模块或其他依赖;不要在无关工作流中强行使用 Power Apps 扩展。
  • 对于通过 expo-print 生成的本地 PDF,使用 native PDF viewer 0.2.9+ 打开/预览,使用 expo-sharing 分享,并使用 Dataverse 文件存储进行保留。仅预览本地 PDF 时,不要要求使用 expo-sharing。
  • 对于用户选择的 Dataverse 文件/图像表单字段,宿主提供的 FilePicker 和 ImagePicker 仍是正确选择。生成的 PDF 和笔输入捕获内容应优先使用原生封装,然后再使用 Dataverse 持久化辅助工具。
Dataverse 构件持久化规则
  • 笔输入会返回类似 data:image/png;base64,... 的 PNG 数据 URI。
  • 对于 Dataverse 图像列,将数据 URI 规范化为生成的服务负载格式。如果需要原始 base64,请去掉 data:image/png;base64, 前缀。
  • 对于 Dataverse 文件列,将生成的 PDF 或签名 PNG 转换为上传字节/File,先保存或更新父行,然后通过生成的服务辅助工具上传。
  • 切勿将文件列字节放入创建/更新 JSON 请求体。只有在父行 ID 存在后,才能上传文件字节。
  • 屏幕必须明确处理不支持、已取消、上传失败和查看器失败等状态。笔输入取消属于非错误结果,屏幕可以忽略。

缺少或受限的包:package.json 和运行时门控是权威依据。如果缺少相关包/控件,请停止并说明情况。

工作流

  1. 验证项目 → 2. 确定功能 → 3. 如果存在专用技能,则自动路由至该技能 → 4. 验证模块是否随模板提供 → 5. 编写封装 → 6. 类型检查 → 7. 总结

步骤 1 — 验证项目
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
test -f app.config.js && test -f power.config.json && test -f package.json || { echo "BLOCKED: working_dir is not an initialized app" >&2; exit 1; }
第 2 步 — 解析功能

遥测检查点:resolve_native_capability

如果 $ARGUMENTS 包含功能名称、包名称或控件名称,请使用该名称。否则,查找 native-app-plan.md 中的 ## Native Capabilities 部分,并列出计划中的功能供用户确认。如果两者都不存在,请向用户展示上文支持的功能列表,以及 package.json 中与其请求直接匹配的任何相关已安装包。

将功能名称规范化为小写、以连字符分隔的形式(例如,Camera → camera、ImagePicker → image-picker、SecureStore → secure-store)。同时规范化别名:take-photo / photo / camera-control / expo-camera → camera;gallery / pick-image / expo-image-picker → image-picker;scanner / barcode / qr → barcode-scanner;open-pdf / view-pdf / pdf-control / pdf-viewer-control / @microsoft/power-apps-native-pdf-viewer → pdf-viewer;native-pdf-viewer → pdf-viewer;generate-pdf / pdf-export → pdf-report;signature / sign-off / ink / draw / pen-control / @microsoft/power-apps-native-pen-input → pen-input;location-tracking / background-location / gps-tracking / geo-tracking / track-location / power-apps-native-bglocation / @microsoft/power-apps-native-bglocation → geolocation;vibration / vibration-feedback / impact-feedback / selection-feedback / notification-feedback / expo-haptics → haptics。

当用户提出“location”或“GPS”时,请根据意图消除歧义:持续/后台跟踪或上传到持久化的 Dataverse → geolocation(@microsoft/power-apps-native-bglocation);单次前台坐标读取 → location(expo-location)。如果意图不明确,请先询问一次,再进行路由。

如果用户指定的内容不在支持表格中,请应用 Native capability gate:从 package.json 中解析相关包,仅当该包存在且未被运行时禁止时才继续;否则,请停止并说明情况。

第 3 步 — 路由到嵌套辅助程序或内联封装

遥测检查点:dispatch_native_capability

对于规范化后的 camera、image-picker、barcode-scanner、qr-scanner、pdf-report、pdf-viewer、pen-input 或 geolocation,不要继续执行通用封装流程,也不要让用户运行另一个斜杠命令。读取嵌套辅助程序,并在本次 /add-native 调用中遵循其步骤:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
case "<capability>" in
  camera|image-picker|barcode-scanner|qr-scanner) test -f "${PLUGIN_ROOT}/skills/add-native/add-camera/SKILL.md" && echo "INTERNAL_HELPER:add-camera" ;;
  pdf-report) test -f "${PLUGIN_ROOT}/skills/add-native/add-pdf-report/SKILL.md" && echo "INTERNAL_HELPER:add-pdf-report" ;;
  pdf-viewer) test -f "${PLUGIN_ROOT}/skills/add-native/add-pdf-viewer/SKILL.md" && echo "INTERNAL_HELPER:add-pdf-viewer" ;;
  pen-input) test -f "${PLUGIN_ROOT}/skills/add-native/add-pen-input/SKILL.md" && echo "INTERNAL_HELPER:add-pen-input" ;;
  geolocation) test -f "${PLUGIN_ROOT}/skills/add-native/add-geolocation/SKILL.md" && echo "INTERNAL_HELPER:add-geolocation" ;;
  *) echo "INLINE" ;;
esac
  • **INTERNAL_HELPER:**读取打印出的辅助程序文件,并使用相同的绝对 working_dir、参数、用户提供的回答、模式标志以及当前的所有者/阶段/approved_scope 执行其工作流。即使从其他目录启动,每个辅助程序也必须通过步骤 0 将所有操作绑定到该根目录。每个辅助程序都必须在写入/复用之前以及成功摘要之前执行 native-artifact-compatibility.md;此分支不会到达外层步骤 5。将 NEEDS_CONTEXT/BLOCKED 和制品结果传递给所有者,然后停止当前叶级流程,而非停止所属工作流。不要继续执行内联生成,也不要将辅助程序的不完整结果报告为成功。/add-native 仍是这些控件唯一面向用户的命令。
  • **INLINE:**继续执行步骤 4。
第 4 步 — 验证模块是否随模板提供

确认底层原生功能包确实存在于项目的 package.json 中(以捕获用户手动移除该包,或模板版本早于预期的情况):

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node -e "const p = require('./package.json'); const m = '<expo-module-name>'; if (!p.dependencies?.[m]) { console.error('MISSING: ' + m + ' is not in package.json. The template should ship it. Re-scaffold via /create-mobile-app, restore it from upstream, or wait for the template release that adds it — this skill will not install it.'); process.exit(1); }"

如果检查失败,请停止。不要运行 npx expo install。逐字打印错误信息。

第 5 步 — 编写封装

遥测检查点:generate_native_wrapper

开始前打印:

"→ 正在编写 src/native/.ts(带判别联合结果和 iOS/Android 平台保护的类型化封装)…"

读取并执行 native-artifact-compatibility.md 中针对内联能力行的步骤 1–3,然后再编写或复用输出文件。 仅当 src/native/<wrapper-filename>.ts 文件不存在且属于范围内时才创建。如果 文件已存在,请检查其导出是否符合已批准的能力契约,包括行为和存储。兼容时原样 复用;若已获批准,则无需再次询问,进行范围内更新。否则,在更新前向负责人返回 NEEDS_CONTEXT(或在独立场景中询问);绝不能悄悄跳过该功能或覆盖自定义代码。

每个封装导出:

  • request<Capability>Permission(): Promise<boolean> — 仅当底层 API 具有权限模型时提供(位置、生物识别、通知)。不适用的能力(安全存储、文件系统、分享、剪贴板)请跳过。
  • 一个或两个返回类型化结果的领域方法。

screen-builder Agent 所依赖的契约:

  • 所有封装都返回判别联合结果({ ok: true, ... } | { ok: false, reason, message? })——绝不抛出异常
  • 不支持的运行时/平台状态应优雅降级,或返回 { ok: false, reason: 'unsupported' }——绝不崩溃
  • 当某项能力在 iOS 和 Android 上存在差异时,按受支持的原生平台分支处理
  • 屏幕仅在非 Dataverse 原生工作流中导入这些封装。Dataverse 的 File/Image 字段使用上文 File/Image Picker Ownership 一节中的 @microsoft/power-apps-native-host 控件。
触觉反馈封装

对于规范化能力 haptics,按照以下契约生成 src/native/haptics.ts:

import * as Haptics from 'expo-haptics';
import { Platform } from 'react-native';

export type HapticImpactStyle = 'light' | 'medium' | 'heavy' | 'soft' | 'rigid';
export type HapticNotificationType = 'success' | 'warning' | 'error';
export type HapticResult =
  | { ok: true }
  | { ok: false; reason: 'unsupported' | 'error'; message: string };

const impactStyles: Record<HapticImpactStyle, Haptics.ImpactFeedbackStyle> = {
  light: Haptics.ImpactFeedbackStyle.Light,
  medium: Haptics.ImpactFeedbackStyle.Medium,
  heavy: Haptics.ImpactFeedbackStyle.Heavy,
  soft: Haptics.ImpactFeedbackStyle.Soft,
  rigid: Haptics.ImpactFeedbackStyle.Rigid,
};

const notificationTypes: Record<HapticNotificationType, Haptics.NotificationFeedbackType> = {
  success: Haptics.NotificationFeedbackType.Success,
  warning: Haptics.NotificationFeedbackType.Warning,
  error: Haptics.NotificationFeedbackType.Error,
};

async function runHaptic(effect: () => Promise<void>): Promise<HapticResult> {
  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
    return {
      ok: false,
      reason: 'unsupported',
      message: 'Haptic feedback is available only on iOS and Android.',
    };
  }

  try {
    await effect();
    return { ok: true };
  } catch (error) {
    return {
      ok: false,
      reason: 'error',
      message: error instanceof Error ? error.message : 'The native haptic call failed.',
    };
  }
}

export function triggerImpact(style: HapticImpactStyle = 'medium'): Promise<HapticResult> {
  return runHaptic(() => Haptics.impactAsync(impactStyles[style]));
}

export function triggerNotification(
  type: HapticNotificationType = 'success'
): Promise<HapticResult> {
  return runHaptic(() => Haptics.notificationAsync(notificationTypes[type]));
}

export function triggerSelection(): Promise<HapticResult> {
  return runHaptic(() => Haptics.selectionAsync());
}

只有当触觉反馈出现在已批准的 native-capabilities 和逐屏计划中时,屏幕才调用此封装。根据语义选择反馈:

  • triggerImpact('light' | 'medium' | 'heavy' | 'soft' | 'rigid') 用于有意的按压或具有实体触感的操作。
  • triggerSelection() 用于选择器、分段控件或其他选择项发生变化时。
  • 在确认相应结果后,调用 triggerNotification('success' | 'warning' | 'error')。
  • 视觉反馈仍应是主要信号。检查非 OK 结果并记录日志,但不要因为触觉反馈不可用,就让原本成功的业务操作失败。

操作成功后在屏幕中的用法:

setSaveState({ kind: 'success', message: 'Saved' });
const hapticResult = await triggerNotification('success');
if (!hapticResult.ok) {
  console.warn('[haptics] success feedback unavailable', hapticResult);
}

expo-haptics 无需请求权限,也无需更改 app.config.js。

其他内联封装

请查阅模块发布的 API 文档(链接位于其 npm 页面),了解方法签名和权限模式。使用以下安全存储骨架作为判别联合结构的规范示例,然后将其转换为目标模块的 API。

安全存储规范骨架:

// src/native/secureStore.ts
import * as SecureStore from 'expo-secure-store';
import { Platform } from 'react-native';

export type SecureResult<T> =
  | { ok: true; value: T }
  | { ok: false; reason: 'unsupported' | 'not-found' | 'error'; message?: string };

export async function getSecret(key: string): Promise<SecureResult<string>> {
  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
    return { ok: false, reason: 'unsupported', message: 'SecureStore is not available on this platform.' };
  }
  try {
    const value = await SecureStore.getItemAsync(key);
    if (value === null) return { ok: false, reason: 'not-found' };
    return { ok: true, value };
  } catch (e: any) {
    return { ok: false, reason: 'error', message: e?.message };
  }
}

export async function setSecret(key: string, value: string): Promise<SecureResult<true>> {
  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
    return { ok: false, reason: 'unsupported', message: 'SecureStore is not available on this platform.' };
  }
  try {
    await SecureStore.setItemAsync(key, value);
    return { ok: true, value: true };
  } catch (e: any) {
    return { ok: false, reason: 'error', message: e?.message };
  }
}
第 6 步 — 类型检查

遥测检查点:validate_native_wrapper

开始前打印:

"→ 正在运行 tsc,以验证封装可编译(约 10–20 秒)。"

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
npx --no-install tsc --noEmit

仅修复范围内的封装侧错误。类型检查后,执行共享兼容性契约的 第 4 步:重新检查所有请求的产物、API/行为和存储义务,然后再返回成功结果。仅 类型检查成功并不足够。正常使用 /add-native 时不要运行平台专属的原生构建 命令,因为该模块已包含在当前模板二进制文件中。模板/基础版本维护者首次添加 expo-haptics 或更改其版本时,必须重新构建 Android 和 iOS 基础二进制文件, 以便 Expo 自动链接包含该模块。

第 7 步 — 总结

在总结之前返回共享兼容性结果;使用实际创建/更新/复用的路径,不要声称已完成尚未解决的产物。

✅ Native wrapper generated: <capability>
─────────────────────────────────────────────
Module (template-shipped) : <expo-module>@<version-from-package.json>
Wrapper created           : src/native/<capability>.ts
package.json              : unchanged ✓
app.config.js             : unchanged ✓

Type-check: PASS

Sample usage:

  import { takePhoto } from '../../src/native/camera';

  const result = await takePhoto();
  if (result.ok) {
    setPhotoUri(result.uri);
  } else if (result.reason === 'permission-denied') {
    showToast('Camera permission required');
  }

⚠️  No per-app native rebuild required when using a binary produced from the current
    template. The underlying native module is already linked; wrapper edits are picked
    up by Metro. Template/base dependency changes require a new native base build.
─────────────────────────────────────────────

备注

  • 此技能不会修改 package.json、app.config.js、src/playerConfig.ts、src/generated/ 或任何屏幕文件。
  • 对于支持表中未列出的功能(expo-notifications、Bluetooth、NFC、BLE、AR——模板添加这些功能前),请告知用户模板目前尚未提供这些功能,并在上游模板仓库中提交需求。不要尝试自行安装或配置。
  • 纯 JavaScript 库不在此技能的适用范围内。/create-mobile-app 或 /edit-app 会通过 shared/references/javascript-dependency-planning.md 选择并安装这些库;无需原生封装,也无需重新构建 Android/iOS。上述禁令仅适用于包含原生源码或配置的包。

评论

支持 Markdown