diff --git a/docs/IMPLEMENTATION_HANDOFF.md b/docs/IMPLEMENTATION_HANDOFF.md index 281ee84..9256cf6 100644 --- a/docs/IMPLEMENTATION_HANDOFF.md +++ b/docs/IMPLEMENTATION_HANDOFF.md @@ -108,6 +108,8 @@ pnpm --dir packages/update test - alpha 版本号改为取源码现有序号与 Nexus 远端序号的最大值后加一,禁止版本倒退和重复发布。 - `#49` 被 Jenkins CPS 的不可序列化正则 Matcher 阻断;算法改为只保留整数,并从 Nexus group 读标签、向 hosted 写制品。 - `#50` 证明旧 Jenkins 工作树仍保留 CRLF;主检出由 `CleanBeforeCheckout` 改为 `WipeWorkspace`,确保 `.gitattributes` 在全新工作树生效。 +- `XuqmRuntime.activate` 增加显式 `reloadBundle`:buz-only release 可替换旧定义并热加载;含 common 的 release 仍由宿主冷启动。 +- `#51` 通过换行、格式和类型门禁后,暴露 pack 校验脚本硬编码 Unix `npm`;现改为 `fileURLToPath` 与 Windows `cmd.exe/npm.cmd` 分支。 ### 2026-07-17 / common 第一轮 diff --git a/packages/update/README.md b/packages/update/README.md index 7a2400d..ba49f8e 100644 --- a/packages/update/README.md +++ b/packages/update/README.md @@ -37,6 +37,10 @@ apply from: file("../../node_modules/@xuqm/rn-update/android/xuqm-bundles.gradle ## 运行时规则 +安装仅包含 buz 的 release set 后,使用 +`XuqmRuntime.activate(moduleId, { reloadBundle: true })` 重新求值并激活新 bundle。若 release set +包含 common,宿主必须冷启动,不能在同一 JavaScript VM 中混用新旧 common。 + ```ts import { UpdateSDK, XuqmRuntime } from '@xuqm/rn-update' diff --git a/packages/update/scripts/verify-package.mjs b/packages/update/scripts/verify-package.mjs index a3f720d..39e7e8c 100755 --- a/packages/update/scripts/verify-package.mjs +++ b/packages/update/scripts/verify-package.mjs @@ -4,15 +4,26 @@ import { execFileSync } from 'node:child_process' import { readFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import path from 'node:path' +import { fileURLToPath } from 'node:url' -const cwd = new URL('..', import.meta.url).pathname -const result = JSON.parse( - execFileSync('npm', ['pack', '--json', '--ignore-scripts'], { - cwd, - encoding: 'utf8', - env: { ...process.env, npm_config_cache: path.join(tmpdir(), 'xuqm-rn-npm-cache') }, - }), -)[0] +const cwd = fileURLToPath(new URL('..', import.meta.url)) +const packOutput = + process.platform === 'win32' + ? execFileSync( + process.env.ComSpec ?? 'cmd.exe', + ['/d', '/s', '/c', 'npm.cmd pack --json --ignore-scripts'], + { + cwd, + encoding: 'utf8', + env: { ...process.env, npm_config_cache: path.join(tmpdir(), 'xuqm-rn-npm-cache') }, + }, + ) + : execFileSync('npm', ['pack', '--json', '--ignore-scripts'], { + cwd, + encoding: 'utf8', + env: { ...process.env, npm_config_cache: path.join(tmpdir(), 'xuqm-rn-npm-cache') }, + }) +const result = JSON.parse(packOutput)[0] const required = [ 'android/src/main/AndroidManifest.xml', 'android/src/main/java/com/xuqm/update/XuqmBundleModule.java', diff --git a/packages/update/src/PluginRuntime.ts b/packages/update/src/PluginRuntime.ts index 1f82d02..0bafaf8 100644 --- a/packages/update/src/PluginRuntime.ts +++ b/packages/update/src/PluginRuntime.ts @@ -19,6 +19,11 @@ export interface XuqmRuntimeOptions { checkUpdatesOnStart?: boolean } +export interface XuqmPluginActivationOptions { + /** Re-evaluate the active bundle after a buz-only release was installed. */ + reloadBundle?: boolean +} + const definitions = new Map() /** Called by a plugin bundle when it is evaluated. */ @@ -55,9 +60,19 @@ export const XuqmRuntime = (() => { return { kind: 'none' as const } }, - async activate(moduleId: string): Promise { + async activate( + moduleId: string, + activationOptions: XuqmPluginActivationOptions = {}, + ): Promise { if (!options) throw new Error('[XuqmRuntime] configure() must be called before activate()') - if (active.has(moduleId)) return + if (active.has(moduleId) && !activationOptions.reloadBundle) return + + if (activationOptions.reloadBundle) { + const previous = definitions.get(moduleId) + if (previous?.deactivate) await previous.deactivate() + active.delete(moduleId) + definitions.delete(moduleId) + } let definition = definitions.get(moduleId) if (!definition) { diff --git a/packages/update/src/index.ts b/packages/update/src/index.ts index b85e0ff..54af458 100644 --- a/packages/update/src/index.ts +++ b/packages/update/src/index.ts @@ -20,4 +20,9 @@ export type { ReleaseSetPlan, } from './releaseSet' export { XuqmRuntime, definePlugin } from './PluginRuntime' -export type { XuqmPluginContext, XuqmPluginDefinition, XuqmRuntimeOptions } from './PluginRuntime' +export type { + XuqmPluginActivationOptions, + XuqmPluginContext, + XuqmPluginDefinition, + XuqmRuntimeOptions, +} from './PluginRuntime'