docs(sdk): 添加 React Native SDK 文档和 Android/HarmonyOS 发版脚本
- 新增 XuqmGroup React Native SDK 使用文档,包含安装、初始化、HTTP客户端、IM模块、推送模块、版本管理等功能说明 - 添加 Android Gradle 发版任务脚本,支持构建发布 APK 并上传到更新服务 - 添加 HarmonyOS hvigorw 发版任务脚本,支持 HAP 包构建和上传功能 - 实现多平台版本检查、自动重连、灰度发布等发版流程自动化 - 集成商店提交、定时发布、Webhook 回调等发布后处理功能
这个提交包含在:
父节点
2352b46c6b
当前提交
eaf1c00d20
@ -34,14 +34,19 @@ platform :ios do
|
|||||||
server_url = ENV.fetch("XUQM_SERVER_URL")
|
server_url = ENV.fetch("XUQM_SERVER_URL")
|
||||||
app_key = ENV.fetch("XUQM_APP_KEY")
|
app_key = ENV.fetch("XUQM_APP_KEY")
|
||||||
api_token = ENV.fetch("XUQM_API_TOKEN")
|
api_token = ENV.fetch("XUQM_API_TOKEN")
|
||||||
|
tenant_url = ENV.fetch("XUQM_TENANT_URL", "").strip
|
||||||
scheme = ENV.fetch("XUQM_SCHEME")
|
scheme = ENV.fetch("XUQM_SCHEME")
|
||||||
workspace = ENV.fetch("XUQM_WORKSPACE")
|
workspace = ENV.fetch("XUQM_WORKSPACE")
|
||||||
bundle_id = ENV.fetch("XUQM_BUNDLE_ID", "")
|
bundle_id = ENV.fetch("XUQM_BUNDLE_ID", "")
|
||||||
store_targets = ENV.fetch("XUQM_STORE_TARGETS", "")
|
tenant_cfg = xuqm_fetch_sdk_config(tenant_url, app_key, "IOS")
|
||||||
auto_publish = ENV.fetch("XUQM_AUTO_PUBLISH_AFTER_REVIEW", "false") == "true"
|
store_targets = ENV.fetch("XUQM_STORE_TARGETS", tenant_cfg.fetch("updateDefaultStoreTargets", "APP_STORE"))
|
||||||
publish_immediately = ENV.fetch("XUQM_PUBLISH_IMMEDIATELY", "false") == "true"
|
auto_publish = ENV.fetch("XUQM_AUTO_PUBLISH_AFTER_REVIEW", tenant_cfg.fetch("updateDefaultAutoPublishAfterReview", "false")) == "true"
|
||||||
webhook_url = ENV.fetch("XUQM_WEBHOOK_URL", "")
|
publish_immediately = ENV.fetch("XUQM_PUBLISH_IMMEDIATELY", tenant_cfg.fetch("updateDefaultPublishImmediately", "false")) == "true"
|
||||||
scheduled_at = ENV.fetch("XUQM_SCHEDULED_PUBLISH_AT", "")
|
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 ────────────────────────────────────────────
|
# ── 1. Read local version ────────────────────────────────────────────
|
||||||
version_name = get_version_number(target: scheme)
|
version_name = get_version_number(target: scheme)
|
||||||
@ -53,10 +58,24 @@ platform :ios do
|
|||||||
UI.message("Server latest versionCode: #{server_version_code}")
|
UI.message("Server latest versionCode: #{server_version_code}")
|
||||||
|
|
||||||
if version_code <= 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!(
|
UI.user_error!(
|
||||||
"Local versionCode (#{version_code}) must be greater than server (#{server_version_code}). " \
|
"Local versionCode (#{version_code}) must be greater than server (#{server_version_code}). " \
|
||||||
"Please bump CFBundleVersion before releasing."
|
"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
|
end
|
||||||
|
|
||||||
# ── 3. Archive & export IPA ──────────────────────────────────────────
|
# ── 3. Archive & export IPA ──────────────────────────────────────────
|
||||||
@ -76,8 +95,48 @@ platform :ios do
|
|||||||
UI.user_error!("IPA not found in #{ipa_output}") unless ipa_path
|
UI.user_error!("IPA not found in #{ipa_output}") unless ipa_path
|
||||||
UI.success("IPA: #{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 ────────────────────────────
|
# ── 4. Upload to XuqmGroup update service ────────────────────────────
|
||||||
change_log = prompt(text: "Release notes: ", ci_input: "")
|
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(
|
version_id = xuqm_upload_ipa(
|
||||||
server_url: server_url,
|
server_url: server_url,
|
||||||
app_key: app_key,
|
app_key: app_key,
|
||||||
@ -161,6 +220,20 @@ platform :ios do
|
|||||||
|
|
||||||
# ── Private helpers ──────────────────────────────────────────────────────
|
# ── 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|
|
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]}")
|
uri = URI("#{options[:server_url]}/api/v1/updates/app/list?appId=#{options[:app_key]}&platform=#{options[:platform]}")
|
||||||
req = Net::HTTP::Get.new(uri)
|
req = Net::HTTP::Get.new(uri)
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户