| name | modernize-flp-sandbox |
| description | Use when migrating UI5 apps from legacy FLP sandbox to the new sandbox (New Sandbox). Triggers on /modernize-flp-sandbox, mentions of "flpSandbox.html", "sap-ushell-config", or requests to update sandbox configuration.
|
New Sandbox Migration Skill
Migrate a UI5 application from the legacy FLP sandbox to New Sandbox.
You analyze, transform, verify, and report. The skill is fully automatic
for the core migration; for adjacent concerns (see scope below) it asks
the consumer before acting.
Read references/operations.md now — it is the operational reference for
all HTML / JSON / hook-module transformations.
Read references/sandbox-config-surface.md now — it is the ground-truth
description of what New Sandbox allows, blocks, and silently overrides.
All §6 rules cite this file rather than restating constraints inline.
Scope — three layers
The skill operates in three layers. Each §6 subsection below is tagged with
one of them so it is always clear what is core and what is adjacent.
-
Core migration — actions that are necessary for the app to boot
under New Sandbox. The skill performs them unconditionally and fails the
migration if they cannot be performed. Triggers: §6a, §6b, §6c, §6f.
-
Test infrastructure — actions that get the existing test suite
running again under New Sandbox without changing test semantics. The
skill performs the safe ones (resource-root rebind, OPA bootstrap
extraction). For anything that touches test logic, it detects, reports,
and offers to apply on consumer request. Triggers: §6d, §6e, §6g,
§6h, §6j, §6l.
-
Adjacent migrations & advisories — concerns that are surfaced by
the New Sandbox migration but are not part of it. The skill detects
and reports only; no rewrites are applied.
Triggers:
- QUnit 1.x → 2.x patterns (§6i) — advisory.
- Shell-feature tests (§6j, §6l) — advisory.
Note: deprecated ushell service usage (§6k) is detected as part of
the core pass and reported as "Manual action required" — it is a hard
runtime block, not an advisory.
1. Detect App Root
If the user provided a path argument, use it. Otherwise, detect from CWD:
- Look for
package.json + webapp/ in CWD and parent directories
- If not found, tell the user: "Could not detect a UI5 app root. Please run from inside
the app directory or provide a path:
/modernize-flp-sandbox ~/path/to/app"
Set APP_ROOT to the resolved absolute path. All file operations use this as the base.
2. Ensure Clean State (Pre-Migration)
Before touching any files, secure a rollback point.
If git repository detected (git -C $APP_ROOT rev-parse --git-dir succeeds):
cd $APP_ROOT
git stash push -m "modernize-flp-sandbox: pre-migration backup $(date +%Y%m%d-%H%M%S)"
Save the stash ref for rollback. If git stash fails (nothing to stash), note it — the
working tree is already clean.
If no git repository:
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
Save the backup path for rollback.
3. Analyze
Read all HTML files under $APP_ROOT/webapp/test/ (recursive, including subdirectories) and build a pattern inventory:
Detect legacy sandbox HTML files — a file is a legacy sandbox HTML if it contains ANY of:
id="sap-ushell-bootstrap" (sandbox bootstrap script tag), OR
window["sap-ushell-config"] assignment, OR
src= referencing sandbox2.js (intermediate New Sandbox old-style bootstrap — predates SandboxBootTask.js)
The sandbox2.js pattern indicates a partial/old-style New Sandbox migration that still needs updating to the current SandboxBootTask.js-based approach. Treat it identically to the other legacy patterns.
OPA test-page (does NOT receive SandboxBootTask / boot-manifest):
A file matches the legacy sandbox detection criteria above AND ALSO
contains ALL of:
QUnit.config or QUnit.config.autostart assignment, OR window.QUnit = { pattern
sap.ui.require( with entries from "sap/ui/qunit/, "sap/ui/test/, or "sap/ui/thirdparty/qunit
These files are NOT added to the legacy HTML migration list. Instead, they receive a different treatment composed of two pieces: (a) §6g extracts the inline OPA bootstrap into a sibling JS file and rewrites the HTML attributes, (b) §6i detects QUnit 1.x patterns in the journey/page modules and hands off to the modernize-flp-sandbox-qunit sub-skill on consumer consent. The HTML edits performed in §6g specifically:
- Remove
<script src="../ushellConfig.js"> and <script src="...sandbox.js" id="sap-ushell-bootstrap"> includes (these are no longer needed once the primary FLP HTML is migrated)
- Replace inline QUnit/OPA bootstrap
<script> block with direct script-tag loading (qunit-2.css, qunit-2.js, qunit-junit.js) + <script src="AllJourneys.js">
- Create
AllJourneys.js from the inline bootstrap content (see §6g below)
- Update
data-sap-ui-preload="async" to data-sap-ui-async="true" and rename data-sap-ui-compatVersion → data-sap-ui-compat-version and data-sap-ui-resourceroots → data-sap-ui-resource-roots
- Do NOT add
SandboxBootTask.js, boot-manifest, or <div id="canvas">
Externalized ushell config detection:
After scanning HTML files for legacy sandbox detection, also check: for
each <script src="..."> tag in any HTML file under webapp/test/, if
the src attribute resolves to a file (default: .js extension; the
extension is not required — accept any src whose contents are loaded
as JavaScript) that:
(a) is reachable from the HTML's directory (resolve src against the
HTML location; the file may live anywhere under $APP_ROOT/, not
only under webapp/test/), AND
(b) contains an assignment into the global ushell-config object via
any of these write patterns:
- window["sap-ushell-config"] = …
- globalThis["sap-ushell-config"] = …
- self["sap-ushell-config"] = …
- any Object.assign(<global>["sap-ushell-config"], …) where
<global> is one of the three above
- any <global>["sap-ushell-config"].<prop> = … (property-write
after a prior ??=/||= initializer in the same file)
A file that only *reads* `window["sap-ushell-config"]` does NOT
qualify — bare reads are common in app code that consults the
config and must not be deleted.
Record each such file as an externalUshellConfigFile. For migration:
- Parse the
window["sap-ushell-config"] value from the external file identically to inline config (same rules for extracting applications, rootIntent, etc.)
- Remove the
<script src="..."> include tag from ALL HTML files that reference it
- The external file itself will be deleted in Step 9 (Legacy File Cleanup)
For each legacy HTML file found, record:
- Filename
- Has mock server? (contains
MockServer or use-mockserver or locate-reuse-libs)
- Has RTA/LREP? (contains
fakeLrep or FakeLrepConnector or xx-flexibilityServices)
- Has custom plugins? (contains
bootstrapPlugins with entries other than RuntimeAuthoringPlugin)
- Has deprecated services in config? (contains
Personalization, LaunchPage, NavTargetResolution in services config)
- Has
locate-reuse-libs.js? (non-standard pattern — flag for Gap-Report)
- Existing
data-sap-ui-libs value (carry over)
- Existing
data-sap-ui-resourceroots / data-sap-ui-resource-roots value (carry over, rename to kebab-case)
- Existing
data-sap-ui-theme value (carry over)
- Existing
data-sap-ui-language value (carry over)
Check for existing New Sandbox artifacts:
fioriSandboxAppConfig.json — exists? (will be created/overwritten)
sandbox/ directory — exists? For each *.js file inside, classify:
legacy if file contains Container.createRendererInternal OR attachRendererCreatedEvent OR file does NOT contain the literal token execute:. Capture the filename for in-place rewrite (Step 6c).
modern if file exports { execute: ... }. Reuse untouched.
- For each
legacy file, capture top-level namespace declarations as preservedDeclarations[]. Match the regex ^\s*sap(\.[a-zA-Z0-9_]+)+\s*\?\?=\s*\{\} and analogous globalThis.sap/window.sap chains. Conservative fallback: if a non-recognised top-of-file prelude exists, copy lines verbatim from start of file until the first sap.ui.define( or (function() IIFE.
- Multiple legacy files found: rewrite ALL of them. Each gets the same
execute: export
treatment. Only one file needs to be wired via beforeFlpStart — if one file's name
suggests it is the primary init (heuristic: name contains Init and not Mock/MockServer),
prefer the primary one. Emit a Gap-Report note listing all rewritten files so the developer
can verify the correct one is referenced.
- A FakeLREP file (default name
fakeLrep.json / fakeLRep.json, but any
*Lrep*.json / *LRep*.json under webapp/test/ qualifies) — exists?
Note exact filename for the rta reference.
Check for OPA test wiring:
- An OPA test runner HTML (default location
webapp/test/integration/opaTests.qunit.html; fallback: any
*qunit.html under webapp/test/integration/ whose
data-sap-ui-resource-roots attribute references the legacy mockserver
HTML) — exists? Read the attribute. Record any value matching
"../flpSandboxMockServer" (without .html, possibly with trailing
path) — this triggers Step 6d.
- An OPA Common-helper file (default location
webapp/test/integration/pages/Common.js; fallback: any *.js under
webapp/test/integration/ containing both iStartMyAppInAFrame( or
getFrameUrl( AND sap.ui.require.toUrl() — exists? Record whether
it matches the iframe-URL-builder pattern — this triggers Step 6e.
Scan application code for deprecated ushell service usage:
This is the analysis-time inventory pass. The action — reporting as
"Manual action required" in the Gap-Report — is in §6k.
The canonical 12-entry table with successors and reasons lives in
references/sandbox-config-surface.md §5; do not duplicate it here.
Search all *.js files under webapp/ excluding test/, resources/,
and *.min.js for getService(<name>) / getServiceAsync(<name>)
where <name> is one of the 12 deprecated services in the reference.
Per match record file path, line number, service name, and (if visible)
the variable bound to the resolved service.
If at least one match is found, surface the inventory in the Gap-Report
during §8 and let §6k drive the follow-up. If no matches: note
"No deprecated service usage found in app code."
Check UI5 framework version:
Read ui5.yaml and ui5-local.yaml (if present) for the framework.version field.
New Sandbox requires UI5 >= 1.147. If the version is below 1.147, automatically bump it:
- Run
npm info @sapui5/distribution-metadata versions --json 2>/dev/null | python3 -c "import json,sys; vs=[v for v in json.load(sys.stdin) if not 'SNAPSHOT' in v]; vs.sort(key=lambda v: [int(x) for x in v.split('.')]); print(vs[-1])" to find the latest stable SAPUI5 version >= 1.147.
- Write that version to
ui5.yaml (replace the framework.version value in-place).
- Log: "Auto-bumped ui5.yaml framework version from
<old> to <new> (New Sandbox requires >= 1.147)."
Note this in the Gap-Report as an automatic change that was applied.
3a. Hook Dependency Graph Scan
This step identifies eager sap/ushell/* imports anywhere in the transitive AMD graph reachable from the beforeFlpStart hook. Such imports trigger Hard Constraint H2 from references/sandbox-config-surface.md §1 — the app never renders. The architectural detail and the rewrite recipe live in references/operations.md §6f.
This step builds the inventory only. The actual graph walk and rewrite/report happens in Section 6f (after the hook is generated in 6c).
Inventory items to record now (during Step 3):
- Locate the hook entry module. If
webapp/test/fioriSandboxAppConfig.json exists with beforeFlpStart, resolve that module path. Otherwise the entry is the legacy hook detected in Step 3 (typically webapp/test/sandbox/fioriSandboxInit.js or whatever the legacy mockserver init file is). On a first-run migration, the entry is whichever module Section 6c will write/rewrite.
- Record the entry module path as
hookEntryModule for Section 6f.
- Record
hookGraphRoots[] — additional modules the hook will require directly (e.g. localService/mockserver). These come from inspecting the legacy hook's body. Section 6f recurses from each.
The graph walk itself (depth-first traversal, dependency extraction, classification) runs at the start of Section 6f, after Sections 6a–6e have produced the post-migration hook content.
4. Scenario Wizard (only when multiple flp*.html found)
Skip this step if only one legacy sandbox HTML was found in step 3.
Context: New Sandbox uses a single shared fioriSandboxAppConfig.json. Multiple HTML files
cannot have independent configurations. The solution is to consolidate into one HTML file with
a URL parameter that selects the scenario at runtime.
Non-interactive mode: Before prompting, check for $APP_ROOT/webapp/test/wizard-answers.json.
If it exists, use its values directly. This lets callers pre-fill answers for unattended runs
(CI, scripted migrations). Skip the questions below if found.
{
"scenarios": [
{ "file": "flpSandbox.html", "id": "default", "description": "Live backend" },
{ "file": "flpSandboxMockServer.html", "id": "mockserver", "description": "Mock server" }
],
"defaultScenario": "mockserver",
"targetHtml": "flpSandbox.html",
"paramName": "scenario"
}
Fields: scenarios (all legacy HTMLs with their assigned ids), defaultScenario (id of the
scenario that runs when no URL param is present), targetHtml (the HTML file that survives),
paramName (URL parameter name — optional, defaults to "scenario" if absent).
Ask the user:
-
"Found multiple legacy sandbox files: <list with detected characteristics>.
What scenario does each file represent?" (e.g., "mockserver", "cdm", "default/no-mock")
-
"Which scenario should be the default when no URL parameter is set?
(Recommendation: mockserver — it works offline without a proxy, which is the
common case for local development. Other scenarios often require Fiori Tools or
a network proxy.)"
-
"Which HTML filename should be kept for the migrated file?"
-
"What URL parameter name should be used to select the scenario?
(Default: scenario. For apps that need to avoid collisions with other URL parameters,
a project-specific name like myAppScenario is recommended — document it in the README.)"
Record:
- Target HTML filename (the one that survives)
- Scenario list:
[{id: "mockserver", description: "..."}, {id: "default", ...}]
- Default scenario ID (recommended:
mockserver)
- Parameter name (default:
"scenario"; user may override to avoid collisions)
- Obsolete HTML files (everything except the target)
URL Parameter Convention:
The default parameter name is "scenario". It is short and human-readable.
If multiple apps share the same browser context, use a project-specific prefix to avoid
parameter collisions (e.g. "myAppScenario"). Document the chosen name in the project's
README or migration guide.
| Scenario | Example URL (default param name) |
|---|
| Use default (no param) | flpSandbox.html |
| Explicit mockserver | flpSandbox.html?scenario=mockserver |
| Live backend | flpSandbox.html?scenario=default |
| Custom named scenario | flpSandbox.html?scenario=cdm |
If parameter is absent → default scenario runs (recommended default: mockserver).
5. Pre-Migration Checklist
Before writing any files, verify:
APP_ROOT is set (Step 1 completed)
- Rollback point secured (Step 2 completed)
- Pattern inventory built (Step 3 completed)
- If multiple legacy HTML files found: Wizard answers available (Step 4 completed)
All good → proceed to Step 6.
6. Migrate
Execute all transformations. Work file by file. If any write operation fails, immediately
jump to ROLLBACK (Section 10).
SCOPE: Modify only the files listed below. Files explicitly out of
scope: unitTests.qunit.html, files under unit/, *.qunit.js.
Files in scope:
- All
flp*.html files detected in §3 → full transformation (§6a-§6c)
- OPA test-page HTMLs (default
webapp/test/integration/Journey.qunit.html)
→ bootstrap extraction (§6g)
- OPA test runner HTML (default
webapp/test/integration/opaTests.qunit.html)
→ resource-root rebind (§6d)
- QUnit test suite index (default
webapp/test/testsuite.qunit.html)
→ journey-URL rewrite (§6g)
- OPA Common-helper file (default
webapp/test/integration/pages/Common.js)
→ URL-param injection (§6e)
webapp/test/fioriSandboxAppConfig.json → create / overwrite (§6b)
webapp/test/sandbox/*.js legacy hook files → rewrite in-place (§6c)
- Files reachable from the
beforeFlpStart transitive AMD graph
→ auto-apply transformations from references/native-replacements.md
only (§6f)
- OPA test code under
webapp/test/integration/ and (for §6h) app code
under webapp/ → mechanical rewrites against the rules in §6h–§6l;
§6i is detect-only (advisory). §6k is detect-only (manual required —
hard runtime block, no sub-skill offered).
When flpSandboxMockServer.html is referenced by opaTests.qunit.html,
migrate it normally (§6a) AND rebind the OPA reference (§6d). Both must
happen together.
Detail. Each subsection below states what the step does, when it
runs, and which layer it belongs to. The full algorithm — detection
patterns, regex, edge cases, recipes — lives in
references/operations.md. Each
subsection links into the matching section there.
6a. Transform legacy HTML file(s) in-place
Layer: core. Performed unconditionally on every legacy sandbox
HTML detected in §3.
What it does: removes the legacy ushell-config write, the
sap-ushell-bootstrap script tag, and obsolete bootstrap attributes;
adds SandboxBootTask.js (or its CDN variant), the boot-manifest
attributes, and the <div id="canvas"> invariant; merges resource
roots across HTML variants and always adds the sandbox root.
→ See references/operations.md §6a.
6b. Create or update fioriSandboxAppConfig.json
Layer: core. The sandbox honors only the keys listed in
references/sandbox-config-surface.md §2
— emit none beyond that set.
What it does: builds the JSON from the parsed legacy
window["sap-ushell-config"]. Splits each application key on the
first - into semanticObject + action. Strips query strings out
of rootPath and ensures a trailing slash. Aggregates tiles across
all HTML variants when the Wizard ran. Adds beforeFlpStart,
rta, plugins only when the inputs warrant it.
→ See references/operations.md §6b.
6c. Create or rewrite hook module
Layer: core. Backs beforeFlpStart and runs before
applyUshellConfig. The contract lives in
references/sandbox-config-surface.md §8:
the hook may not touch globalThis["sap-ushell-config"] and must not
eagerly require sap/ushell/*.
What it does: produces a single-scenario hook (mock-server only) or a
multi-scenario hook (URL-parameter switch) depending on whether the
Wizard ran. Rewrites legacy hook modules in place, preserving any
top-of-file namespace declarations. Falls back to a minimal no-op
when no scenario actually needs a mock server.
→ See references/operations.md §6c.
6d. Rebind OPA resource roots
Layer: test-infra. Mechanical resource-root rewrite — applied
unconditionally because it does not change test semantics.
What it does: in the OPA test runner HTML (default
webapp/test/integration/opaTests.qunit.html), rewrites any
data-sap-ui-resource-roots value pointing at the legacy mockserver
HTML so it points at the migrated target HTML.
→ See references/operations.md §6d.
6e. Inject scenario URL parameter into Common.js
Layer: test-infra. Only triggered when the Wizard consolidated
multiple HTMLs into one and the chosen URL-parameter convention has
to be threaded through the iframe URL builder.
What it does: in the OPA Common-helper file, injects the scenario
parameter into the iframe URL before the # hash is appended (so
window.location.search still sees it).
→ See references/operations.md §6e.
6f. Resolve hook-graph ushell dependencies
Layer: core. Hard runtime block H2 (see
references/sandbox-config-surface.md §1)
— no sap/ushell/* may be required eagerly from the hook's
transitive AMD graph.
What it does: walks the AMD closure starting at
hookEntryModule (§3a), classifies every sap/ushell/* hit as
trivial-replaceable / architectural-relocatable / manual-required,
and either auto-applies a recipe from
references/native-replacements.md
or emits a Gap-Report entry with a concrete suggestion.
→ See references/operations.md §6f.
6g. Extract OPA bootstrap into a sibling JS file
Layer: test-infra. Mechanical extraction of the OPA bootstrap
block out of an OPA test HTML so the page survives the loss of the
legacy sandbox.js include.
What it does: extracts the inline <script> block content into a
sibling .js file (default name AllJourneys.js). Converts
QUnit 1.x globals and jQuery.sap.getUriParameters into modern
equivalents. Updates the test-suite index (testsuite.qunit.html)
to use URLSearchParams-style journey URLs.
→ See references/operations.md §6g.
6h. Detect bindings to legacy ushell-config values
Layer: test-infra (auto-fix where mechanical, report otherwise).
Identify code that hard-codes values which were free-form in the
legacy window["sap-ushell-config"] but are now derived from a
different source (or fixed entirely) under New Sandbox. Auto-fix the
safe, mechanical cases; report everything else.
A migrated app boots cleanly under New Sandbox even when this rule
does nothing — the consequence is a test failure or runtime mismatch
later, not a boot crash. That is why this is test-infra, not core:
the source change has already been performed by §6a (which removes
the legacy ushell-config write), and §6h only cleans up downstream
fall-out.
The set of values is enumerated in
references/sandbox-config-surface.md §3 + §4.
Categories: A tile-derived (auto-fix), B user-profile (strict mode,
auto-fix), C personalization (strict mode, auto-fix), D fixed-by-
default (report only).
→ See references/operations.md §6h.
6i. QUnit 1.x → 2.x patterns in OPA test code (handoff)
Layer: advisory. The actual rewrite lives in the
modernize-flp-sandbox-qunit sub-skill.
The main skill detects, reports, and offers to invoke the sub-skill
on consumer request.
Why surfaced by the migration: §6a removes
data-sap-ui-preload="async" and adds data-sap-ui-async="true" on
the test bootstrap. In that load-timing mode, QUnit 1.x globals
(module, ok, equal, …) and the direct-call form
QUnit.ok(...) are no longer defined when journey/page modules
execute. Bare references throw ReferenceError at module load.
The root cause is independent of the sandbox migration — the test
code does not match the QUnit version. The migration just exposes
it. That is why the actual rewrite is delegated to a stand-alone
sub-skill the consumer can also invoke directly on apps that have
not migrated.
→ See references/operations.md §6i.
6j. FLP shell-control awareness in OPA test code (reporting)
Layer: test-infra (reporting only — no auto-rewrite). Detect OPA
test code that targets FLP shell controls directly and surface
concrete fix suggestions in the §8 report. Never rewrite test logic
without consumer consent.
Why surfaced by the migration: the FLP shell renders increasingly
through UI5 Web Components in newer UI5 versions. App-level controls
and the app's own behavior are unaffected, but OPA tests that match
shell controls by sap.m.* control type or that call non-WebComponent
APIs on the shell header break silently or throw at runtime. The
exact version at which a given shell control switches is a moving
target — this rule does not encode specific version thresholds, it
just identifies the test patterns that are at risk.
Patterns: S1 = sap.m control + shell-only id, S2 = type:"Back"
matcher, S3 = non-WebComponent calls on the shell header
(getHeadItems, header.$("icon").click()).
→ See references/operations.md §6j.
6k. Deprecated ushell-services in app code (detection)
Layer: core detection. Every deprecated service is a hard runtime
block at getService() / getServiceAsync() time — see
references/sandbox-config-surface.md §5.
An app that still consumes any of them does not boot under New Sandbox.
The skill scans app code, records every hit, and surfaces all findings
in the Gap-Report under "Manual action required". No rewrite is
attempted and no sub-skill is offered — the rewrite is non-trivial
(sync→async transitions, API shape changes per service) and must be
done by the developer.
→ See references/operations.md §6k.
6l. OPA tests that exercise FLP-shell features (detection + ask)
Layer: test-infra (detection + ask). Detect journey tests whose
behavior depends on the surrounding FLP shell, surface the list to
the consumer, and — only if a recognised iframe-toggle pattern is
present in the app — offer to apply it. Never modify a test without
consent.
Signals: (a) shell-control selectors from the §6j catalog,
(b) calls to deprecated-or-successor shell services from the §6k
list, (c) convention-named page-object methods (heuristic).
Probes: P1 = existing startMyAppInAFrame flag, P2 =
iStartMyAppInAFrame helper, P3 = bespoke iframe URL builder.
→ See references/operations.md §6l.
8. Report
Always emit the Gap-Report, whether successful or failed:
== 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 or removal instruction from sandbox-config-surface.md §5>
(one line per finding; section omitted if no deprecated services found)
✗ Custom FakeLrep connector at <path>
→ Own connector code may have sap/ushell dependencies incompatible
with beforeFlpStart. Migrate manually.
(omitted if not present)
✗ Hook-graph — architectural-relocatable: <module> uses <sap/ushell/X>
→ [diff sketch]
(one entry per hit; omitted if none)
✗ Hook-graph — manual-required: <module> uses <sap/ushell/X> at load time
→ [textual sketch]
(one entry per hit; omitted if none)
✗ <Layer-2 step> — pattern not recognised
→ [description of what was expected and what was found instead]
(one entry per unrecognised pattern; omitted if all Layer-2 steps ran cleanly)
── Advisory (no migration blocker) ───────────────────────────────────
ℹ <N> QUnit 1.x pattern(s) in <M> file(s) under webapp/test/integration/
(omitted if none)
ℹ Shell-control test patterns flagged: <N> in <M> files (S1: ?, S2: ?, S3: ?)
(omitted if none)
ℹ locate-reuse-libs.js: script tag removed — deprecated Fiori Tools pattern,
removal is safe. (omitted if not present)
ℹ <description of any other non-standard pattern and how it was handled>
→ Consider documenting this pattern in the Migration Guide.
(omitted if none)
[On failure: "Rolled back to original state. Partial attempt saved to webapp/test/.migration-attempt/"]
9. Legacy File Cleanup (REQUIRED — do not skip)
This step is mandatory. Execute it regardless of whether the migration completed successfully or failed.
- No Wizard: the original legacy HTML file(s) were transformed in-place — there are no extra files to delete. Skip this step.
- Wizard was used: delete all obsolete HTML files identified during the wizard (everything except the target HTML). The git history preserves them if ever needed.
- Always: delete each
externalUshellConfigFile[] (external ushell config files whose content has already been parsed and moved to fioriSandboxAppConfig.json).
rm <obsolete-file-1> <obsolete-file-2> ...
Verify: confirm with ls webapp/test/flp*.html that only the migrated target file remains.
10. Rollback
On any failure during step 6 (Migrate):
Save partial attempt:
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
Restore original state:
If git stash was used:
cd $APP_ROOT
git checkout -- webapp/test/
git stash pop
If backup directory was used:
cp $APP_ROOT/webapp/test/.migration-backup/* $APP_ROOT/webapp/test/
Emit the Gap-Report with status FAILED and note what failed.