Use to deploy, publish, or push an Expo/React Native Power Apps mobile app to a Power Platform tenant.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add microsoft/power-platform-skills --skill deploy --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Deploy?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/microsoft-deploy-power-platform-skills)More formats (shields.io, HTML) on the badges page.
---
name: deploy
description: Use to deploy, publish, or push an Expo/React Native Power Apps mobile app to a Power Platform tenant.
user-invocable: true
allowed-tools: Read, Glob, Bash, AskUserQuestion, Skill
model: sonnet
---
**📋 Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** — read first.
# Deploy
Builds the mobile app in the current directory and pushes it to the Power Platform environment recorded in `power.config.json`.
This skill uses the standard 4-step deployment flow for this plugin: check memory bank, build, deploy, then update memory bank.
## Out of scope (deliberately)
- `expo run:ios` / `expo run:android` — local native compile is the user's choice; run your platform-specific native command directly when ready.
- OTA updates and store distribution — out of scope for v0.
- Starting Metro for local dev — run `npm run dev` (= `expo start`) directly.
## Workflow
1. Check memory bank → 2. Build → 2.5 Offline profile coverage gate → 3. Deploy → 4. Update memory bank
---
### Step 1 — Check memory bank
Read `memory-bank.md` from the project root if present. Capture:
- Project name
- Environment (id + display name)
- Current version
If absent, continue — the project may have been created without the plugin. Re-derive env from `power.config.json` if needed.
### Step 2 — Build
**Print before starting:**
> "→ Building production web bundle via `npm run build` (= `expo export --platform web`). ~30–90 seconds."
First regenerate `connectorSchemas.ts` so `app/_layout.tsx`'s `schemaMap` import reflects every connector currently in `.power/schemas/`. The npm `prestart`/`preandroid`/`preios` hooks cover dev runs, but `npm run build` does **not** — if a connector was added since the last `npm run dev`, the bundled JS would ship a stale schema map. Always regenerate before build:
```bash
npm run generate-schemas
npm run build
```
If `package.json` has no `build` script, fall back to:
```bash
npx expo export --platform web
```
(That's what the upstream template's `build` script runs.)
If the build fails:
- **`TS6133` (unused import)** → remove the import and retry once.
- **Other TypeScript errors** → report file + line and STOP. Don't deploy a broken build.
- **Metro bundler errors** → surface the full stack and STOP.
Verify `dist/` exists with `index.html` before continuing.
### Step 2.5 — Offline profile coverage gate
This is the final chance to catch schema that never made it into the Mobile Offline Profile before it ships — a table added to the data model but not the profile never syncs to devices, and a new column arrives blank offline. Validate that every schema change is covered **before** pushing.
Run the local, no-network delta check (`.datamodel-manifest.json` vs `offline-profile.json`):
```bash
node "${PLUGIN_ROOT}/scripts/offline-profile-delta.js"
```
Branch on the JSON `status` (full contract in [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md)):
| `status` | Action |
|---|---|
| `no-manifest` | Connectors-only app — no Dataverse schema. Continue to Step 3 silently. |
| `no-profile` | No offline profile in this project. Print one line: `↷ No offline profile — skipping offline coverage check. Run /setup-offline-profile if you want offline support.` Continue to Step 3. |
| `in-sync` | Print `✓ Offline profile covers all schema changes.` Continue to Step 3. |
| `error` | `offline-profile.json` is unreadable — the script prints `status: error` and **exits non-zero**. Offline coverage can't be validated against a corrupt file, so **STOP before pushing**: surface the `error` string and have the user fix `offline-profile.json` and re-run, or type the `deploy without offline` override (below) to push anyway. |
| `delta` | **STOP before pushing.** See below. |
**On `delta`** — print the uncovered schema, then gate with `AskUserQuestion`:
```
⚠ The offline profile is missing schema changes. If you deploy now, these won't be
available on disconnected devices:
Tables not in the profile : <missingTables[].logicalName>
Tables with new columns : <tablesWithNewColumns[].logicalName (newColumns)>
```
Options:
- **Update the offline profile now (recommended)** — read and execute `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md` for each `missingTables[]` entry (or once with `--all-new`), then read and execute `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` with `--table <t> --columns add:<newColumns>` for each `tablesWithNewColumns[]` entry. Follow the ordering in the reconciliation reference, then re-run the delta check; when it reports `in-sync`, continue to Step 3.
- **Deploy anyway** — requires an explicit override. Wait for the exact phrase `deploy without offline` (case-insensitive); a bare `y`/`yes` is not enough, mirroring the environment-mismatch gate in Step 3. Then continue to Step 3 and note the skipped reconciliation in the Step 4 build-history row.
Do not push until the gate is resolved (reconciled to `in-sync`, or explicitly overridden).
### Step 3 — Deploy
**Resolve and confirm the target environment FIRST.** `npx power-apps push` deploys to the environment configured in `power.config.json`. Resolve that ID to a Dataverse URL so the user catches drift before pushing.
Run:
```bash
ENV_ID=$(node -e "console.log(require('./power.config.json').environmentId)")
node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$ENV_ID"
```
From `resolve-environment.js` capture the **Environment URL** (e.g. `https://contoso.crm.dynamics.com/`), **Environment ID**, and **Tenant ID**. Cross-check against `memory-bank.md` / `power.config.json`:
- **Match** → proceed to the confirmation prompt below.
- **Mismatch** → STOP. Surface both values side-by-side and ask the user to either (a) update `power.config.json` by re-running init in the intended app root, or (b) explicitly type `override` to push to the environment already recorded in `power.config.json`. Do not proceed on a bare `y`.
- **Cannot resolve/authenticate** → STOP with `az login --tenant <env-tenant>` instructions, or ask the user to provide the environment URL directly.
**Print before starting:**
> "→ Pushing bundle to Power Platform via `npx power-apps push`. ~30–60 seconds."
Confirm with the user using the **resolved env URL, not just the friendly name**:
> "Ready to deploy to **<env-name>** (`<env-url>`)? This will update the live app for every user in that environment. Type `yes deploy to <env-name>` to confirm."
Wait for the exact phrase `yes deploy to <env-name>` (case-insensitive, env-name matching). A bare `y` / `yes` is not enough — too easy to fire on autopilot when the wrong env is active. Then:
```bash
npx power-apps push --non-interactive
```
Capture the app URL from the output if printed.
If deploy fails, report the error and STOP — do not retry silently. Common fixes:
| Error | Fix |
|---|---|
| `npx power-apps push` auth error, wrong user, or multiple accounts | Follow shared-instructions command-failure handling. `az login` / `az account set` does not switch the standalone Power Apps CLI account. |
| Environment mismatch | Re-run `npx power-apps init -t MobileApp --display-name <name> --environment-id <id> --non-interactive` in a fresh/app root for the intended target|
| `npx power-apps push` not recognised | Run `npm install` in the project so `@microsoft/power-apps` provides the CLI, or install `@microsoft/power-apps-cli` only as a last-resort prerequisite after user confirmation. |
### Step 4 — Update memory bank
If `memory-bank.md` exists, increment the version (`v1.0.0` → `v1.1.0`) and update:
- Current version
- Last deployed timestamp
- App URL (if captured)
- Append a row to the **Build history** section: `| v1.1.0 | <timestamp> | deploy | success |`
Print the summary card:
```
✅ Deploy — <project-name>
─────────────────────────────────────────────
Version : <new-version>
Environment : <env-name>
App URL : <url or "see make.powerapps.com">
Bundle path : dist/
Local dev: npm run dev (= expo start, QR for native dev clients)
Re-deploy: /deploy
List conns: /list-connections
─────────────────────────────────────────────
```
---
## Local dev (out of scope for this skill — for reference only)
When the user wants to iterate locally, they run **directly**:
```bash
npm run dev # = expo start → Metro + QR for native dev clients
```
This launches Metro and prints a QR code. They can:
- Scan the QR with the installed native dev client
- Press `r` to reload, `j` to open the debugger, `m` for the dev menu
Runtime debugging for this plugin uses `/debug-app` with native dev-client sessions and Metro terminal logs. Do not use React Native Web, browser automation, direct Metro/localhost HTTP probes, or screen-by-screen runtime checks.
If they want to compile a native binary locally, they run the platform-specific native command directly. Local native compile and manual device testing are user-owned and are not deployment gates for this skill.
## Reference
- [`shared/version-check.md`](${PLUGIN_ROOT}/shared/version-check.md) — min versions (only Always-required tier matters here)
- [`shared/memory-bank.md`](${PLUGIN_ROOT}/shared/memory-bank.md) — Build history schema
- [`shared/references/offline-profile-reconciliation.md`](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) — Step 2.5 offline coverage gate
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!