Install
$ agentstack add skill-gusbavia-fabric-apps-skills-fabric-app-bootstrap ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Fabric App Bootstrap
Stand up and deploy a Microsoft Fabric App (preview) from the CLI, including fully headless with a service principal. Distilled from a real production-shaped build; every gotcha here cost real hours.
What a Fabric App is (and is not)
One workspace item ("App", backed by an AppBackend) with three managed children:
| Service | What it gives you | |---|---| | Static hosting | your built SPA on a public URL (served from OneLake) | | SQL Database | schema generated from TypeScript @entity() decorators | | GraphQL API | data plane the client SDK talks to, behind Entra SSO |
It will NOT host: Node SSR (no Next.js server), server-held secrets (no BFF with a client secret), Python or any custom runtime. The deploy is a zip of dist/, capped at 100 MB compressed. End-user auth on a deployed app is Entra SSO only.
Porting check: if the existing app fetches server-side with a secret, that code cannot board. Visual/client code survives intact. Route the data layer through one of the companion skills.
Porting from Next.js (proven pattern, ~30 min/page): rebuild the shell as a Vite SPA, then alias next/link and next/navigation to two ~25-line shims in vite.config.ts (resolve.alias) so page components, CSS modules and shared components copy over VERBATIM with zero import rewrites. Wrap async server components in a client component with useState + useEffect. Re-export the old data layer's types and swap only the implementation.
Honest scoping of "zero clicks": steady-state deploys are fully headless. First-run is not: the App item is created in the portal, two tenant settings are admin toggles, and if the app has a database the schema apply needs one interactive owner login (see gotchas). Say this up front when planning CI.
Prerequisites
- Tenant setting "Fabric App items (preview)" enabled (Fabric admin portal)
- A workspace + an App item created in it (portal: + New item → App)
- Node 18+, a Vite SPA (React/Vue/Svelte — templates are Vite)
- Headless path only: an Entra service principal with Contributor on the workspace and tenant setting "Service principals can use Fabric APIs" = On
The recipe
# 1. one-time: bind the local project to the EXISTING Fabric item
# (records the item id in rayfin/.deployments.json so it never duplicates)
npx rayfin -y init . --project-name \
--services auth --auth-methods fabric \
--workspace-id --item-id
# 2a. interactive login (item OWNER). If the app has a DATABASE this is
# required at least once — schema applies are owner-only, the SP can't.
npx rayfin login
# 2b. headless login for CI / automation (static deploys + data plane).
# Not either/or with 2a: DB apps need 2a once, then 2b for every deploy.
# In CI, read the secret from an env var — a literal on the command line
# leaks into logs and process lists.
npx rayfin login --service-principal \
--client-id "$SP_CLIENT_ID" --client-secret "$SP_CLIENT_SECRET" --tenant "$TENANT_ID"
# 3. full deploy: build + zip + upload, prints the live URL
npx rayfin up -y
# 4. iterate (static tier only, much faster)
npx rayfin up staticapp deploy -y
Vite config that survives Fabric hosting
// vite.config.ts
export default defineConfig({
plugins: [react()],
base: "./", // relative assets — works under any URL prefix
build: { outDir: "dist" },
});
Use HashRouter (or equivalent) for routing: static hosting serves one index.html, so #/routes survive deep links and refreshes; path routes 404.
Gotchas (each one is a scar)
| Symptom | Cause / fix | |---|---| | rayfin up schema step fails 403 "Only AppBackend artifact owner" | DB schema applies are owner-only. SP cannot do it. Owner runs npx rayfin login once interactively; SP handles everything else. | | Deploy balloons / rejected | 100 MB zip cap. Convert images to WebP (sharp, quality ~82) before they bite. | | curl -I on the live URL returns 500 | Harmless platform quirk on HEAD. Verify with GET or a browser. | | Re-deploy says login expired | SP token lasts ~1h. Re-run the same rayfin login --service-principal. | | rayfin up db apply compile error TS5096 | Root tsconfig has allowImportingTsExtensions: true; the rayfin/tsconfig.json must override it to false (it needs to emit). | | Functions tier 404 on __private/functions/deploy | rayfin functions is experimental and not provisioned in every region. Keep services.functions: false until it deploys. |
Verify the deploy
GET the printed URL in a browser. Static content is publicly reachable; data calls gate on SSO. Test SDK auth flows embedded in the Fabric portal first — standalone browser tabs have flakier token refresh.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: gusbavia
- Source: gusbavia/fabric-apps-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.