HTTPS MITM proxy for PCF development against live Dataverse pages.
By default it intercepts control assets and serves local build output.
With --hot, it also enables deterministic in-place PCF control hot-reload (no full page refresh).
npx pcf-dev-proxyThis auto-detects ControlManifest.Input.xml, resolves cc_<namespace>.<constructor>, and serves files from out/controls/<constructor>.
npx pcf-dev-proxy --hotHot mode injects an HMR client into bundle.js — any browser connecting through the proxy gets live reload automatically. It adds:
- local HMR control plane on
127.0.0.1(default port8643) - direct WebSocket from page to control plane
- in-page runtime instrumentation for PCF instance swap
- CSP header stripping on passthrough responses (hot mode only)
Primary workflow is explicit trigger from build completion:
npx pcf-dev-proxy reload \
--control cc_Contoso.MyControl \
--trigger pcf-start \
--build-id "$(date -u +%Y-%m-%dT%H:%M:%SZ)"Optional fallback watcher:
npx pcf-dev-proxy --hot --watch-bundleWhen hot mode is enabled (--hot):
GET /healthPOST /reloadPOST /ackGET /last-ack
Example reload payload:
{ "controlName": "cc_Contoso.MyControl", "buildId": "2026-02-27T12:34:56.789Z", "trigger": "pcf-start" }Example ACK payload from runtime:
{ "id": "r-123", "controlName": "cc_Contoso.MyControl", "buildId": "2026-02-27T12:34:56.789Z", "status": "success", "instancesTotal": 2, "instancesReloaded": 2, "durationMs": 184 }# start proxy
npx pcf-dev-proxy [options]
# enqueue a reload (no proxy startup)
npx pcf-dev-proxy reload --control <name> [options]Options:
--port <number>Proxy port (default8642)--ws-port <number>HMR control plane port (default8643)--dir <path>Directory to serve files from--control <name>Override control name--hotEnable hot mode (default: on). Injects HMR client intobundle.jsso any browser through the proxy gets live reload.--no-hotDisable hot mode--watch-bundleWatchbundle.jsand auto-trigger reload (hot mode only)
Reload subcommand options:
--ws-port <number>Control plane port--control <name>Required control name--build-id <id>Build identifier--trigger <source>Trigger label--changed-files <csv>Optional changed files metadata
The proxy prints connection commands on startup. Use a dedicated profile so proxy settings don't affect your daily browsing:
# Chrome (macOS)
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--proxy-server=127.0.0.1:8642 \
--ignore-certificate-errors-spki-list=<SPKI from proxy output> \
--user-data-dir=~/.pcf-dev-proxy/chrome-profile \
--disable-session-crashed-bubbleThe profile at ~/.pcf-dev-proxy/chrome-profile is persistent — your Dataverse login, cookies, and session survive across proxy restarts.
Playwright and agent-browser work too — see the proxy startup output for copy-pasteable commands.
# terminal 1 — build watcher
pcf-start
# terminal 2 — proxy (prints connection commands)
npx pcf-dev-proxy --hot
# terminal 3 — launch browser using the command from proxy output
# terminal 4 (or post-build hook) — trigger reload after build completes
npx pcf-dev-proxy reload --control cc_Contoso.MyControl --trigger pcf-start- No reload applied: check
GET /last-ackfor latest runtime status. - ACK timeout: ensure Dataverse tab is open in a browser connected through the proxy.
- Multiple rapid builds: queue is latest-wins per control; only newest pending reload is applied.
- Node.js >= 18
- A Chromium-based browser (Chrome, Edge, Playwright, agent-browser)
- A PCF project with
ControlManifest.Input.xml
MIT