npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@delegateas/create-dataverse-webresource

v1.0.12

Published

Scaffold a single-file HTML Dataverse web resource, deployed with XrmSync

Readme

create-dataverse-webresource

Scaffolds Dataverse HTML web resources that are one file each: TypeScript and CSS in src/, bundled into self-contained .html files, deployed with XrmSync.

Every folder under src/ is one web resource, so a single project can hold as many pages as you like — see More than one web resource.

Run it inside the empty folder that should hold the project. The one positional name is used for the source folder, npm package, and deployed HTML file:

mkdir account-summary && cd account-summary
npx @delegateas/create-dataverse-webresource account-summary

You are asked for anything you do not pass as an option:

npx @delegateas/create-dataverse-webresource account-summary \
  --url https://contoso.crm4.dynamics.com \
  --solution MySolution \
  --prefix new \
  --remote [email protected]:contoso/account-summary.git

| Option | Meaning | | --- | --- | | -u, --url | Dataverse environment URL. Written to appsettings.json as DATAVERSE_URL. | | -n, --solution | Unique name of the solution to deploy into. | | -p, --prefix | Publisher prefix of that solution. Used to spell out the web resource name. | | -r, --remote | Git remote to push the new project to. Optional — press Enter to skip when asked. | | -y, --yes | Do not ask anything — for scripts and CI. |

The generated project is then installed, committed to a fresh git repository (and pushed, if you gave a remote), built once, and uploaded once — so it is already live at <url>/WebResources/<prefix>_<solution>/<name>.html, which the scaffolder prints when it finishes.

Type generation is part of setup and runs by default. The initial upload runs when an interactive terminal is available; npm run deploy does the same thing later.

What you get

./
  src/<name>/main.ts         your code — the page starts here
  src/<name>/styles.css      your styles
  src/<name>/index.html      the page shell
  src/assets.d.ts            lets TypeScript understand imported CSS and images
  build.mjs                  lints src/, then bundles each web resource into ONE html file
  dev.mjs                    build on save + sync to Dataverse, in one command
  autocommit.mjs             commits every build to git, so no work is ever lost
  mock.mjs                   local Dataverse-shaped Web API for `npm run offline`
  mock/metadata.json         schema that does not exist in Dataverse yet
  mock/data/*.json           seeded records and canned server results
  eslint.config.js           the lint rules the build enforces
  appsettings.json           which environment and solution to deploy to, create/update only
  AGENTS.md                  instructions for coding agents working in the project
  .config/dotnet-tools.json  the pinned XrmSync and XrmDefinitelyTyped versions

npm run dev in that folder rebuilds on every save and keeps the environment in sync, so edits show up in the real form after a hard refresh. npm run deploy is the one-shot equivalent.

More than one web resource

build.mjs treats every folder under src/ that holds an index.html as its own web resource, and builds it into dist/<folder>.html. XrmSync uploads all of dist/ recursively, so that becomes <prefix>_<solution>/<folder>.html in the solution.

Adding a page is therefore adding a folder — copy the one you have, rename it, done. There is no list to keep in step: not in appsettings.json, not in build.mjs. AGENTS.md and the generated README.md both say so, so an agent asked for a second page does the right thing on its own.

A folder under src/ without an index.html is not a web resource, which is where shared code goes. The watcher works out the list when it starts, so a brand new folder needs npm run dev restarted.

Never deletes anything

A scaffolded project is a sidecar: the solution it deploys into belongs to a real project, and already holds web resources this one knows nothing about. XrmSync's default is to make the solution mirror the folder it syncs, which would delete every one of them. So the template sets "NoDelete": true on the sync item — a deploy creates and updates, and that is all it can do.

The cost is that removing a folder from src/ no longer removes the web resource from Dataverse. That is the right trade for a sidecar, and both the generated README.md and AGENTS.md say so, so neither the user nor an agent is surprised by it.

Why offline mode

npm run offline points XrmQuery at a real localhost /api/data/v9.2/ endpoint. Seed records are ordinary JSON files under mock/data/, while mock/metadata.json declares tables, columns and relationships that do not exist in Dataverse yet. The metadata generator emits a gitignored .offline/schema.d.ts that merges with XrmDefinitelyTyped output, so UX work can begin before the real schema exists.

The HTTP mock is tested through XrmQuery, including CRUD and upsert, alternate keys, lookup binding, related queries, one-to-many and many-to-one expands, filtering, paging, formatted values, associations, canned actions/functions, batch requests, basic FetchXML and configured predefined queries. This is a lightweight table/query implementation, not XrmMockup: it does not execute plugins, workflows, security or arbitrary server logic, and no XrmMockup settings are added to appsettings.json.

Compared with the old implementation, generated projects keep one server file plus the data and metadata users actually edit; the browser-side engine and overlay source trees remain gone.

Why a bundler

Dataverse serves an HTML web resource as a single file — it cannot load a sibling .js, a stylesheet, or anything from a CDN. tsc alone cannot produce one bundled file, so the template uses esbuild (one dependency) in a small build.mjs that inlines the JavaScript, the CSS and any imported images as data URIs. The build fails if an external reference ever survives, rather than shipping a page that renders blank inside a form.

Every dependency of the generated project is dev-only — nothing ships to Dataverse except the HTML file.

Why lint in the build

These projects are largely written by coding agents, which do not read a lint script they were never told to run. So build.mjs runs ESLint itself: npm run dev reports problems after every rebuild, and npm run build and npm run deploy fail on them. That is the whole feedback loop, in the command people already run.

Watch mode deliberately reports without blocking. If a lint error stopped dist/ from being rewritten, XrmSync would keep syncing a stale file while you fixed it — a much worse failure than a noisy console. One-shot builds do fail, so nothing broken is uploaded on purpose.

The rules live in templates/default/eslint.config.js, which is also the source of this package's own config: eslint.config.js at the repository root imports the rule list from it, so the linter that checks the scaffolder is the one it ships. They are strict on accessibility, size, complexity and unsafe DOM writes — cheap to satisfy in a file this small, and exactly the mistakes that turn a web resource into a blank form.

Why commit in the build

Same reasoning as the linting, aimed at a worse failure. The people these projects are for do not know git, so left to themselves they accumulate one enormous uncommitted diff and have no way back from a change that made things worse — which is exactly the change an agent is most likely to make.

Telling the agent to commit does not solve it: it is advice, and advice gets skipped. So the build commits instead. autocommit.mjs runs git add -A and commits on every rebuild in npm run dev and on every npm run build, whichever tool or human made the edit. Nothing to configure, nothing agent-specific, and it keeps working when the project is opened in a different editor.

The commits are noisy and are not expected to build — a broken state is committed too, because it is the state you most want to escape from. Failures are never fatal: no repository, no git, or no configured identity all leave the build's exit code alone, and the identity case prints the two git config --global commands that fix it, once per process. NO_AUTOCOMMIT=1 turns it off.

The generated AGENTS.md tells agents not to rewrite that history, since it is the user's only undo.

Requirements

  • Node.js 20 or newer.
  • The .NET SDK, for XrmSync. The scaffolder warns if it is missing but still creates a working project.

Developing this package

npm install
npm run lint    # same rules the template ships; `npm run lint:fix` fixes what it can
npm test        # builds, then runs the end-to-end tests in test/

The tests scaffold into a temp folder, check the output, then install and build a real project and assert the result is genuinely self-contained. The install-and-build tests skip themselves if the npm registry is unreachable, and can be skipped deliberately with CDW_SKIP_BUILD_TESTS=1 — which is what CI does on every push, since that suite installs a whole project and takes minutes.

Note that npm test runs the TypeScript tests through Node's type stripping, so developing this package needs Node 22 or newer (see .nvmrc), even though the published CLI runs on Node 20.

Releasing

.github/workflows/publish.yml publishes to npm when a v* tag is pushed:

npm version patch      # or minor / major — bumps package.json and commits
git push --follow-tags

The workflow refuses to publish if the tag and package.json version disagree, then typechecks and runs the full test suite before publishing. A prerelease version (0.2.0-beta.1) is published under the next dist-tag, so npx @delegateas/create-dataverse-webresource keeps resolving to the last stable release.

Authentication is npm trusted publishing over GitHub's OIDC — there is no NPM_TOKEN to rotate, and every release gets a provenance attestation. This requires one-time setup on npmjs.com: on the package's Settings → Trusted publisher, register this repository with workflow publish.yml and environment npm. Until that exists, the publish step fails to authenticate.

To rehearse the release path without publishing, run the Publish workflow manually from the Actions tab with dry_run left checked.

Note: XrmSync's web resource support is not in its 1.0.0 release, so templates/default/_dotconfig/dotnet-tools.json pins a -preview version deliberately. The pin is 1.0.0-preview.24 or later for a second reason: NoDelete arrived in that release, and the template's appsettings.json sets it. Move the pin forward once a stable release includes both the webresources command and NoDelete.

XrmSync 1.0.0-preview.25 also updates DataverseConnection to 1.2.4; keep that pin or a newer listed XrmSync release so generated projects use the current Dataverse authentication layer.

The same file pins XrmDefinitelyTyped at 7.0.0-preview.3, matching the @delegateas/xrmquery version in the template's package.json. Those two are a pair — the tool generates the declarations the library's types merge into — so move both pins together. preview.3 is the floor: earlier previews could not generate large tables such as contact and systemuser, and ignored the XrmDefinitelyTyped section of appsettings.json.