diff --git a/docs-site/docs/rn/update.md b/docs-site/docs/rn/update.md index 2b4caf9..728d686 100644 --- a/docs-site/docs/rn/update.md +++ b/docs-site/docs/rn/update.md @@ -55,64 +55,34 @@ await UpdateSDK.openStore(appUpdate.appStoreUrl, appUpdate.marketUrl) --- -## 4. RN Bundle 热更新 +## 4. RN 插件更新 -### 4.1 注册插件 +插件更新以服务端返回的 release-set 为最小事务单位。一个 release-set 只包含目标 +app/buz 及其 Common 依赖;SDK 会统一完成签名、宿主身份、整包版本、原生基线、 +SHA-256 和最终依赖闭包校验。宿主不能分别下载后自行拼接版本。 -在插件 Bundle 的入口文件顶部注册插件元数据: +进入 buz 前自动检查并安装: ```ts -// plugin/index.ts -import { UpdateSDK } from '@xuqm/rn-update' -import meta from './plugin.json' - -UpdateSDK.registerPlugin(meta) +await UpdateSDK.checkAndInstallPlugin('home', { + onProgress(moduleId, progress) { + console.log(moduleId, progress.percent) + }, +}) ``` -`plugin.json` 示例: +需要由宿主展示确认弹窗时,检查与安装分开调用: -```json -{ - "moduleId": "home", - "version": "1.2.3" +```ts +const plan = await UpdateSDK.checkPluginRelease('home') +if (plan && (await showPluginUpdateDialog(plan))) { + await UpdateSDK.installPluginRelease(plan) } ``` -### 4.2 检查热更新 - -```ts -const rnUpdate = await UpdateSDK.checkRnUpdate('home') -if (rnUpdate.needsUpdate) { - console.log('最新版本:', rnUpdate.latestVersion) - console.log('下载地址:', rnUpdate.downloadUrl) - console.log('更新说明:', rnUpdate.note) - console.log('最低依赖版本:', rnUpdate.minCommonVersion) -} -``` - -### 4.3 下载并缓存 Bundle - -```ts -// 下载 Bundle 源码 -const source = await UpdateSDK.downloadRnBundle(rnUpdate.downloadUrl) - -// 缓存到本地 -await UpdateSDK.cacheRnBundle('home', rnUpdate.latestVersion, rnUpdate.md5, source) - -// 读取已缓存的 Bundle -const cached = await UpdateSDK.getCachedRnBundle('home') -if (cached) { - console.log('缓存版本:', cached.version) - console.log('缓存时间:', cached.downloadedAt) -} -``` - -### 4.4 获取已注册插件版本 - -```ts -const version = UpdateSDK.getRegisteredPluginVersion('home') -console.log('当前运行版本:', version) -``` +`checkPluginRelease` 只检查,不下载、不修改本地状态。安装阶段会再次验证签名和检查时 +记录的本地基线;计划已过期时必须重新检查。插件版本、路径和事务状态由原生层统一 +管理,宿主不得另建一套缓存版本表。 --- @@ -160,30 +130,32 @@ await XuqmSDK.login({ userId: 'user_001', userSig: 'your_user_sig_jwt' }) SDK 提供 `xuqm_release.mjs` 脚本,可一键完成 RN Bundle 构建并上传至 XuqmGroup 版本管理服务: ```bash -node scripts/xuqm_release.mjs --platform android --env production +XUQM_API_TOKEN=*** node scripts/xuqm_release.mjs --platform android --publish ``` 脚本执行流程: -1. 执行 `react-native bundle` 构建 JS Bundle -2. 自动提取模块 ID、版本号、更新日志 -3. 上传 Bundle 到 XuqmGroup 后台 -4. 返回发布结果 +1. 按模块构建 JS/Hermes Bundle 和 Source Map +2. 生成根目录唯一的 `rn-manifest.json` +3. 生成 `..xuqm.zip` +4. 上传归档并由服务端校验、签名 +5. 按用户选择发布,返回不可变发布记录 常用参数: | 参数 | 说明 | |------|------| | `--platform` | 平台:`android` / `ios` | -| `--env` | 环境:`production` / `staging` | -| `--moduleId` | 模块 ID(如 `home`)| -| `--forceUpdate` | 是否强制更新 | +| `--module` | 可重复传入,只构建或发布指定模块 | +| `--note` | 本次发布说明 | +| `--publish` | 上传后立即发布 | +| `--bugcollect` | `platform` 上传 Source Map;`disabled` 仅保留本地产物 | 在 `package.json` 中添加快捷命令: ```json { "scripts": { - "xuqm:release": "node scripts/xuqm_release.mjs --platform android --env production" + "xuqm:release": "node scripts/xuqm_release.mjs --platform android --publish" } } ``` diff --git a/tenant-platform/src/api/update.ts b/tenant-platform/src/api/update.ts index 929e255..ac93163 100644 --- a/tenant-platform/src/api/update.ts +++ b/tenant-platform/src/api/update.ts @@ -43,8 +43,7 @@ updateClient.interceptors.request.use((config) => { url.startsWith('/api/v1/updates/app/check') || url.startsWith('/api/v1/updates/app/inspect') || url.startsWith('/api/v1/updates/public/') || - url.startsWith('/api/v1/rn/update/check') || - url.startsWith('/api/v1/rn/inspect') || + url.startsWith('/api/v1/rn/release-set/check') || url.startsWith('/api/v1/rn/files/') ) if (!skipAuth) { @@ -186,12 +185,21 @@ export interface AppPackageInspectResult { export interface RnBundle { id: string appKey: string + packageName: string moduleId: string - platform: 'ANDROID' | 'IOS' | 'HARMONY' + type: 'common' | 'app' | 'buz' + platform: 'ANDROID' | 'IOS' version: string - md5: string - minCommonVersion?: string - packageName?: string + appVersionRange: string + builtAgainstNativeBaselineId: string + buildId: string + bundleFormat: string + commonVersionRange?: string + minNativeApiLevel: number + bundleSha256: string + archiveSha256: string + keyId: string + signature: string note?: string publishStatus: 'DRAFT' | 'PUBLISHED' | 'DEPRECATED' publishMode?: PublishMode @@ -203,16 +211,6 @@ export interface RnBundle { createdAt: string } -export interface RnBundleInspectResult { - moduleId?: string - platform?: 'ANDROID' | 'IOS' | 'HARMONY' - version?: string - minCommonVersion?: string - packageName?: string - fileName?: string - detected: boolean -} - export interface UnifiedAppUploadItem { fileKey: string platform: 'ANDROID' | 'IOS' | 'HARMONY' @@ -226,19 +224,8 @@ export interface UnifiedAppUploadItem { publishImmediately: boolean } -export interface UnifiedRnUploadItem { - fileKey: string - moduleId: string - platform: 'ANDROID' | 'IOS' | 'HARMONY' - version: string - minCommonVersion?: string - packageName?: string - note?: string -} - export interface UnifiedReleaseManifest { appVersions: UnifiedAppUploadItem[] - rnBundles: UnifiedRnUploadItem[] } export interface DownloadPageHistoryItem { @@ -342,10 +329,6 @@ export const updateAdminApi = { return updateClient.post<{ data: RnBundle }>('/api/v1/rn/upload', formData, uploadProgressConfig(onProgress)) }, - inspectRnBundle(formData: FormData, onProgress?: UploadProgressHandler) { - return updateClient.post<{ data: RnBundleInspectResult }>('/api/v1/rn/inspect', formData, uploadProgressConfig(onProgress)) - }, - uploadUnifiedRelease(formData: FormData, onProgress?: UploadProgressHandler) { return updateClient.post('/api/v1/updates/unified/upload', formData, uploadProgressConfig(onProgress)) }, diff --git a/tenant-platform/src/views/update/VersionManagementView.vue b/tenant-platform/src/views/update/VersionManagementView.vue index 8bb6d0c..c937b15 100644 --- a/tenant-platform/src/views/update/VersionManagementView.vue +++ b/tenant-platform/src/views/update/VersionManagementView.vue @@ -178,8 +178,18 @@
+ + + + + + + + + + - @@ -924,44 +933,25 @@ :auto-upload="false" :limit="1" :on-change="onRnBundleChange" - accept=".bundle,.js" + accept=".xuqm.zip" >
- 将 RN Bundle 拖到这里,或 点击选择文件 + 将 CLI 生成的 .xuqm.zip 发布包拖到这里,或 点击选择文件
- -
- - {{ rnInspectUploadProgress }}% -
-
- - - - - - - - - - - - -
@@ -981,7 +971,7 @@

释放文件以上传

-

支持 .apk、.bundle、.js

+

支持 .apk、.xuqm.zip

@@ -1005,7 +995,6 @@ import { type GrayTag, type PublishMode, type RnBundle, - type RnBundleInspectResult, type StoreConfig, type StoreType, } from '@/api/update' @@ -1132,12 +1121,12 @@ async function handleDrop(e: DragEvent) { openUploadAppDialog() await nextTick() await onAppPackageChange({ raw: file }) - } else if (ext === '.bundle' || ext === '.js') { + } else if (file.name.toLowerCase().endsWith('.xuqm.zip')) { showUploadRn.value = true await nextTick() await onRnBundleChange({ raw: file }) } else { - ElMessage.warning(`不支持的文件类型:${file.name},请拖入 .apk、.bundle 或 .js 文件`) + ElMessage.warning(`不支持的文件类型:${file.name},请拖入 .apk 或 .xuqm.zip 文件`) } } @@ -1167,7 +1156,6 @@ const appPackageInspecting = ref(false) const appPackageUploadProgress = ref(0) const appVersionUploadProgress = ref(0) const appUploadRef = ref(null) -const rnInspectUploadProgress = ref(0) const rnBundleUploadProgress = ref(0) const operationLogs = ref<{ id: string @@ -2155,81 +2143,28 @@ async function submitAppUpload() { const showUploadRn = ref(false) const uploadingRn = ref(false) const rnUploadForm = ref({ - moduleId: '', - platform: 'ANDROID' as 'ANDROID' | 'IOS' | 'HARMONY', - version: '', - minCommonVersion: '', - packageName: '', note: '', file: null as File | null, }) -function parseRnBundleName(fileName: string): RnBundleInspectResult | null { - const baseName = fileName.replace(/\.[^.]+$/, '') - const parts = baseName.split('__') - if (parts.length >= 4) { - return { - moduleId: parts[0], - platform: parts[1].toUpperCase() as 'ANDROID' | 'IOS' | 'HARMONY', - version: parts[2], - minCommonVersion: parts[3], - packageName: parts[4], - fileName, - detected: true, - } - } - return null -} - async function onRnBundleChange(uploadFile: { raw?: File } | null) { const file = uploadFile?.raw ?? null rnUploadForm.value.file = file if (!file) return - - const local = parseRnBundleName(file.name) - if (local) { - if (local.moduleId) rnUploadForm.value.moduleId = local.moduleId - if (local.platform) rnUploadForm.value.platform = local.platform - if (local.version) rnUploadForm.value.version = local.version - if (local.minCommonVersion) rnUploadForm.value.minCommonVersion = local.minCommonVersion - if (local.packageName) rnUploadForm.value.packageName = local.packageName - return - } - - const formData = new FormData() - formData.append('bundle', file) - rnInspectUploadProgress.value = 0 - try { - const res = await updateAdminApi.inspectRnBundle(formData, (percent) => { - rnInspectUploadProgress.value = percent - }) - rnInspectUploadProgress.value = 100 - const inspected = res.data.data as RnBundleInspectResult - if (inspected.moduleId) rnUploadForm.value.moduleId = inspected.moduleId - if (inspected.platform) rnUploadForm.value.platform = inspected.platform - if (inspected.version) rnUploadForm.value.version = inspected.version - if (inspected.minCommonVersion) rnUploadForm.value.minCommonVersion = inspected.minCommonVersion - if (inspected.packageName) rnUploadForm.value.packageName = inspected.packageName - } catch { - ElMessage.warning('已选择文件,但未能从文件名识别出 RN Bundle 元数据,请补全后上传') - } finally { - rnInspectUploadProgress.value = 0 + if (!file.name.toLowerCase().endsWith('.xuqm.zip')) { + rnUploadForm.value.file = null + ElMessage.warning('RN 插件只接受发布 CLI 生成的 .xuqm.zip') } } async function submitRnUpload() { const f = rnUploadForm.value - if (!f.moduleId || !f.version || !f.file) return ElMessage.warning('请填写模块ID、版本和 Bundle 文件') + if (!f.file) return ElMessage.warning('请选择 CLI 生成的 .xuqm.zip 发布包') uploadingRn.value = true rnBundleUploadProgress.value = 0 try { const fd = new FormData() fd.append('appKey', appKey.value) - fd.append('moduleId', f.moduleId) - fd.append('platform', f.platform) - fd.append('version', f.version) - if (f.minCommonVersion) fd.append('minCommonVersion', f.minCommonVersion) - if (f.packageName) fd.append('packageName', f.packageName) if (f.note) fd.append('note', f.note) fd.append('bundle', f.file) await updateAdminApi.uploadRnBundle(fd, (percent) => {