Plugin check: Run
node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
setup-pipeline
Sets up a Power Platform Pipeline for automated Power Pages solution deployments. Creates the pipeline configuration directly in Dataverse using the PP Pipelines OData API — no YAML files, no external CI/CD infrastructure needed.
GitHub Actions and Azure DevOps Pipeline options are shown in the platform menu as coming soon.
Refer to
${CLAUDE_PLUGIN_ROOT}/references/cicd-pipeline-patterns.mdfor all HAR-confirmed API patterns used in this skill.
Prerequisites
powerpages.config.jsonexists in the project root.solution-manifest.jsonexists (solution must be created first viasetup-solution)- Azure CLI logged in (
az account showsucceeds) - PAC CLI logged in (
pac env whosucceeds) - A Power Platform environment with Pipelines package installed (the "host" environment)
Phases
Phase 0 — ALM plan gate
plan-almis the front door. When the user expresses an ALM intent (promote / ship / deploy / set up CI-CD / move to staging / push to prod), the orchestrator (/power-pages:plan-alm) should run first. This Phase 0 enforces that and is meant to fail closed when there's no plan, not to be a one-time check the user can dismiss forever.
Skip rule. If this skill was invoked as part of an active plan-alm orchestration, skip Phase 0 entirely and proceed to Phase 1. The gate helper exposes this via its inExecution block — pass through silently to Phase 1 when:
inExecution.status === "active"
The helper computes this from docs/.alm-plan-data.json — PLAN_STATUS === "In Execution" AND LAST_INVOCATION_AT within the last 60 minutes. check-alm-plan.js refreshes LAST_INVOCATION_AT automatically on every invocation that finds the plan in execution, so each in-chain skill keeps the chain alive for the next one — even multi-hour deploys (deploy-pipeline alone can take 60 min per stage) survive the window without the chain incorrectly de-classifying. Stalled chains (no heartbeat for > 60 min) reclassify as stale-heartbeat and Phase 0 gates fire normally so an abandoned plan doesn't silently bypass user confirmation.
When inExecution.status is anything other than "active" ("not-running", "stale-heartbeat", "no-plan"), run the Phase 0 gate flow below. Branch on the remaining helper fields:
Step 1 — Run the gate helper.
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" \
--projectRoot "." \
--envUrl "{devEnvUrl}" \
--token "{token}" \
--solutionId "{solutionId from .solution-manifest.json, if available}"
The helper returns JSON with { exists, stale, staleness: { reason, detail }, generatedAt, planStatus, ... }. The freshness check requires env credentials + solutionId; without those the helper does an existence-only check.
Step 2 — Branch on the result.
| Result | Behavior |
|---|---|
deferred: true | The user has explicitly deferred ALM for this project (.alm-deferred marker present). Pass through silently to Phase 1 — do not nag. |
exists: false | The user hasn't run plan-alm yet. See Step 3. |
exists: true, stale: false | Plan is current. Pass through silently to Phase 1. |
exists: true, stale: true (reason: solution-modified) | The solution changed after the plan was generated. See Step 4. |
Step 3 — No plan. Tell the user:
<!-- gate: setup-pipeline:0.no-plan | category=intent | cancel-leaves=nothing -->"No ALM plan exists for this project.
/power-pages:plan-almbuilds one — it detects the project state, asks about your promotion strategy (PP Pipelines vs Manual export/import), and orchestrates the right skills (including this one) in the right order. Want me to run plan-alm now?"
🚦 Gate (intent · setup-pipeline:0.no-plan): Fail-closed entry gate when
check-alm-plan.jsreturnsexists:false. Helper-script-backed.
AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Run /power-pages:plan-alm first? | ALM plan gate | Yes — run /power-pages:plan-alm now (Recommended), Continue without a plan (advanced — I know what I'm doing), Cancel |
- Yes (Recommended) → invoke
/power-pages:plan-alm. plan-alm's Phase 7 dispatches back into this skill at the appropriate stage. - Continue without a plan → set
BYPASSED_PLAN_GATE = trueand proceed to Phase 1. - Cancel → exit cleanly.
Step 4 — Stale plan. Tell the user:
<!-- gate: setup-pipeline:0.stale-plan | category=intent | cancel-leaves=nothing -->"ALM plan exists from
{generatedAt}but the source solution has been modified since (at{solution.modifiedon}). Components may have changed. Re-runningplan-almwill refresh the analysis and the rendered HTML."
🚦 Gate (intent · setup-pipeline:0.stale-plan): Fail-closed entry gate when
check-alm-plan.jsreturnsstale:true. Helper-script-backed.
AskUserQuestion:
| Question | Header | Options |
|---|---|---|
| Refresh the plan first? | ALM plan freshness | Refresh — re-run /power-pages:plan-alm (Recommended), Continue with the existing plan, Cancel |
- Refresh (Recommended) → invoke
/power-pages:plan-alm. After completion, re-run the Phase 0 helper once to confirm freshness; if still stale, surface the detail and proceed to Phase 1 anyway (don't infinite-loop). - Continue → set
STALE_PLAN_ACK = trueand proceed to Phase 1. - Cancel → exit cleanly.
Why this gate exists. Direct invocation of this skill bypasses the orchestrator's pre-deploy completeness check, host-resolution decision, deployment-strategy selection, and rendered HTML plan. Users who run setup-pipeline directly often miss components that should have been added to the solution, miss the asset advisory for large web files, or build a pipeline against the wrong host environment. The gate ensures plan-alm either ran (so all of those decisions are surfaced and recorded) or the user explicitly chose to bypass it.
Phase 1 — Detect Project Context
Create all tasks upfront at the start of this phase.
Tasks to create:
- "Detect project context"
- "Select CI/CD platform"
- "Confirm pipeline configuration"
- "Run preflight checks"
- "Create deployment environments"
- "Create pipeline and stages"
- "Verify and write artifacts"
Steps:
-
Read project context using
detect-project-context.js:node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/detect-project-context.js"Capture output as JSON; extract
.siteName(store assiteName),.websiteRecordId,.environmentUrl(store asdevEnvUrl), and.solutionManifest(store assolutionManifest). IfsiteNameis absent (nopowerpages.config.json), stop and advise running/power-pages:create-sitefirst. IfsolutionManifestis null (no.solution-manifest.json), stop and advise running/power-pages:setup-solutionfirst.Manifest version check:
- If
solutionManifest.schemaVersion === 2(multi-solution layout), setMULTI_SOLUTION_MODE = trueand storesolutionManifest.solutions[]asSOLUTIONS_LIST. See Phase 6b — a SINGLE pipeline ships all solutions through per-solution stage runs (the pre-v1.3.x "one pipeline per solution" layout was reverted because it cluttered the Pipelines UI). - If
schemaVersionis absent or1(single solution), readsolutionManifest.solution.uniqueNameandsolutionManifest.solution.solutionId. One pipeline will be created (existing flow).
- If
-
Run
verify-alm-prerequisites.jsto confirm PAC CLI auth, acquire a token, and verify API access:node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --envUrl "{devEnvUrl}"Capture output as JSON; extract
.envUrl(use to confirmdevEnvUrl) and.token(store asDEV_TOKEN). -
Run silently:
pac env list --output json 2>/dev/nullStore output as
ENV_LIST.