powpow-cli
v0.6.0
Published
Pro-code development tool for Power Pages with TypeScript transpilation and local development server features.
Downloads
26
Maintainers
Readme
PowPow CLI
PowPow is a Power Pages pro-code development tool that streamlines the development process by offering code transpilation and a local development server. It aims to reduce development iteration time and enable the use of TypeScript for authoring website code assets.
Note: This tool is in early development and is not tested for production use. Use at own risk.
Features
- TypeScript & JSX support — Write Power Pages code in TypeScript/TSX and have it transpiled and bundled automatically
- Three resource types — Bundle web-templates, web-files, and server-logic (Power Pages server-side scripts) from a single project
- Rolldown bundler — Fast, tree-shaken, minified ES module builds powered by Rolldown
- Local dev server — Serves built assets over HTTP with CORS support for rapid iteration
- Incremental watch mode — On file changes, only the entries that actually depend on the changed file are rebuilt, and outputs whose content didn't change aren't rewritten to disk (browser refresh is still manual today; live-reload is on the roadmap)
- Zero-config globals — React, ReactDOM, jQuery, Bootstrap,
shell, andMicrosoft.Dynamic365.Portalare wired up automatically; justimport { useState } from 'react' - Bundled typings — Power Pages global typings (
Server,Microsoft,shell, plus React/jQuery/etc. ambient stubs) ship in the package; no@types/*install needed - Build-time dependency graph — Console summary flags duplicated modules across entries and shows which globals each entry uses
- Interactive CLI —
powpow initbootstraps a project end-to-end (npm/pnpm install, tsconfigs, scaffolding);powpow addwires up entry points - Companion browser extension — Use with PowPow Interceptor to live-swap portal assets during development
Prerequisites
- Node.js v22 or later
- A Power Pages portal downloaded locally via
pac powerpages download
Quick Start
1. Bootstrap a project
In the root of your project (an empty directory is fine), run:
npx powpow-cli initThis single command:
- Asks whether you want npm or pnpm (auto-detected from existing lockfiles).
- Creates
package.jsonif missing, sanitizing the package name derived from the directory. - Installs
powpow-cliandtypescriptas devDependencies. - Adds
powpow:devandpowpow:buildscripts topackage.json. - Scaffolds a starter source directory at
src/(organise it however you like — entry-point sources can live anywhere under it). - Writes three tsconfigs:
- root
tsconfig.jsonwith TypeScript projectreferencesto the two below tsconfig.web.jsonextendingpowpow-cli/presets/tsconfig.web.base.json(browser-typed, JSX, DOM lib)tsconfig.server-logic.jsonextendingpowpow-cli/presets/tsconfig.server-logic.base.json(server-typed, no DOM, no JSX)
- root
- Writes a starter
powpow.config.json.
init is idempotent — re-running on an existing project skips files that already exist.
{
"$schema": "./node_modules/powpow-cli/presets/powpow.config.schema.json",
"portalConfigPath": "my-portal",
"entryPoints": []
}2. Add entry points
Map a source file to a Power Pages resource (web template, web file, or server logic):
npx powpow addThe interactive prompt lists available portal resources, lets you pick one, and either creates a new source file or links an existing one. The new file is placed at the path you choose under sourceDir (defaulting to a filename derived from the resource's adx_name, with original casing preserved) and is created empty so you can start from a blank slate. The resource type — web-template, web-file, or server-logic — is taken from the target GUID's Power Pages metadata, so source files can be organised however you prefer. The entry is appended to powpow.config.json.
3. Develop
Start the dev server and watch-mode bundler together:
npx powpow devThis runs Rolldown in watch mode and starts an HTTP server on port 3001 (configurable via the PORT environment variable). Watch mode is incremental — only entries that actually depend on the changed file get rebuilt, and outputs whose final content didn't change aren't rewritten to disk. Built assets are written directly into the portal directory and served by the dev server for use with PowPow Interceptor.
4. Build for deployment
Run a full type-check and production build:
npx powpow buildBuilt output is written to the portal resource content paths defined by your entry points, ready to be committed and deployed.
Commands
| Command | Description |
| --- | --- |
| powpow init | Bootstrap a project: create powpow.config.json, scaffold tsconfigs, install deps. Use --force to overwrite an existing config. |
| powpow add | Scan the portal directory and add a resource → source file mapping. |
| powpow dev | Start the dev server and Rolldown in watch mode (incremental rebuilds). |
| powpow build | Type-check with tsc and build all entry points with Rolldown. |
| powpow serve | Start the dev server only (no build/watch). |
| powpow doctor | Diagnose config, resource, and source-file issues. |
| powpow remove | Unmap a portal resource from an entry point, optionally deleting the source file. |
Global Options
| Option | Description |
| --- | --- |
| --config <path> | Path to powpow.config.json (default: ./powpow.config.json) |
| --verbose | Show debug output and full error stack traces. |
| --quiet | Only show errors. |
| --silent | Suppress all output. |
| -h, --help | Show help message |
| -v, --version | Print installed version |
Command-specific options
| Option | Applies to | Description |
| --- | --- | --- |
| --force | init | Overwrite an existing powpow.config.json. |
| --skip-typecheck | build | Skip the tsc type check before bundling. |
Configuration
powpow.config.json is the single configuration file for a project.
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| portalConfigPath | string | Yes | Relative path to the Power Pages portal config root directory. |
| sourceDir | string | No | Relative path to the TypeScript source directory. Defaults to src. |
| entryPoints | EntryPoint[] | Yes | Array of source-to-resource mappings. Each entry may also carry an options object for per-entry overrides (see below). |
| globals | Record<string, string> | No | Map of package specifiers to globalThis variable names (UMD globals). |
| extensionId | string | No | Chrome extension ID of the PowPow Interceptor. When set, the dev server only accepts requests from chrome-extension://<id>. See Dev Server trust model below. |
| version | string | No | Config schema version. |
Entry Points
Each entry point maps a source file (or bare package specifier) to a Power Pages resource GUID:
{
"entryPoints": [
{
"source": "my-feature/index.tsx",
"target": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
{
"source": "lodash",
"target": "f0e1d2c3-b4a5-6789-0fed-cba987654321"
}
]
}- File sources are resolved relative to
sourceDir. - Bare specifiers (e.g.
lodash) bundle an installed npm package into the target web file.
An entry may also specify options to override project-level settings for that single entry:
{
"source": "admin/index.tsx",
"target": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"options": {
"globals": { "jquery": "jQuery" },
"minify": false
}
}options.globals is merged over the top-level globals (entry keys win). options.minify overrides the default for that entry.
Globals
Use globals to reference libraries that are already loaded on the page as UMD globals instead of bundling them:
{
"globals": {
"react": "React",
"react-dom": "ReactDOM",
"bootstrap": "bootstrap"
}
}Imports of these packages are replaced with references to globalThis[variableName] at build time.
How It Works
Build Pipeline
- Entry point resolution — Each entry point is resolved to its source file and target Power Pages resource (web template or web file).
- Rolldown bundling — Source files are bundled as ES modules with tree-shaking and minification.
- Output generation — Web template output is wrapped in a
<script type="module">tag. Web file output is written as a plain ES module. - Direct write — Built files are written directly to the portal resource content paths on disk.
Module Resolution
For every import, the bundler tries three strategies in order:
- UMD global — packages listed in
globals(or the per-entryoptions.globals) are replaced withglobalThis[name]references at build time. - Cross-entry external — if the import resolves to another entry's exact source file, it is externalized as a runtime URL with a
?v=<hash>cache-buster. Behaviour by owner type:- imported from a web-file owner → externalized.
- imported from a web-template owner → warning, then inlined (web-templates can't be loaded as modules).
- imported from a server-logic owner → error (server-logic entries are not importable).
- any cross-entry import made from a server-logic entry → error (server-logic must inline everything).
- Inlined — everything else (relative file imports that aren't another entry, npm packages without a UMD shim) is bundled in. Modules inlined by more than one entry are flagged as duplicates in the build summary.
Ownership Model
Ownership is based on exact source-file match: an entry owns the file at its source path and nothing else. There is no directory-tree ownership — adjacent files in the same folder are independent unless they are themselves entries.
Dev Server
The dev server exposes three routes:
| Route | Description |
| --- | --- |
| GET /manifest | JSON manifest of all mapped resources with their serve paths |
| GET /web-templates/:guid | Serves a web template's built HTML content |
| GET /web-files/* | Serves a web file by its partial URL path |
The server is designed to work with the PowPow Interceptor browser extension, which intercepts Power Pages asset requests and redirects them to the local dev server.
Dev Server trust model
- Bind address. The dev server binds to
127.0.0.1only; it is not reachable from other machines on your network. - CORS. By default (no
extensionIdin config), the dev server responds withAccess-Control-Allow-Origin: *. Any page open in your browser can read the/manifestendpoint and discover the GUID-to-source mapping of your project. This is convenient for first-run setup but not suitable for environments where the project layout is sensitive. - Restricting CORS. Set
extensionIdinpowpow.config.jsonto the Chrome extension ID of your PowPow Interceptor installation. The dev server will then reject any request whoseOriginheader does not matchchrome-extension://<id>, and theAccess-Control-Allow-Originheader will echo only that origin. - Output cache busting. Cross-entry imports of web-file resources get a
?v=<hash>query parameter appended at build time, so browsers and Power Pages caches pick up new builds automatically. Web-template output is an inline<script type="module">and is not cached separately.
TypeScript Configuration
powpow init writes three tsconfigs that wire the browser and server-logic worlds together via TypeScript project references:
tsconfig.json— solution root. Emptyfiles, withreferencesto both child configs. Runningtsc -bfrom the project root type-checks everything.tsconfig.web.json— browser entries (src/web-templates/,src/web-files/). Extendspowpow-cli/presets/tsconfig.web.base.json(ES2023 target, DOM lib, React JSX transform, and the Power Pages browser globals frompowpow-cli/types/browser).tsconfig.server-logic.json— server-logic entries (src/server-logic/). Extendspowpow-cli/presets/tsconfig.server-logic.base.json(ES2023, no DOM, no JSX, and theServerglobal frompowpow-cli/types/server).
Both base configs are shipped inside the powpow-cli package, so upgrading the CLI updates the compiler options. Each child config includes its own root only, so a single editor instance type-checks each directory with the right ambient types in scope.
Programmatic API
PowPow exports its core modules for programmatic use:
import {
// Build
build,
watchBuild,
typeCheck,
// Config
findConfig,
loadConfig,
loadAndValidate,
saveConfig,
validateEntryPoints,
resolvePortalDir,
resolveProjectRoot,
resolveSourceDir,
// Dev server
startDevServer,
// Bundler plugin
powpow,
// Resources
scanPortalResources,
// Logging
log,
setLogLevel,
getLogLevel,
} from 'powpow-cli';
import type {
PowpowConfig,
EntryPoint,
EntryPointOverrides,
PortalResource,
ResourceType,
LogLevel,
} from 'powpow-cli';Contributing
Bug reports and feature requests are welcome on the GitHub issue tracker. Pull requests welcome too.
Local development
git clone https://github.com/itera-fredrikstad/powpow-cli.git
cd powpow-cli
pnpm install --frozen-lockfile
pnpm buildRequires Node.js ≥22 and pnpm 10.
Before opening a PR
CI runs the equivalent of these three commands on every PR — please run them locally first so the feedback loop stays fast:
pnpm lint
pnpm test
pnpm buildCode style
Biome handles formatting and linting:
pnpm format— auto-formatpnpm lint:fix— auto-fix lint issuespnpm lint— check only (what CI runs)
Pull request guidelines
- Keep PRs small and focused on a single concern.
- Describe the user-facing change in the PR body.
- For changes to
dev, watch mode, or the bundler plugin, include a short manual repro plan (which entry types, what change you made, what you expected to rebuild). - Don't bump the package version in your PR — releases are cut by maintainers via the
publish.ymlworkflow on tagged commits.
