docs(sdk): 添加 React Native SDK 文档和 Android/HarmonyOS 发版脚本

- 新增 XuqmGroup React Native SDK 使用文档,包含安装、初始化、HTTP客户端、IM模块、推送模块、版本管理等功能说明
- 添加 Android Gradle 发版任务脚本,支持构建发布 APK 并上传到更新服务
- 添加 HarmonyOS hvigorw 发版任务脚本,支持 HAP 包构建和上传功能
- 实现多平台版本检查、自动重连、灰度发布等发版流程自动化
- 集成商店提交、定时发布、Webhook 回调等发布后处理功能
这个提交包含在:
XuqmGroup 2026-04-29 17:35:52 +08:00
父节点 2352b46c6b
当前提交 eaf1c00d20

查看文件

@ -34,14 +34,19 @@ platform :ios do
server_url = ENV.fetch("XUQM_SERVER_URL")
app_key = ENV.fetch("XUQM_APP_KEY")
api_token = ENV.fetch("XUQM_API_TOKEN")
tenant_url = ENV.fetch("XUQM_TENANT_URL", "").strip
scheme = ENV.fetch("XUQM_SCHEME")
workspace = ENV.fetch("XUQM_WORKSPACE")
bundle_id = ENV.fetch("XUQM_BUNDLE_ID", "")
store_targets = ENV.fetch("XUQM_STORE_TARGETS", "")
auto_publish = ENV.fetch("XUQM_AUTO_PUBLISH_AFTER_REVIEW", "false") == "true"
publish_immediately = ENV.fetch("XUQM_PUBLISH_IMMEDIATELY", "false") == "true"
webhook_url = ENV.fetch("XUQM_WEBHOOK_URL", "")
scheduled_at = ENV.fetch("XUQM_SCHEDULED_PUBLISH_AT", "")
tenant_cfg = xuqm_fetch_sdk_config(tenant_url, app_key, "IOS")
store_targets = ENV.fetch("XUQM_STORE_TARGETS", tenant_cfg.fetch("updateDefaultStoreTargets", "APP_STORE"))
auto_publish = ENV.fetch("XUQM_AUTO_PUBLISH_AFTER_REVIEW", tenant_cfg.fetch("updateDefaultAutoPublishAfterReview", "false")) == "true"
publish_immediately = ENV.fetch("XUQM_PUBLISH_IMMEDIATELY", tenant_cfg.fetch("updateDefaultPublishImmediately", "false")) == "true"
webhook_url = ENV.fetch("XUQM_WEBHOOK_URL", tenant_cfg.fetch("updateDefaultWebhookUrl", ""))
scheduled_at = ENV.fetch("XUQM_SCHEDULED_PUBLISH_AT", tenant_cfg.fetch("updateDefaultScheduledPublishAt", ""))
publish_mode = ENV.fetch("XUQM_PUBLISH_MODE", tenant_cfg.fetch("updateDefaultPublishMode", "")).to_s.strip.upcase
dry_run = ENV.fetch("XUQM_DRY_RUN", "false") == "true" || publish_mode == "DRY_RUN" || !tenant_cfg.fetch("updateEnabled", true)
allow_version_mismatch = ENV.fetch("XUQM_ALLOW_VERSION_MISMATCH", "false") == "true"
# ── 1. Read local version ────────────────────────────────────────────
version_name = get_version_number(target: scheme)
@ -53,10 +58,24 @@ platform :ios do
UI.message("Server latest versionCode: #{server_version_code}")
if version_code <= server_version_code
if !allow_version_mismatch
UI.message("Local version is not greater than server latest. Please enter corrected release version info.")
version_name_input = prompt(text: "Release version name [#{version_name}]: ", ci_input: "").to_s.strip
version_code_input = prompt(text: "Release version code [#{version_code}]: ", ci_input: "").to_s.strip
version_name = version_name_input unless version_name_input.empty?
version_code = version_code_input.to_i unless version_code_input.empty?
end
UI.user_error!(
"Local versionCode (#{version_code}) must be greater than server (#{server_version_code}). " \
"Please bump CFBundleVersion before releasing."
)
) if version_code <= server_version_code
end
if version_name != get_version_number(target: scheme)
increment_version_number(version_number: version_name)
end
if version_code != get_build_number.to_i
increment_build_number(build_number: version_code.to_s)
end
# ── 3. Archive & export IPA ──────────────────────────────────────────
@ -76,8 +95,48 @@ platform :ios do
UI.user_error!("IPA not found in #{ipa_output}") unless ipa_path
UI.success("IPA: #{ipa_path}")
if dry_run
UI.message("Dry-run summary:")
UI.message(" updateEnabled=#{tenant_cfg.fetch("updateEnabled", true)}")
UI.message(" releaseVersion=#{version_name} (#{version_code})")
UI.message(" publishMode=#{publish_mode.empty? ? "MANUAL" : publish_mode}")
UI.message(" publishImmediately=#{publish_immediately}")
UI.message(" autoPublishAfterReview=#{auto_publish}")
UI.message(" scheduledAt=#{scheduled_at.empty? ? "-" : scheduled_at}")
UI.message(" webhookUrl=#{webhook_url.empty? ? "-" : webhook_url}")
UI.message(" storeTargets=#{store_targets.empty? ? "-" : store_targets}")
UI.message("Dry-run completed. No upload performed.")
next
end
# ── 4. Upload to XuqmGroup update service ────────────────────────────
change_log = prompt(text: "Release notes: ", ci_input: "")
if publish_mode.empty?
publish_mode = if publish_immediately
"NOW"
elsif !scheduled_at.empty?
"SCHEDULED"
elsif auto_publish
"AUTO_REVIEW"
else
prompt(text: "Publish mode (MANUAL/NOW/SCHEDULED/AUTO_REVIEW) [MANUAL]: ", ci_input: "").to_s.strip.upcase
end
end
dry_run = true if publish_mode == "DRY_RUN"
if !scheduled_at.empty?
publish_immediately = false
auto_publish = false
end
if publish_mode == "SCHEDULED" && scheduled_at.empty?
scheduled_at = prompt(text: "Scheduled publish time (ISO datetime): ", ci_input: "").to_s.strip
end
if publish_mode == "NOW"
publish_immediately = true
auto_publish = false
elsif publish_mode == "AUTO_REVIEW"
auto_publish = true
publish_immediately = false
end
version_id = xuqm_upload_ipa(
server_url: server_url,
app_key: app_key,
@ -161,6 +220,20 @@ platform :ios do
# ── Private helpers ──────────────────────────────────────────────────────
private_lane :xuqm_fetch_sdk_config do |options|
tenant_url = options[:tenant_url].to_s.strip
return {} if tenant_url.empty?
uri = URI("#{tenant_url}/api/sdk/config?appId=#{options[:app_key]}&platform=#{options[:platform]}")
req = Net::HTTP::Get.new(uri)
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |h| h.request(req) }
body = JSON.parse(resp.body)
body["data"] || {}
rescue => e
UI.message("Failed to load tenant defaults: #{e.message}")
{}
end
private_lane :xuqm_get_latest_version_code do |options|
uri = URI("#{options[:server_url]}/api/v1/updates/app/list?appId=#{options[:app_key]}&platform=#{options[:platform]}")
req = Net::HTTP::Get.new(uri)