| name | scaffold-code-app |
| description | Scaffold a new Power Apps Code App — clone the Vite starter template, pin the Power Apps SDK, and initialise the app against a Power Platform environment. Use when asked to "scaffold a Code App", "set up a new Power Apps Code App", or "bootstrap the Code Apps starter". |
| metadata | {"author":"chuckytesla","version":"2.0.0","argument-hint":"-n '<app display name>' -e <environment-id>"} |
Scaffold a Power Apps Code App
Bootstraps a new Power Apps Code App
in the current directory: it clones the official Vite starter, pins the Power Apps SDK, and runs
init against your Power Platform environment. The skill is a generic engine — the app name and
environment are inputs you provide (or the agent asks for); nothing project-specific is baked into
the skill.
This skill only scaffolds — it does not sign you in and it does not push the app into a solution.
Sign-in is owned by the dataverse-scaffold-tables skill (it runs npx power-apps login); pushing
the built app into a Dataverse solution is the separate deploy-code-app skill, run last.
Inputs (provide, derive, or ask)
| Input | How it's used | If unknown |
|---|
| App display name | init -n '<name>' | Ask the user. Don't invent one. |
| Power Platform environment ID | init -e <id> | Try power.config.json (environmentId) or .env; otherwise ask the user. |
Never hardcode these — they belong to the project/user, not the skill.
Steps (run in order, from the app's root directory)
1. Clone the Code Apps starter template
npx degit github:microsoft/PowerAppsCodeApps/templates/vite --force
--force overwrites files in the current directory. Run this in the intended app root. If the
directory already has work in it, confirm with the user before overwriting.
Then install dependencies:
npm install
2. Pin the Power Apps SDK to 1.2.2
Set the @microsoft/power-apps dependency in package.json to 1.2.2 (exact). Edit the
dependencies entry directly:
"@microsoft/power-apps": "1.2.2"
Then npm install again so the lockfile and node_modules match.
3. Initialise the Code App
Populate both the app display name and the environment ID. If either is unknown, ask the user —
do not guess.
npx power-apps init -n '<APP_DISPLAY_NAME>' -e "<POWER_PLATFORM_ENVIRONMENT_ID>"
This writes power.config.json (appDisplayName, environmentId, region, etc.). Interactive
sign-in is handled later by the dataverse-scaffold-tables skill — don't run power-apps login here.
4. Set up the quality tooling (make the app verifiable)
The Vite starter ships no test runner and at most a minimal linter, but every build task is gated
by npm run verify. Make the project verifiable now, before any feature work. Do not paraphrase this
list or trust a gate that merely "looks right": the gate only protects the build if it runs the
compiler, so set the scripts exactly as below.
a. Test runner. Add Vitest + React Testing Library + jsdom, wired to npm test. Add
src/setupTests.ts importing @testing-library/jest-dom, referenced from the Vitest config.
b. Linter. Add ESLint + typescript-eslint + eslint-plugin-react-hooks (plus the Vite
react-refresh plugin the starter expects), wired to npm run lint. Set the ESLint
languageOptions.ecmaVersion to 2022 to match the app's target, not the starter's 2020.
c. The gate must typecheck. Set these package.json scripts verbatim. The typecheck step is
what catches the class of error a fast model produces (reading a response by the wrong property, a
string where a Choice integer is required, a create payload missing a required field). A gate without
it passes green while the build is broken:
"typecheck": "tsc -b",
"lint": "eslint .",
"test": "vitest",
"verify": "npm run typecheck && npm run lint && npm run test -- --run"
npm run verify must run tsc. If it only runs lint and test, type errors stay hidden until
npm run build at deploy. That is a configuration failure, the exact failure mode this harness exists
to remove.
d. Keep test types out of the app build, but still typecheck the tests — ship these verbatim.
Tests must not force their globals into the production compile, yet they must still be type-checked by
the gate. Do not hand-tune these: a fast model gets the include wrong (TS6307) and the .ts-vs-.tsx
split wrong (TS1005) and loses minutes to each, every task.
-
In tsconfig.app.json, exclude the test files and keep types to app types only (no
vitest/globals):
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/setupTests.ts"]
-
Add tsconfig.test.json with this exact shape. It must include the whole src (not only the
test files): a test imports App.tsx and the generated services, and those files must be in the
project or tsc -b raises TS6307.
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.test.tsbuildinfo",
"types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", ".power/schemas/appschemas/dataSourcesInfo.ts"]
}
-
Reference both tsconfig.app.json and tsconfig.test.json from the root tsconfig.json so
tsc -b checks them in one pass.
-
Any test file containing JSX must be .tsx (e.g. smoke.test.tsx), never .test.ts: a .ts
file with <App /> fails to parse (TS1005).
This is why the app build never needs vitest/globals bolted into tsconfig.app.json: the tests get
their globals from their own project, and a wrong fixture shape still fails tsc at the gate.
e. Keep the Power Apps Vite plugin (CDN-safe asset base path). The template's vite.config.ts
includes powerApps() from @microsoft/power-apps-vite. Leave it in the plugins array. A Code
App is served from the Power Platform CDN at a non-root base path, and this plugin sets the asset base
so Vite's bundled asset URLs and images resolve once deployed. Remove it (or drop the base) and the
app's images and hashed assets 404 on the CDN. If a project ever does not use the plugin, set
base: './' in vite.config.ts instead. Images are referenced by bundled import from src/, not
from public/ by absolute path (see the instantiation's "Brand assets and wiring").
Also force binary assets to inline as base64, because power-apps push corrupts binary asset
files on upload: a deployed JPEG comes back ~1.85x larger and will not decode, even though the local
dist build is a byte-perfect image. Base64 travels in the bundle as text, which the push pipeline
does not mangle (the app's .js survives the same way). In vite.config.ts:
build: { assetsInlineLimit: 100_000_000, cssCodeSplit: false },
Keep hero/background images modest (optimise to a few hundred KB): base64 adds ~1.85x of text to the
bundle. Caveat to verify on the deployed URL: inlined assets become data: URIs, so the governed
app's CSP must allow data: in img-src/font-src. If a data: CSP violation appears, allow data:
in the Power Platform admin CSP, or use a CSS/SVG brand treatment for the hero (text-based, survives
push, needs no data:).
f. Vitest must inline the Power Apps SDK (Windows ESM subpath fix). Set the vite.config.ts test
block so Vitest processes the SDK through Vite, or it fails to resolve nested SDK subpaths on Windows
(Cannot find module '.../@microsoft/power-apps/dist/data/multiSelectPicklistUtils'). Ship it now so no
task rediscovers it:
test: {
globals: true,
environment: "jsdom",
setupFiles: "./src/setupTests.ts",
server: { deps: { inline: [/@microsoft\/power-apps/] } },
},
g. Self-host the fonts (the governed app's CSP blocks the Google Fonts CDN). Do not load Barlow
via @import url(https://fonts.googleapis.com/...). The deployed Code App runs under a strict CSP
(style-src 'self') that blocks external font CDNs, so the fonts silently fail and type renders in a
fallback (the demo's "monospace textarea" was this). Install them locally instead:
@fontsource/barlow and @fontsource/barlow-semi-condensed, importing the needed weights in
main.tsx (or drop woff2 files into src/assets/fonts and @font-face them). Everything is then
'self' and loads under any CSP. See the design contract §1.6.
h. Ship a typed mock factory so fixtures stay terse. The generated Eppc_* read models carry many
required system fields (createdbyyominame, modifiedonbehalfbyyominame, organizationidname,
and more). Hand-writing them in each fixture is the TS2739 error that recurred in every test task.
Add src/test-utils/factories.ts exporting makeSession(overrides): Eppc_sessions and
makeFeedback(overrides): Eppc_feedbacks that fill every required field with a sane default and spread
overrides last. Tests then call makeSession({ eppc_title: "..." }) and stay both typed and short.
See AGENTS.md (repo root) → Stack notes for the exact pins and the test-authoring rules (typed
fixtures, one un-mocked smoke render). After this, npm run verify runs clean on the empty app, so
the first task gate (Task 1) has a working harness to run against rather than being set up live.
After scaffolding
- Verify
power.config.json has the expected appDisplayName and environmentId.
- Confirm
package.json shows @microsoft/power-apps at 1.2.2.
- Confirm
npm run verify runs clean on the empty app, so the gate is ready before Task 1.
- Next, scaffold/seed the Dataverse tables and add them as data sources — the
dataverse-scaffold-tables skill signs you in (npx power-apps login) before it creates the
schema, then dataverse-create-records and dataverse-add-data-source wire the data in.
- To publish the app into a solution, use the separate
deploy-code-app skill (build + push) —
run it last, after the tables are scaffolded into that same solution.