Dverse.Cli 0.1.0-ci.102

This is a prerelease version of Dverse.Cli.
dotnet tool install --global Dverse.Cli --version 0.1.0-ci.102
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Dverse.Cli --version 0.1.0-ci.102
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Dverse.Cli&version=0.1.0-ci.102&prerelease
                    
nuke :add-package Dverse.Cli --version 0.1.0-ci.102
                    

dverse

Declarative deployment + SDK for Microsoft Dataverse / Dynamics 365 plugins. dverse replaces spkl-style imperative registration with a Terraform-like plan/apply model, and replaces hand-written plugin boilerplate with source-generated code.

Dataverse online only. dverse authenticates via Microsoft Entra ID (Azure.Identity) and always registers plugins in the sandbox — there is no on-premises / IFD support and none is planned for v1.

You write only this, in a plain SDK-style csproj:

using Dverse;
using Dverse.Model;

public partial class AccountPlugins
{
    [Step(typeof(Account), Msg.Update, Stage.PreOperation,
        Filtering = new[] { Account.Cols.CreditLimit })]
    public void BlockCreditLimitDowngrade(IStepContext<Account> ctx)
    {
        if (ctx.Target.CreditLimit < ctx.PreImage.CreditLimit)
            throw ctx.Fail("Credit limits can't be lowered.");
    }
}

Then run dotnet build -t:Deploy. Everything else is generated or computed:

  • the IPlugin entry class and the embedded context runtime (no base classes, no runtime DLL, no ILMerge),
  • the pre-image registration — columns inferred from your code (ctx.PreImage.CreditLimit above ⇒ a pre-image with exactly creditlimit); escapes are compile errors, never a silent "all columns",
  • the registration manifest, diffed field-by-field against the live environment.

A no-op deploy performs zero writes in ~5 read round trips. dverse plan shows exactly what would change (--json for machines, exit code 0 = clean / 2 = changes pending) — put it in CI and never let an environment drift again.

Feature overview

Feature How
Early-bound models entities declared once in dataverse.yaml; plan/apply refresh dataverse.snapshot.json automatically (with a schema diff); a source generator emits entity classes, Cols constants, and optionset enums in-memory — no generated .cs files on disk
Plugin steps [Step(typeof(Account), Msg.Update, Stage.PreOperation, ...)] on a plain method taking IStepContext<Account>
Images Inferred from ctx.PreImage.X / ctx.PostImage.X access; explicit [Image(...)] as the escape hatch
Custom APIs [CustomApi("dverse_Reverse")] on a method with record request/response types; parameters sync automatically; typed callers are generated
Computed columns [Derive(Account.Cols.Description)] on a pure static method — compiles to coalesced, dependency-ordered Create/Update steps with inferred filtering
Real-time rollups [Rollup(Account.Cols.OpenBalance)] over Children<Contact> — the Where/aggregate expression compiles to one server-side FetchXML aggregate per affected parent, with reparenting (old + new parent) handled via an inferred pre-image
Web resources a webresources: entry in dataverse.yaml — built (e.g. esbuild), content-hash diffed, published in one PublishXml; dverse webresources pull adopts existing ones
PCF controls dverse pcf init scaffolds the standard pcf-scripts project; a pcf: entry in dataverse.yaml deploys every built control via a staged solution import — hash-diffed, so an unchanged control is zero writes
Code signing dverse sign init creates the code-signing certificate; a signing: entry in dataverse.yaml Authenticode-signs every assembly before upload — in-process, cross-platform, no signtool, and deterministic so a no-op deploy stays zero writes
Managed identity a managedIdentity: entry in dataverse.yaml creates and binds the managedidentity row so plugins reach Azure with no stored credentials (ctx.ManagedIdentity); dverse sign info prints the exact Azure federated credential to create for your certificate
Command-bar buttons [Command(typeof(Account), "Recalculate", Confirm = …, Progress = …, Success = "{response.Count} done", Refresh = Refresh.Form)] next to a [CustomApi] — the button, its JavaScript and its registration are generated; conditions (EnableWhenNotEmpty, RequireSelection, …) compile to a classic ribbon rule automatically
Modern commands a commands: entry in dataverse.yaml — appaction rows declared in YAML with a real field-level diff, so a changed label is one PATCH of one column; pruned by publisher prefix
Classic ribbons a ribbons: entry in dataverse.yaml — committed RibbonDiffXml per entity plus the application ribbon, diffed per element against the platform's decomposed storage, deployed by staged solution import
Schema & data a schema: entry declares tables, columns, option sets and relationships; seed: upserts reference rows; dverse schema adopt captures what already exists
Forms, views & apps committed formxml / savedquery / app-module fragments (dverse forms pull, dverse views pull), converged and published in the same single PublishXml
Deployment dverse plan / dverse apply (or dotnet build -t:Plan / -t:Deploy): field-level diff, sha256 assembly-upload skip, stale components pruned automatically, source versions auto-bumped when content changes (assembly revision / PCF patch)
Guard rails 42 compile-time diagnostics (DVERSE0xx): invalid stage/mode combos, pre-image on Create, filtering on unsupported messages, duplicate keys, impure derives, …

Getting started

The five-minute version

# 1. install the CLI (or run it from source: dotnet run --project src/Dverse.Cli --)
dotnet tool install --global Dverse.Cli --prerelease
dverse version   # the installed package version, e.g. 0.1.0-ci.89 (+ the commit it was built from)

# 2. in your plugin repo: describe your environments (no secrets — this file is committed)
cat > dataverse.yaml <<'EOF'
default: dev
environments:
  dev:
    url: https://yourorg.crm.dynamics.com
    solution: yoursolution
    prefix: your
EOF

# 3. sign in once (device code; later runs are silent)
dverse auth login
dverse whoami

# 4. declare the entities once in dataverse.yaml:
#      model:
#        entities: [account, contact]
#    plan/apply keep the model snapshot fresh automatically from then on

# 5. deploy (from the plugin project directory: builds, diffs, applies)
dverse apply
# ... or preview first / via MSBuild:
dverse plan
dotnet build -t:Deploy

Architecture in one paragraph

Plugins target net462 and reference exactly one package, Dverse.Sdk (attributes + source generators + MSBuild targets). The generators are pure: metadata comes exclusively from the committed dataverse.snapshot.json, so builds are reproducible and CI needs no Dataverse connection. The registration manifest is emitted as an inactive preprocessor block — it contributes zero IL, so a registration-only change (a rank, a filter column) never changes the assembly hash and never re-uploads the DLL. The dverse CLI talks raw Dataverse Web API (no Microsoft.PowerPlatform.Dataverse.Client dependency), reads the environment in a constant number of queries, canonicalizes both sides, and PATCHes only changed columns. Matching precedence for existing steps: explicit IdKey → natural key (plugin type, message, entity, stage) when unique on both sides → name; ambiguity is a hard error, never a heuristic.

Repository layout

src/Dverse.Cli/              the dverse CLI (net9.0, packs as dotnet tool Dverse.Cli)
src/Dverse.Sdk/              attributes (netstandard2.0) — the package plugins reference
src/Dverse.Sdk.Generators/   Roslyn incremental generators + diagnostics (ships inside Dverse.Sdk)
src/Dverse.Sdk.Targets/      MSBuild props/targets (ship inside Dverse.Sdk)
tests/                    unit, generator, and live-environment integration tests
samples/TestPlugins/      end-to-end sample plugin project (project references, for development)
samples/pcf/CreditGauge/  sample PCF control (scaffolded with `dverse pcf init`, built output committed)
demos/NugetDemo/          standalone consumer demo — uses only the published NuGet packages
docs/                     getting started, spkl migration, API spec, backlog

Building

dotnet build && dotnet test

Integration tests run only when DVERSE_TEST_URL is set and create only prefixed, torn-down test components. CI publishes prerelease packages to NuGet on every push to main.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.1.0-ci.102 0 7/31/2026
0.1.0-ci.101 0 7/31/2026
0.1.0-ci.100 0 7/30/2026
0.1.0-ci.99 0 7/30/2026
0.1.0-ci.98 21 7/30/2026
0.1.0-ci.97 18 7/30/2026
0.1.0-ci.96 24 7/30/2026
0.1.0-ci.95 26 7/30/2026
0.1.0-ci.94 25 7/30/2026
0.1.0-ci.93 38 7/29/2026
0.1.0-ci.92 38 7/28/2026
0.1.0-ci.91 38 7/28/2026
0.1.0-ci.90 31 7/28/2026
0.1.0-ci.89 41 7/27/2026
0.1.0-ci.88 40 7/27/2026
0.1.0-ci.87 40 7/27/2026
0.1.0-ci.86 34 7/27/2026
0.1.0-ci.85 37 7/27/2026
0.1.0-ci.84 40 7/27/2026
0.1.0-ci.83 43 7/27/2026
Loading failed