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 deterministicallyapplies 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 neveredits the source in place. Still, snapshot the source folder (git) before doing anything.
Apply them with per-category consent; when a case is ambiguous (e.g. Liquid conditionals), flag it
for the user instead of guessing.
pac-log.txt: pac pages upload flips the server-sideBootstrap 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 tohttps://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.
AskUserQuestion: use this environment / chooseanother via pac org list + pac org select).
```bash
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.
the flag flip:
```bash
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:
$ARGUMENTS names one): verify it existsand looks like a downloaded traditional site (contains website.yml and sitesetting.yml). Use it
as SITE_FOLDER.
```bash
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:
```bash
pac pages download --path "
```
Use the downloaded directory as SITE_FOLDER.
Confirm this is a traditional site, not a code site. A code-site project haspowerpages.config.json
and 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 orprotected 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 withoutbeing 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:
```bash
curl -sL "https://
```
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:
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.
.css files with non-Bootstrap selectors) — won't be auto-converted.{% ... %} around classes, conditional dropdowns) — needs review."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 (consent · migrate-bootstrap:4.run-engine): Explicit consent before runningpac pages bootstrap-migrate. The engine writes a new<SITE_FOLDER>V5copy and never edits the source, so canceling leaves nothing changed.
AskUserQuestion: "Run the Bootstrap 5 migration on<SITE_FOLDER>? This creates a new <SITE_FOLDER>V5 copy and does not modify the original.").
```bash
pac pages bootstrap-migrate --path "
```
<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:
<MIGRATED_FOLDER>/logs.txt and categorize per the format documented in the enginereference. 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 *.htmlfor 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.
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:
page-header, pager, btn-block, Liquid edge cases, partial paths).
🚦 Gate (progress · migrate-bootstrap:6.residual-fixes): Per-category consent before applying each residual fix to the MIGRATED_FOLDER files. Changes are local to the V5 copy and committed per category; canceling leaves nothing outward-facing changed.
AskUserQuestion: "Apply the <category> fixesto <N> file(s)?"). On consent:
Edit.*-diff.json to confirm you didn't disturbauto-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 bothadd and commit so they run against the V5 copy, not the current working directory.
file and line and a suggested manual change.
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 upload publishes 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 theSite/BootstrapV5Enabled=true setting).
Usepac 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 theyrender, and capture console/network errors.
panels/cards, forms, pagination) for layout regressions introduced by the migration.
${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 acrossenvironments) the ALM skills (/plan-alm).
Output: Site validated on Bootstrap 5, usage recorded, summary + residual list presented.
continue/triage rather than auto-reverting.
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