Skill v1.0.0
Trusted Publisher100/100version: "1.0.0" name: migrate-bootstrap description: >- 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. user-invocable: true argument-hint: Optional website name or local site folder path allowed-tools: Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion, Skill, Task, TaskCreate, TaskUpdate, TaskList model: opus
Plugin check: Runnode "${PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
Migrate a Traditional Power Pages Site from Bootstrap 3 to Bootstrap 5
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.
Core Principles
- Traditional sites only: This skill targets traditional Power Pages sites (Liquid web templates,
*.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.
- The engine does the bulk; you do the residual:
pac pages bootstrap-migratedeterministically
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).
- Non-destructive, with a restore point: The engine writes a new `<folder>V5` copy and never
edits the source in place. Still, snapshot the source folder (git) before doing anything.
- Assist and flag — never silently rewrite: Residual fixes are Liquid-aware and can break layouts.
Apply them with per-category consent; when a case is ambiguous (e.g. Liquid conditionals), flag it for the user instead of guessing.
- Verify the runtime flag flip via `pac-log.txt`:
pac pages uploadflips 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.
- Use TaskCreate/TaskUpdate: Create the todo list upfront with all 8 phases before starting work.
Initial request: $ARGUMENTS
Phase 1: Verify Prerequisites
Goal: Ensure PAC CLI is installed, the user is authenticated, the target environment is confirmed, and the required commands are available.
Actions:
- Create the todo list with all 8 phases (see Progress Tracking).
- Run
pac helpto 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. <!-- not-a-gate: data-gathering — free-text environment URL when PAC CLI isn't authenticated; the prompt itself writes nothing -->
- Run
pac auth who. If not authenticated, ask for the environment URL viaAskUserQuestion,
then pac auth create --environment "<URL>" and re-verify. Capture the environment name, URL, and ID.
<!-- gate: migrate-bootstrap:1.confirm-env | category=consent | cancel-leaves=nothing -->
🚦 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:
``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.
- Locate the PAC diagnostic log — capture the path now; you will need it in Phase 7 to verify
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.
Phase 2: Acquire the Site & Back Up
Goal: Get a local copy of the traditional site to migrate, and establish a restore point.
Actions:
2.1 Locate or download the site
- If the user provided a local site folder path (or
$ARGUMENTSnames 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.
``bash pac pages list ``
<!-- gate: migrate-bootstrap:2.1.select-site | category=plan | cancel-leaves=nothing -->
> 🚦 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 "<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 haspowerpages.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.
2.2 Confirm the site is ACTIVATED (not just a website record)
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-
200without
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:
``bash 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.
2.3 Snapshot the source
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.
Phase 3: Pre-Migration Assessment
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
Grepover*.html,*.aspx,*.ascx,*.css:
- Component classes:
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.
- Count affected files and the rough number of occurrences per category.
- Flag risk areas:
- Heavy custom CSS (large
.cssfiles with non-Bootstrap selectors) — won't be auto-converted. - Liquid-entangled markup (
{% ... %}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.
Phase 4: Run the Migration Engine
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 runningpac 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:
``bash pac pages bootstrap-migrate --path "<SITE_FOLDER>" ``
- Confirm the engine produced `<SITE_FOLDER>V5` containing:
- Rewritten
*.html / *.js / *.aspx / *.ascx / *.cssfiles - Swapped
bootstrap.min.css(Bootstrap 5) - Updated
sitesetting.ymlwith aSite/BootstrapV5Enabledrecord logs.txtand per-file*-diff.json
See 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.
Phase 5: Review the Change Report
Goal: Turn logs.txt into a structured, grouped summary that surfaces the manual work.
Actions:
- Read
<MIGRATED_FOLDER>/logs.txtand 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:
GrepforNeed hierarchy change→ the manual hierarchy items (often zero).- The most reliable residual signal is the V5 output itself:
Grepthe 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:
- Counts per category.
- The list of files with hierarchy changes and CSS-dependent contextual classes (e.g.
panel-primary) — these are the Phase 6 work items.
Output: Grouped change summary; explicit list of residual manual items.
Phase 6: AI-Assisted Residual Fixes
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:
- Group the residual items by category (grid hierarchy, navbar structure, panel/card styling,
page-header, pager, btn-block, Liquid edge cases, partial paths). <!-- gate: migrate-bootstrap:6.residual-fixes | category=progress | cancel-leaves=nothing -->
🚦 Gate (progress · migrate-bootstrap:6.residual-fixes): Per-category consent before applying each residual fix to theMIGRATED_FOLDERfiles. Changes are local to the V5 copy and committed per category; canceling leaves nothing outward-facing changed.
- For each category, get per-category consent (
AskUserQuestion: "Apply the<category>fixes
to <N> file(s)?"). On consent:
- Apply the recipe from the manual-fixes reference using
Edit. - Re-check the affected lines against the file's
*-diff.jsonto confirm you didn't disturb
auto-applied changes.
- Commit per category:
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.
- For ambiguous / Liquid-entangled cases, do not rewrite — list them for the user with the
file and line and a suggested manual change.
Output: Residual fixes applied (or flagged) per category, each committed; ambiguous items listed.
Phase 7: Upload, Auto-Enable Runtime Flag, and Verify
Goal: Upload the migrated site (which automatically flips the server-side Bootstrap 5 flag) and verify the flag actually took effect.
Actions:
7.1 Pre-flight the manifest
The automatic flag flip is gated on two files in MIGRATED_FOLDER:
website.ymlmust have a validadx_websiteid.sitesetting.ymlmust containSite/BootstrapV5Enabledwith valuetrue.
Verify both (Grep/Read). If either is missing, the flag flip silently no-ops — fix before uploading.
7.2 Final consent gate, then upload
<!-- gate: migrate-bootstrap:7.2.upload | category=final | cancel-leaves=nothing -->
🚦 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).
Usepac pages upload— notupload-code-site(that is the code-site path and will corrupt atraditional site).
7.3 Verify the flag flip (mandatory)
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.
Phase 8: Validate & Summarize
Goal: Confirm the live site works on Bootstrap 5 and record the outcome.
Actions:
- Runtime smoke test — invoke
/test-siteagainst the site URL to crawl key pages, verify they
render, and capture console/network errors.
- Before/after visual check — spot-check the highest-traffic pages (home, navbar, any
panels/cards, forms, pagination) for layout regressions introduced by the migration.
- Record skill usage — follow
${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.
- Summary — present:
- Files changed per category (auto-applied vs assisted vs flagged-for-manual).
- Residual items that still need human attention.
- Confirmation the Bootstrap 5 runtime flag is enabled.
- Location of the source snapshot (for rollback) and
MIGRATED_FOLDER.
- Suggest next steps — manual QA pass,
/test-sitere-runs, and (if promoting across
environments) the ALM skills (/plan-alm).
Output: Site validated on Bootstrap 5, usage recorded, summary + residual list presented.
Important Notes
Throughout All Phases
- Use TaskCreate/TaskUpdate to track progress at every phase.
- Ask for user confirmation at the key decision points (below).
- Present errors clearly — show the relevant command output and explain it before suggesting fixes.
- No automated rollback after upload — rely on the Phase 2 source snapshot; report failures and
continue/triage rather than auto-reverting.
Key Decision Points (Wait for User)
- Phase 1: If not authenticated, get environment URL; confirm/switch target environment.
- Phase 2: If multiple sites, which website to download; confirm traditional (not code) site.
- Phase 4: Consent to run the migration engine.
- Phase 6: Per-category consent before applying residual fixes.
- Phase 7: Final consent before uploading (outward-facing).
Progress Tracking
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