plugins/power-pages/skills/migrate-bootstrap/SKILL.md
Migrates a traditional Power Pages site from Bootstrap 3 to Bootstrap 5. Downloads the site, runs the pac pages bootstrap-migrate engine, reviews the change report, applies AI-assisted fixes for the residual hierarchy/CSS changes the engine only flags, uploads the migrated site (which auto-enables the Bootstrap 5 runtime flag), verifies the flag, and validates. Use when the user wants to upgrade an older Bootstrap-3 portal to Bootstrap 5. NOT for code sites (React/Vue/Angular/Astro) — those are never Bootstrap-3.
npx skillsauth add microsoft/power-platform-skills migrate-bootstrapInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Plugin check: Run
node "${PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
Guide the user through migrating a traditional / native Power Pages site from Bootstrap 3
to Bootstrap 5. Follow a systematic approach: verify tooling, acquire and back up the site,
assess scope, run the pac pages bootstrap-migrate engine, review the change report, apply
the residual fixes the engine can only flag, upload (which auto-enables the runtime flag),
verify, and validate.
*.webtemplate.source.html, *.html/.aspx/.ascx, *.css, sitesetting.yml). It does NOT
apply to code sites (React/Vue/Angular/Astro) — they are never Bootstrap-3-based. If the target
is a code site, stop and tell the user this skill doesn't apply.pac pages bootstrap-migrate deterministically
applies the well-known class renames. The skill's value is assisting with the hierarchy / CSS
changes the engine only logs (see references/bootstrap-v5-manual-fixes.md).<folder>V5 copy and never
edits the source in place. Still, snapshot the source folder (git) before doing anything.pac-log.txt: pac pages upload flips the server-side
Bootstrap 5 flag automatically, but flip outcomes are logged and swallowed by the CLI. Never
trust the upload exit code alone. The authoritative record is the PAC diagnostic log (pac telemetry status → pac-log.txt); grep it for BootstrapV5UploadPostProcessor. The flip requires an
active portal — a website record with no provisioned site logs no portal found and skips.Initial request: $ARGUMENTS
Goal: Ensure PAC CLI is installed, the user is authenticated, the target environment is confirmed, and the required commands are available.
Actions:
pac help to confirm PAC CLI is installed and on PATH. If missing, point the user to
https://aka.ms/PowerPlatformCLI (dotnet tool install --global Microsoft.PowerApps.CLI.Tool)
and re-verify.pac auth who. If not authenticated, ask for the environment URL via AskUserQuestion,
then pac auth create --environment "<URL>" and re-verify. Capture the environment name, URL,
and ID.🚦 Gate (consent · migrate-bootstrap:1.confirm-env): Confirm the target environment before any download, migration, or upload. Running the migration against the wrong environment is destructive, so this confirmation is mandatory.
Confirm the target environment with the user (AskUserQuestion: use this environment / choose
another via pac org list + pac org select).
Probe command availability — both verbs are required:
pac pages help
Confirm bootstrap-migrate and upload appear in the verb list. If either is missing (feature
not enabled in this CLI build / tenant), stop and tell the user the migration can't proceed
until those commands are available.
Locate the PAC diagnostic log — capture the path now; you will need it in Phase 7 to verify the flag flip:
pac telemetry status
It prints The diagnostic logs can be found at: <…>\logs\pac-log.txt. This is a rolling log
across all pac runs and is the authoritative record of the Bootstrap V5 flag flip. Note:
pac pages upload writes no per-folder log — pac-log.txt is the only place its post-processor
records the flip result. Set PAC_LOG = <that path>.
Output: PAC CLI verified, authenticated session, confirmed environment, bootstrap-migrate
and upload confirmed available, PAC_LOG path captured.
Goal: Get a local copy of the traditional site to migrate, and establish a restore point.
Actions:
If the user provided a local site folder path (or $ARGUMENTS names one): verify it exists
and looks like a downloaded traditional site (contains website.yml and sitesetting.yml). Use it
as SITE_FOLDER.
Otherwise: list available websites and download the chosen one.
pac pages list
🚦 Gate (plan · migrate-bootstrap:2.1.select-site): Choose which website to download when more than one exists. Canceling leaves nothing changed.
Present the websites via AskUserQuestion, then download the selected site:
pac pages download --path "<SITE_FOLDER>" --webSiteId "<WEBSITE_ID>"
Use the downloaded directory as SITE_FOLDER.
Confirm this is a traditional site, not a code site. A code-site project has
powerpages.config.jsonand a frameworkpackage.json; a traditional site haswebsite.yml,sitesetting.yml, and*.webtemplate.source.htmlfiles. If it's a code site, stop — this skill does not apply.
pac pages list shows website records (adx_website). A website record can exist with no
provisioned/active Power Pages site (portal) behind it — created by data import/clone, deactivated,
or orphaned. The Phase 7 flag flip (SetPortalBootstrapV5Enabled) targets the portal, not the
website record, so if no active portal exists the flip silently skips and the live site won't
render — and you won't find this out until after uploading unless you check now.
Resolve the site URL from websitebinding.yml (adx_sitename) and probe the final status code
(-L follows redirects so a sign-in / canonical-host redirect resolves to its real code):
curl -sL -o /dev/null -w "%{http_code}" "https://<adx_sitename>/"
2xx or a redirect that resolves to 2xx/3xx → active portal; proceed. A private or
protected site legitimately redirects to sign-in or a canonical host, so a resolved 3xx is
active, not a failure.
4xx/5xx → inconclusive from the status alone. A private site can return a non-200 without
being unactivated, so do not conclude "not activated" from the code alone — fetch the error
body (or open the URL in a browser) to look for the unactivated signature:
curl -sL "https://<adx_sitename>/" | head -c 4000
If the body shows a Dataverse-connection null-ref (Object reference not set … /
CrmOnlineOrganizationService.ToOrganizationService), or the URL fails to render a portal page in
the browser, the site is most likely NOT activated. This is the same condition that makes the
Phase 7 flag flip log no portal found for website <id> via Power Pages API. A 4xx/5xx without
that signature is more likely an auth wall or transient error on an active site — treat as active and
note it for the user rather than blocking.
If the site is confirmed not active, stop and tell the user to activate/provision it first (Power
Pages admin center, or the /power-pages:activate-site skill). Migrating content into an unactivated
website still uploads, but the runtime flag can't flip and the site can't be verified.
Create a restore point before any migration:
cd "<SITE_FOLDER>"
git init -q && git add -A && git commit -q -m "Pre-migration snapshot (Bootstrap 3)"
If the folder is already a git repo, just commit any pending changes so the pre-migration state is captured.
Output: SITE_FOLDER resolved, confirmed traditional, source snapshot committed.
Goal: Set expectations by inventorying Bootstrap-3 usage and flagging risk areas before running the engine.
Actions:
Scan the site for Bootstrap-3 markers using Grep over *.html, *.aspx, *.ascx, *.css:
panel, navbar-header, img-responsive, btn-block, pull-left|pull-right,
col-(xs|sm|md|lg)-, glyphicon, label-, page-header, pager, data-toggle|data-dismiss.Flag risk areas:
.css files with non-Bootstrap selectors) — won't be auto-converted.{% ... %} around classes, conditional dropdowns) — needs review.Present a concise scope summary and set expectations:
"This is an assisted migration. The engine will auto-apply the common Bootstrap 3→5 class renames. Some changes (grid hierarchy, navbar structure, panel/page-header/pager styling) can only be flagged — I'll help apply those in Phase 6. Visual parity isn't guaranteed and you'll want to QA the result."
Output: Scope summary presented; user understands this is assisted, not push-button.
Goal: Produce the migrated <folder>V5 copy.
Actions:
<!-- gate: migrate-bootstrap:4.run-engine | category=consent | cancel-leaves=nothing -->🚦 Gate (consent · migrate-bootstrap:4.run-engine): Explicit consent before running
pac pages bootstrap-migrate. The engine writes a new<SITE_FOLDER>V5copy and never edits the source, so canceling leaves nothing changed.
Get explicit consent to run the engine (AskUserQuestion: "Run the Bootstrap 5 migration on
<SITE_FOLDER>? This creates a new <SITE_FOLDER>V5 copy and does not modify the original.").
Run:
pac pages bootstrap-migrate --path "<SITE_FOLDER>"
Confirm the engine produced <SITE_FOLDER>V5 containing:
*.html / *.js / *.aspx / *.ascx / *.css filesbootstrap.min.css (Bootstrap 5)sitesetting.yml with a Site/BootstrapV5Enabled recordlogs.txt and per-file *-diff.jsonSee references/migration-engine-reference.md for the
full output contract. Set MIGRATED_FOLDER = <SITE_FOLDER>V5.
Output: MIGRATED_FOLDER produced with rewritten files, swapped CSS, logs.txt, and diffs.
Goal: Turn logs.txt into a structured, grouped summary that surfaces the manual work.
Actions:
Read <MIGRATED_FOLDER>/logs.txt and categorize per the format documented in the engine
reference. The report opens with three file lists — no change, auto-applied
(Replacement/Addition/Deletion), and hierarchy changes (logged only) — followed by a
per-file detail block (Total Number of Changes:<n> + change lines). Use Grep to count and
locate the work:
Grep for Need hierarchy change → the manual hierarchy items (often zero).Grep the migrated *.html
for surviving Bootstrap-3 markers (page-header, glyphicon, label-(info|danger|primary),
panel-(primary|success|info|warning|danger), btn-block, navbar-header). Whatever the
engine left behind is exactly the Phase 6 work.Present a summary:
panel-primary) — these are the Phase 6 work items.Output: Grouped change summary; explicit list of residual manual items.
Goal: Apply the structural / CSS fixes the engine flagged but could not safely auto-apply, in
the MIGRATED_FOLDER files.
Reference recipes: references/bootstrap-v5-manual-fixes.md
Actions:
🚦 Gate (progress · migrate-bootstrap:6.residual-fixes): Per-category consent before applying each residual fix to the
MIGRATED_FOLDERfiles. Changes are local to the V5 copy and committed per category; canceling leaves nothing outward-facing changed.
AskUserQuestion: "Apply the <category> fixes
to <N> file(s)?"). On consent:
Edit.*-diff.json to confirm you didn't disturb
auto-applied changes.git -C "<MIGRATED_FOLDER>" add -A && git -C "<MIGRATED_FOLDER>" commit -m "v5 fixes: <category>"
(init the V5 folder as a repo first if needed). Use -C "<MIGRATED_FOLDER>" on both
add and commit so they run against the V5 copy, not the current working directory.Output: Residual fixes applied (or flagged) per category, each committed; ambiguous items listed.
Goal: Upload the migrated site (which automatically flips the server-side Bootstrap 5 flag) and verify the flag actually took effect.
Actions:
The automatic flag flip is gated on two files in MIGRATED_FOLDER:
website.yml must have a valid adx_websiteid.sitesetting.yml must contain Site/BootstrapV5Enabled with value true.Verify both (Grep/Read). If either is missing, the flag flip silently no-ops — fix before uploading.
🚦 Gate (final · migrate-bootstrap:7.2.upload): Final sign-off before the first outward-facing change.
pac pages uploadpublishes the Bootstrap 5 site and auto-enables the runtime flag. Canceling before upload leaves the live site untouched.
This is the first outward-facing change. Get explicit consent (AskUserQuestion: "Upload
<MIGRATED_FOLDER> to environment <ENV_NAME>? This publishes the Bootstrap 5 site and enables the
Bootstrap 5 runtime."). On consent:
pac pages upload --path "<MIGRATED_FOLDER>"
Leave --modelVersion at its default (Standard) for a traditional site. pac pages upload uploads the
content and auto-flips the server-side Bootstrap 5 flag via its post-processor (triggered by the
Site/BootstrapV5Enabled=true setting).
Use
pac pages upload— notupload-code-site(that is the code-site path and will corrupt a traditional site).
The CLI logs and swallows flag-flip outcomes, so a successful upload does not prove the
runtime is on Bootstrap 5. The authoritative record is pac-log.txt (the PAC_LOG path captured
in Phase 1), not the live site. Check it first — it is decisive in seconds, whereas the live
site can be misleading (an unactivated site throws a 500 that has nothing to do with Bootstrap):
# grep the PAC diagnostic log for the post-processor's result (most recent run is last)
grep -iE "BootstrapV5UploadPostProcessor|SetPortalBootstrapV5Enabled" "<PAC_LOG>"
Three possible outcomes:
| Log line | Meaning | Action |
|----------|---------|--------|
| Set … BootstrapV5Enabled / success (INF) | Flip applied | ✅ Proceed; optionally confirm on the live site. |
| Skipping SetPortalBootstrapV5Enabled: no portal found for website <id> via Power Pages API (WRN) | No active portal — flip never attempted | ❌ The site isn't activated. Activate it (see Phase 2.2 / /power-pages:activate-site), then re-run pac pages upload. |
| ERR/exception from the post-processor | Flip attempted but failed (auth/HTTP) | ❌ Re-confirm 7.1 manifest + permissions, then re-run upload. |
Then corroborate on the live site (only meaningful if the log shows applied): re-render a known
page and confirm the Bootstrap 5 bundle is served, or re-confirm the portal's Bootstrap V5 enabled
state via the Power Pages management surface. If the live URL returns a 5xx with a Dataverse-connection
null-ref (CrmOnlineOrganizationService.ToOrganizationService), that is the "site not activated"
signature — match it against the log's no portal found line rather than treating it as a transient
restart.
Output: Migrated site uploaded; flag-flip outcome read from pac-log.txt (applied / skipped-no-portal
/ errored) and, when applied, confirmed on the live site.
Goal: Confirm the live site works on Bootstrap 5 and record the outcome.
Actions:
/test-site against the site URL to crawl key pages, verify they
render, and capture console/network errors.${PLUGIN_ROOT}/references/skill-tracking-reference.md, passing
--projectRoot "<MIGRATED_FOLDER>" (the folder containing powerpages.config.json) and
--skillName "MigrateBootstrap". Tracking only writes when the project is a code site
(.powerpages-site/site-settings/ exists); for a traditional/native download the script exits
silently as a no-op — that is expected, so call it unconditionally and don't treat the no-op as an
error. If tracking files are written, include them in the final commit.MIGRATED_FOLDER./test-site re-runs, and (if promoting across
environments) the ALM skills (/plan-alm).Output: Site validated on Bootstrap 5, usage recorded, summary + residual list presented.
Before starting Phase 1, create a task list with all phases using TaskCreate:
| Task subject | activeForm | Description |
|-------------|------------|-------------|
| Verify prerequisites | Verifying prerequisites | PAC CLI, auth, environment, and command availability (bootstrap-migrate, upload) |
| Acquire site and back up | Acquiring site | Download/locate the traditional site; snapshot the source |
| Pre-migration assessment | Assessing scope | Inventory Bootstrap-3 usage; flag custom CSS / Liquid risk |
| Run migration engine | Running migration | pac pages bootstrap-migrate → <folder>V5 |
| Review change report | Reviewing report | Parse logs.txt; group auto-applied vs hierarchy/manual |
| Apply residual fixes | Applying fixes | Per-category assisted fixes for the flagged hierarchy/CSS items |
| Upload and verify flag | Uploading and verifying | pac pages upload; verify the Bootstrap 5 runtime flag flipped |
| Validate and summarize | Validating | /test-site, visual check, record usage, summary |
Mark each task in_progress when starting and completed when done via TaskUpdate.
Begin with Phase 1: Verify Prerequisites
development
(Preview) Builds and edits a model-driven Power Apps app from a natural-language intent — tables, columns, relationships, adaptive forms with sub-grids, views, Choice-column charts, generative page intents for overview/dashboard surfaces (page `.tsx` generated in generate-pages after plan approval), and an app module + sitemap — via the headless cds-maker-sdk. Runs an interactive, multi-turn authoring flow (env selection, jobs-to-be-done first, then design-only App Spec authoring across confirmed levels, guardrail lint, plan-mode approval, generate-pages, full build) and a narrated build, and can download a deployed app back into an editable spec to change it. Use when the user says "build an app for X", "create a model-driven app", "make me an app to manage Y", or "edit/add to my app". This skill stands alone and does not require /genpage — but for a standalone generative page added to an app that already exists, use /genpage instead.
data-ai
Use when the user wants to enable offline mode for a Power Apps mobile app and create a Mobile Offline Profile in Dataverse — designs per-table row scope, relationships, columns, and sync frequency through a 3-gate approval flow.
data-ai
Use when the user wants to design or redesign the Dataverse schema and connector plan for an existing mobile app, or has an ER diagram (image, Mermaid, or text) to apply. Skip when the user is creating a brand-new app — /create-mobile-app handles the data model inline.
tools
Use when the user wants to report a bug, file an issue, submit a bug report, or report any problem with the mobile-app plugin.