关于 modernize-flp-sandbox
面向 UI5 开发者的迁移技能,用于将应用从旧版 FLP sandbox 迁至 New Sandbox。它自动执行核心转换,检查测试基础设施,并单独报告相关问题。
主要功能
- 01分析旧版 sandbox 文件
- 02转换 HTML 与配置
- 03创建或重写钩子模块
- 04更新 OPA 测试配置
- 05报告迁移缺口
适用场景
- 01将 UI5 应用迁至 New Sandbox
- 02合并多个 sandbox 场景
- 03恢复 OPA 测试配置
- 04查找已弃用的 ushell 服务用法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill modernize-flp-sandbox安装后在 Claude Code 中输入/modernize-flp-sandbox确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/native-replacements.mdoperations.mdsandbox-config-surface.md详细介绍
New Sandbox 迁移技能
将 UI5 应用从旧版 FLP sandbox 迁移到 New Sandbox。 你负责分析、转换、验证并报告。此技能会全自动完成核心迁移;对于相关事项(见下方范围),会先征求使用者意见再采取行动。
现在阅读 references/operations.md——这是所有 HTML / JSON / hook 模块转换的操作参考。
现在阅读 references/sandbox-config-surface.md——这是关于 New Sandbox 允许、阻止及静默覆盖内容的权威说明。
所有 §6 规则均引用此文件,而不是在正文中重复列出限制。
范围 — 三个层级
-
核心迁移 — 应用在 New Sandbox 下启动所需的操作。无条件执行;若无法完成,则迁移失败。触发条件:§6a、§6b、§6c、§6f。
-
测试基础设施 — 在不改变测试语义的前提下恢复测试套件运行。安全的重写无条件应用(资源根重绑定、OPA 引导代码提取)。任何涉及测试逻辑的操作:检测、报告,并在请求时提供选项。触发条件:§6d、§6e、§6g、§6h、§6j、§6l。
-
相关迁移与提示 — 由迁移引出但不属于迁移本身的事项。仅检测并报告;不应用重写。
- QUnit 1.x → 2.x 模式(§6i)— 提示。
- Shell 功能测试(§6j、§6l)— 提示。
- 已弃用的 ushell 服务(§6k)— 在核心扫描中检测,并报告为“需要手动操作”(硬性运行时阻塞)。
1. 检测应用根目录
如果用户提供了路径参数,则使用该路径。否则,从 CWD 检测:
- 在 CWD 及其父目录中查找
package.json+webapp/ - 如果未找到,告知用户:“无法检测到 UI5 应用根目录。请在应用目录内运行,或提供路径:
/modernize-flp-sandbox ~/path/to/app”
将 APP_ROOT 设为解析后的绝对路径。
2. 确保状态干净(迁移前)
修改任何文件之前,先确保有可回滚点。
如果检测到 git 仓库:
cd $APP_ROOT
git stash push -m "modernize-flp-sandbox: pre-migration backup $(date +%Y%m%d-%H%M%S)"
保存 stash 引用以供回滚。如果没有可 stash 的内容,则工作树已干净。
如果没有 git 仓库:
mkdir -p $APP_ROOT/webapp/test/.migration-backup
cp $APP_ROOT/webapp/test/flpSandbox*.html $APP_ROOT/webapp/test/.migration-backup/ 2>/dev/null
cp $APP_ROOT/webapp/test/fioriSandboxAppConfig.json $APP_ROOT/webapp/test/.migration-backup/ 2>/dev/null
3. 分析
读取 $APP_ROOT/webapp/test/ 下的所有 HTML 文件(递归),并构建模式清单。
检测旧版 sandbox HTML 文件 — 符合以下任一条件的文件即为旧版文件:
id="sap-ushell-bootstrap"(sandbox 引导脚本标签)window["sap-ushell-config"]赋值src=引用了sandbox2.js(中间版本 New Sandbox 的旧式引导方式,早于SandboxBootTask.js;与其他旧版模式同等处理)
OPA 测试页面(不会接收 SandboxBootTask / boot-manifest): 文件符合旧版检测条件,且同时包含以下所有内容:
QUnit.config或QUnit.config.autostart赋值,或window.QUnit = {sap.ui.require(,其中包含来自sap/ui/qunit/、sap/ui/test/或sap/ui/thirdparty/qunit的条目
这些文件不会作为 sandbox HTML 迁移。它们将执行:(a) §6g 将引导代码提取到同级 JS 文件;(b) §6i 检测 QUnit 1.x → 经用户同意后移交子技能。§6g 的 HTML 编辑包括:
- 移除
<script src="../ushellConfig.js">和<script src="...sandbox.js" id="sap-ushell-bootstrap"> - 将内嵌 QUnit/OPA 引导代码替换为直接加载脚本标签(qunit-2.css、qunit-2.js、qunit-junit.js)+
<script src="AllJourneys.js"> - 根据内嵌内容创建
AllJourneys.js(见 §6g) - 将
data-sap-ui-preload="async"更新为data-sap-ui-async="true",将data-sap-ui-compatVersion重命名为data-sap-ui-compat-version,将data-sap-ui-resourceroots重命名为data-sap-ui-resource-roots - 不要添加
SandboxBootTask.js、boot-manifest或<div id="canvas">外部化 ushell 配置检测: 对于webapp/test/下任意 HTML 中的每个<script src="...">,检查引用的文件是否包含对 ushell 配置对象的赋值,方式包括: window["sap-ushell-config"] = …/globalThis[…]/self[…]Object.assign(<global>["sap-ushell-config"], …)<global>["sap-ushell-config"].<prop> = …(初始化后写入属性)
仅读取配置的文件不符合条件。将每个符合条件的文件记录为 externalUshellConfigFile。迁移时:与内联配置采用相同方式解析,从所有引用该文件的 HTML 中移除 <script> 引用,并在步骤 9 中删除该文件。
对于每个旧版 HTML 文件,记录:
- 文件名、是否有模拟服务器、是否有 RTA/LREP、是否有自定义插件、是否有已弃用服务?
- 是否有
locate-reuse-libs.js?(作为 Gap-Report 标记) - 现有的
data-sap-ui-libs、data-sap-ui-resourceroots/data-sap-ui-resource-roots、data-sap-ui-theme、data-sap-ui-language值(保留,并重命名为 kebab-case)
检查是否存在 New Sandbox 构件:
fioriSandboxAppConfig.json— 是否存在?(将创建/覆盖)sandbox/目录 — 对每个*.js文件分类:legacy:包含Container.createRendererInternal或attachRendererCreatedEvent,或者不包含execute:。捕获以便原地重写(§6c)。将顶层命名空间声明捕获为preservedDeclarations[](正则:^\s*sap(\.[a-zA-Z0-9_]+)+\s*\?\?=\s*\{\}和globalThis.sap/window.sap链;后备方案:逐字复制,直到第一个sap.ui.define(或 IIFE)。modern:导出{ execute: ... }。原样复用。- **多个旧版文件:**全部重写。仅有一个通过
beforeFlpStart接入 — 优先选择名称中包含Init(而非Mock)的文件。生成 Gap-Report 说明。
- FakeLREP 文件(
webapp/test/下的*Lrep*.json/*LRep*.json)— 记录准确文件名,以供rta引用。
检查 OPA 测试接线:
- OPA 测试运行器 HTML(默认为
webapp/test/integration/opaTests.qunit.html;后备方案:webapp/test/integration/下任意引用旧版 mockserver HTML 的*qunit.html)— 记录匹配"../flpSandboxMockServer"的值 → 触发 §6d。 - OPA Common-helper 文件(默认为
webapp/test/integration/pages/Common.js;后备方案:webapp/test/integration/下任意包含iStartMyAppInAFrame(/getFrameUrl(和sap.ui.require.toUrl(的*.js)— 触发 §6e。
扫描已弃用的 ushell 服务用法:
在 webapp/ 下搜索 *.js(排除 test/、resources/、*.min.js),查找 getService(<name>) / getServiceAsync(<name>),其中 <name> 是 references/sandbox-config-surface.md §5 中列出的 12 个已弃用服务之一。记录文件、行号、服务名称和绑定变量。在 Gap-Report(§8)中列出;后续处理见 §6k。
检查 UI5 框架版本:
读取 ui5.yaml 和 ui5-local.yaml 中的 framework.version。New Sandbox 要求版本 >= 1.147。如果版本低于此要求,自动升级:
- 查找 >= 1.147 的最新稳定 SAPUI5 版本
- 原地写入
ui5.yaml - 记录:"Auto-bumped ui5.yaml framework version from
<old>to<new>(New Sandbox requires >= 1.147)." - 在 Gap-Report 中说明。
3a. Hook 依赖图扫描
识别从 beforeFlpStart hook 可达的传递 AMD 图中的急切 sap/ushell/* 导入(硬性约束 H2 — 参见 references/sandbox-config-surface.md §1)。详情和重写方案见 references/operations.md §6f。
**此步骤仅构建清单。**实际操作在 §6f 中进行(在 §6c 生成 hook 之后)。
记录:
hookEntryModule— 从现有fioriSandboxAppConfig.json的beforeFlpStart解析,或从 §3 中的旧版 hook 检测(通常为webapp/test/sandbox/fioriSandboxInit.js)。hookGraphRoots[]— hook 直接需要的其他模块(通过检查旧版 hook 主体确定)。
4. 场景向导(仅在发现多个 flp*.html 文件时)
如果只发现一个旧版沙箱 HTML 文件,则跳过。
新版沙箱使用单个 fioriSandboxAppConfig.json——多个 HTML 文件无法拥有独立配置。解决方案:合并为一个 HTML 文件,并通过 URL 参数在运行时选择场景。
**非交互模式:**检查 $APP_ROOT/webapp/test/wizard-answers.json。如果存在,直接使用其中的值(适用于 CI/脚本化迁移)。格式:
{
"scenarios": [
{ "file": "flpSandbox.html", "id": "default", "description": "Live backend" },
{ "file": "flpSandboxMockServer.html", "id": "mockserver", "description": "Mock server" }
],
"defaultScenario": "mockserver",
"targetHtml": "flpSandbox.html",
"paramName": "scenario"
}
询问用户:
- “每个文件代表什么场景?”(例如:“mockserver”、“cdm”、“default/no-mock”)
- “哪个场景应设为默认场景?”(建议:“mockserver”——可离线使用)
- “应保留哪个 HTML 文件名?”
- “URL 参数名称是什么?”(默认值:
scenario;使用项目专属名称可避免冲突)
**记录:**目标 HTML 文件、场景列表、默认场景 ID、参数名称、废弃的 HTML 文件。
URL 约定:flpSandbox.html?scenario=mockserver。未提供参数 → 使用默认场景。在 README 中记录选定的名称。
5. 迁移前检查清单
写入文件前,确认:已设置 APP_ROOT(§1)、已确保可回滚(§2)、已建立清单(§3)、如有需要,已准备好向导答案(§4)。全部确认无误 → 继续执行 §6。
6. 迁移
执行所有转换。如果任何写入失败,跳转到回滚(§10)。
范围 — 修改的文件:
- §3 中检测到的所有
flp*.html→ 完整转换(§6a–§6c) - OPA 测试页 HTML 文件 → 提取引导代码(§6g)
- OPA 测试运行器 HTML 文件 → 重新绑定资源根路径(§6d)
- QUnit 测试套件索引 → 重写 journey URL(§6g)
- OPA Common-helper → 注入 URL 参数(§6e)
webapp/test/fioriSandboxAppConfig.json→ 创建/覆盖(§6b)webapp/test/sandbox/*.js旧版钩子 → 就地重写(§6c)beforeFlpStartAMD 图中的文件 → 自动应用references/native-replacements.md(§6f)webapp/test/integration/下的 OPA 测试代码和应用代码 → §6h–§6l 的机械式重写
不在范围内:unitTests.qunit.html、unit/、*.qunit.js。
如果 opaTests.qunit.html 引用了 flpSandboxMockServer.html,请迁移它(§6a)并同时重新绑定 OPA 引用(§6d)。
以下每个小节均说明内容/时机/层级。完整算法见 references/operations.md。
6a. 就地转换旧版 HTML 文件
**层级:**核心。对每个旧版沙箱 HTML 文件无条件执行。
移除旧版 ushell-config 写入、sap-ushell-bootstrap 脚本标签及过时的引导属性。添加 SandboxBootTask.js(或 CDN 变体)、引导清单属性和 <div id="canvas">。合并各变体的资源根路径,并始终添加 sandbox 根路径。
→ references/operations.md §6a
6b. 创建或更新 fioriSandboxAppConfig.json
**层级:**核心。仅使用 references/sandbox-config-surface.md §2 中列出的键。
根据解析后的旧版配置构建 JSON。按第一个 - 将应用键拆分为 semanticObject + action。从 rootPath 中去除查询字符串,并确保以斜杠结尾。运行向导时,合并各变体中的磁贴。仅在有需要时添加 beforeFlpStart、rta、plugins。
→ references/operations.md §6b
6c. 创建或重写钩子模块
**层级:**核心。为 beforeFlpStart 提供支持,在 applyUshellConfig 之前运行。约定:不得触碰 globalThis["sap-ushell-config"],不得提前 require sap/ushell/*(参见 references/sandbox-config-surface.md §8)。
生成单场景钩子(仅模拟服务器)或多场景钩子(通过 URL 参数切换)。就地重写旧版钩子,并保留命名空间声明。如果不需要模拟服务器,则退回到最简无操作实现。
→ references/operations.md §6c
6d. 重新绑定 OPA 资源根路径
**层级:**测试基础设施。无条件执行(不改变语义)。
在 OPA 测试运行器 HTML 中,将 data-sap-ui-resource-roots 从指向旧版 mockserver HTML 改为指向迁移后的目标。
→ references/operations.md §6d
6e. 将场景 URL 参数注入 Common.js
**层级:**测试基础设施。仅当 Wizard 将多个 HTML 合并时执行。
在 # 哈希之前将场景参数注入 iframe URL(这样 window.location.search 才能获取该参数)。
→ references/operations.md §6e
6f. 解析 hook 图中的 ushell 依赖
**层级:**核心。硬运行时阻断项 H2——不得从 hook 的传递 AMD 图中急切加载任何 sap/ushell/*。
从 hookEntryModule(§3a)遍历 AMD 闭包。将每个 sap/ushell/* 引用分类为可直接替换/可通过架构调整迁移/需要手动处理。自动应用 references/native-replacements.md 中的方案,或生成 Gap-Report 条目。
→ references/operations.md §6f
6g. 将 OPA 引导代码提取到同级 JS 文件
**层级:**测试基础设施。机械式提取。
将内联 <script> 块提取到同级 .js 文件(默认文件名为 AllJourneys.js)。将 QUnit 1.x 全局变量和 jQuery.sap.getUriParameters 转换为现代等效用法。更新测试套件索引,使旅程 URL 使用 URLSearchParams 风格。
→ references/operations.md §6g
6h. 检测绑定到旧版 ushell-config 值的代码
**层级:**测试基础设施(可机械式自动修复的情况会自动修复,否则报告)。
识别硬编码值的代码:这些值在旧版配置中可自由指定,但在 New Sandbox 下的派生方式不同。类别:A 磁贴派生(自动修复)、B 用户配置(自动修复)、C 个性化设置(自动修复)、D 默认固定值(仅报告)。参见 references/sandbox-config-surface.md §3+§4。
→ references/operations.md §6h
6i. QUnit 1.x → 2.x 模式(移交)
**层级:**建议。实际重写由 modernize-flp-sandbox-qunit 子技能执行。
检测并报告,提供调用子技能的选项。之所以提示此项:§6a 会添加 data-sap-ui-async="true"——在此模式下,QUnit 1.x 全局变量(module、ok、equal)会抛出 ReferenceError。根本原因与 Sandbox 迁移无关;只是迁移使其显现出来。
→ references/operations.md §6i
6j. FLP 外壳控件检测(报告)
**层级:**测试基础设施(仅报告)。检测直接针对 FLP 外壳控件的 OPA 测试。较新的 UI5 版本通过 Web Components 渲染外壳,因此匹配 sap.m.* 外壳控件的测试会静默失效。
模式:S1 = sap.m 控件 + 仅限外壳的 id,S2 = type:"Back" 匹配器,S3 = 非 WebComponent 外壳页眉调用。
→ references/operations.md §6j
6k. 应用代码中的已弃用 ushell 服务(检测)
**层级:**核心检测。每个已弃用服务都会在调用 getService()/getServiceAsync() 时造成硬运行时阻断(参见 references/sandbox-config-surface.md §5)。应用将无法启动。
扫描应用代码并记录命中项,在 Gap-Report 中标记为“需要手动处理”。不会尝试重写。
→ references/operations.md §6k
6l. OPA 测试对 FLP-shell 功能的检测 + 询问
层: test-infra(检测 + 询问)。检测依赖 FLP shell 的 journey 测试。仅在存在可识别的 iframe-toggle 模式时才提供应用修复方案。未经同意绝不修改。
信号:shell-control 选择器(§6j)、已弃用的服务调用(§6k)、按约定命名的页面对象方法。
探测项:P1 = startMyAppInAFrame 标记,P2 = iStartMyAppInAFrame helper,P3 = 自定义 iframe URL 构造器。
→ references/operations.md §6l
8. 报告
始终输出 Gap-Report:
== New Sandbox Migration Report ==
App: <app name from package.json>
Path: <APP_ROOT>
Status: ✓ SUCCESSFUL | ✗ FAILED
── Automatically applied ──────────────────────────────────────────────
✓/✗ webapp/test/<filename>.html (HTML transformed)
✓/✗ webapp/test/fioriSandboxAppConfig.json (created)
✓/— webapp/test/sandbox/<hookModule>.js (created / reused / rewrote-legacy)
✓/— rta: "<fakeLRep filename>" (written to config)
✓/— opaTests.qunit.html (resource root rebound)
✓/— Common.js (scenario URL param injected)
✓/— Hook-graph: <N> trivial replacement(s) auto-applied
── Manual action required ─────────────────────────────────────────────
✗ <ServiceName> (deprecated — hard runtime error): <file>:<line>
→ <successor from sandbox-config-surface.md §5>
✗ Custom FakeLrep connector at <path>
→ May have sap/ushell dependencies incompatible with beforeFlpStart.
✗ Hook-graph — architectural-relocatable: <module> uses <sap/ushell/X>
→ [diff sketch]
✗ Hook-graph — manual-required: <module> uses <sap/ushell/X> at load time
→ [textual sketch]
✗ <Layer-2 step> — pattern not recognised
→ [description]
── Advisory (no migration blocker) ───────────────────────────────────
ℹ <N> QUnit 1.x pattern(s) in <M> file(s)
ℹ Shell-control test patterns: <N> in <M> files (S1: ?, S2: ?, S3: ?)
ℹ locate-reuse-libs.js: script tag removed — deprecated pattern, safe.
ℹ <other non-standard patterns>
[On failure: "Rolled back. Partial attempt saved to webapp/test/.migration-attempt/"]
没有发现问题的部分将从报告中省略。
9. 清理旧文件(必需 — 不得跳过)
- 未使用 Wizard: 旧 HTML 就地转换 — 无需删除其他文件。
- 使用了 Wizard: 删除所有过时的 HTML 文件(目标文件除外)。
- 始终: 删除每个
externalUshellConfigFile[]。
rm <obsolete-file-1> <obsolete-file-2> ...
验证: ls webapp/test/flp*.html — 仅保留迁移后的目标文件。
10. 回滚
§6 期间发生任何故障时:
保存部分尝试:
mkdir -p $APP_ROOT/webapp/test/.migration-attempt
cp $APP_ROOT/webapp/test/*.html $APP_ROOT/webapp/test/.migration-attempt/ 2>/dev/null
cp $APP_ROOT/webapp/test/fioriSandboxAppConfig.json $APP_ROOT/webapp/test/.migration-attempt/ 2>/dev/null
恢复:
- Git:
git checkout -- webapp/test/ && git stash pop - 无 Git:
cp $APP_ROOT/webapp/test/.migration-backup/* $APP_ROOT/webapp/test/
输出状态为 FAILED 的 Gap-Report。