TALXIS.DevKit.Templates.Dataverse 1.21.0

Prefix Reserved
dotnet new install TALXIS.DevKit.Templates.Dataverse::1.21.0
                    
This package contains a .NET Template Package you can call from the shell/command line.

Power Platform .NET Templates

This project is currently in a development phase and not ready for production use. While we actively use these tools internally, our aim is to share and collaborate with the broader community to refine and enhance their capabilities. We are in the process of gradually open-sourcing the code, removing internal dependencies to make it universally applicable. At this stage, it serves as a source of inspiration and a basis for collaboration. We welcome feedback, suggestions, and contributions through pull requests.

If wish to use this project for your team, please contact us at hello@networg.com for a personalized onboarding experience and customization to meet your specific needs.

Only use this if you understand the standard platform customization capabilities. Using these templates with parameter combinations other than those documented here might generate invalid source code, which could still be importable to Dataverse. In some situations, this could cause your environment to become irreversibly corrupted.

Goal

The primary objective of this NuGet package is to help Power Platform developers scaffold Power Platform components using a code-first approach.

Guide

You can refer to a VS Code snippets file used by @TomProkop for conference demos.

Dev machine setup

# If you're using .NET CLI for the first time, you might need to set up nuget.org as a package source
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org

# Install PowerShell 7+ to have "pwsh" executable present in your terminal
# To support running the templates cross-platform "pwsh" is used instead of "powershell.exe"
# You can use other installation methods
dotnet tool install --global PowerShell

# Install the template package to your machine
dotnet new install TALXIS.DevKit.Templates.Dataverse

Templates

Solutions

Template commands are designed to be run in the folder where *.*proj is located. Use --output parameter if your working directory is different.

Initialize a new empty solution:

dotnet new pp-solution `
--output "src/Solutions.DataModel" `
--PublisherName "tomas" `
--PublisherPrefix "tom" `
--allow-scripts yes

Tables

Create a new standard table:

dotnet new pp-entity `
--output "src/Solutions.DataModel" `
--Behavior New `
--PublisherPrefix "tom" `
--LogicalName "location" `
--LogicalNamePlural "locations" `
--DisplayName "Location" `
--DisplayNamePlural "Locations" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Create a new activity table:

dotnet new pp-entity `
--output "src/Solutions.DataModel" `
--EntityType "Activity" `
--Behavior "New" `
--PublisherPrefix "tom" `
--LogicalName "shiftevent" `
--LogicalNamePlural "shiftevents" `
--DisplayName "Shift Event" `
--DisplayNamePlural "Shift Events" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Create a new elastic table:

dotnet new pp-entity `
--output "src/Solutions.DataModel" `
--EntityType "Elastic" `
--Behavior "New" `
--PublisherPrefix "tom" `
--LogicalName "inboundmessage" `
--LogicalNamePlural "inboundmessages" `
--DisplayName "Inbound Message" `
--DisplayNamePlural "Inbound Messages" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Add an existing custom table to a solution:

dotnet new pp-entity `
--output "src/Solutions.UI" `
--Behavior "Existing" `
--PublisherPrefix "tom" `
--LogicalName "shiftevent" `
--DisplayName "Shift Event" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Add an existing system table to a solution:

dotnet new pp-entity `
--output "src/Solutions.UI" `
--Behavior "Existing" `
--IsSystemEntity "true"  `
--LogicalName "account" `
--DisplayName "Account" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Columns

Add a whole number column to table:

dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "tom_warehouseitem" `
--AttributeType "WholeNumber" `
--RequiredLevel "required" `
--PublisherPrefix "tom" `
--LogicalName "availablequantity" `
--DisplayName "Available Quantity" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Add a lookup column to table:

dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "tom_warehousetransaction" `
--AttributeType "Lookup" `
--RequiredLevel "required" `
--LogicalName "tom_itemid" `
--DisplayName "Item" `
--ReferencedEntityName "tom_warehouseitem" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Create a global OptionSet:

dotnet new pp-optionset-global `
--output "src/Solutions.DataModel" `
--RequiredLevel "required" `
--LogicalName "${publisherPrefix}_paymentmethod" `
--DisplayName "Payment Method" `
--SolutionRootPath "Declarations" `
--OptionSetOptions "Visa,Mastercard,Cash" `
--allow-scripts yes

Add global OptionSet to the table:
```console
dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "${publisherPrefix}_warehousetransaction" `
--AttributeType "OptionSet(Global)" `
--RequiredLevel "required" `
--LogicalName "${publisherPrefix}_paymentmethod" `
--DisplayName "Payment Method" `
--GlobalOptionSetType "Existing" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Create a local OptionSet:
```console
dotnet new pp-entity-attribute `
--output "src/Solutions.DataModel" `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--AttributeType "OptionSet(Local)" `
--RequiredLevel "required" `
--LogicalName "${publisherPrefix}_packagetype" `
--DisplayName "Package Type" `
--OptionSetOptions "Box,Bag,Envelope" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

UI

Create a model-driven app:

dotnet new pp-app-model `
--output "src/Solutions.UI" `
--PublisherPrefix "tom" `
--LogicalName "warehouseapp" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Add a table to a model-driven app component:

dotnet new pp-app-model-component `
--output "src/Solutions.UI" `
--EntityLogicalName "tom_warehouseitem" `
--SolutionRootPath "Declarations" `
--AppName "tom_warehouseapp" `,
--allow-scripts yes

Add an area to the sitemap:

dotnet new pp-sitemap-area `
--output "src/Solutions.UI" `
--SolutionRootPath "Declarations" `
--AppName "tom_warehouseapp" `,
--allow-scripts yes

Add an group to the area:

dotnet new pp-sitemap-group `
--output "src/Solutions.UI" `
--SolutionRootPath "Declarations" `
--AppName "tom_warehouseapp" `,
--allow-scripts yes

Add an subarea into the group:

dotnet new pp-sitemap-subarea `
--output "src/Solutions.UI" `
--SolutionRootPath "Declarations" `
--EntityLogicalName "tom_warehouseitem" `
--AppName "tom_warehouseapp" `,
--allow-scripts yes

Create a main form for a table:

dotnet new pp-entity-form `
--output "src/Solutions.UI" `
--FormType "main" `
--SolutionRootPath "Declarations" `
--EntitySchemaName "tom_warehouseitem" `
--allow-scripts yes
Form design

Form Structure Hierarchy:

┌──────────────────────────────────────────────────────────┐
│ Form                                                     │
│ ├─ Tab                                                   │
│   ├─ Column                                              │
│     ├─ Section                                           │
│       ├─ Row                                             │
│         ├─ Cell                                          │
│           └─ Control                                     │
└──────────────────────────────────────────────────────────┘

Create a new tab in the form:

dotnet new pp-form-tab  `
--output "src/Solutions.UI"  `
--FormType "main"  `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--RemoveDefaultTab "True" `
--allow-scripts yes

Create a new column in the specific tab:

dotnet new pp-form-column  `
--output "src/Solutions.UI"  `
--FormType "main"  `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnWidth "75"
--allow-scripts yes

Create a new section in the specific column:

dotnet new pp-form-section  `
--output "src/Solutions.UI"  `
--FormType "main"  `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionName "GENERAL"
--SolutionRootPath "Declarations" `
--allow-scripts yes

Create a new row in the specific section:

dotnet new pp-form-row  `
--output "src/Solutions.UI"  `
--FormType "main"  `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionIndex 1 `
--allow-scripts yes

Create a new cell in the specific row:

dotnet new pp-form-cell  `
--output "src/Solutions.UI"  `
--RowIndex "1"  `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionIndex 1 `
--FormType "main"  `
--DisplayName "Name"  `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Create a new control in the specific cell:

dotnet new pp-form-cell-control  `
--output "src/Solutions.UI"  `
--AttributeType "Text"  `
--RowIndex "1"  `
--SetToTabFooter "False" `
--TabIndex 1 `
--ColumnIndex 1 `
--SectionIndex 1 
--AttributeLogicalName "${publisherPrefix}_name"  `
--FormType "main"  `
--FormId $warehouseitemFormGuid `
--EntitySchemaName "${publisherPrefix}_warehouseitem" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Security roles

Create a security role:

dotnet new pp-security-role `
--output "src/Solutions.Security" `
--SolutionRootPath "Declarations" `
--rolename "Warehouse Manager" `
--allow-scripts yes

Add privileges to a security role:

dotnet new pp-security-role-privilege `
--output "src/Solutions.Security" `
--SolutionRootPath "Declarations" `
--EntitySchemaName "tom_warehouseitem" `
--rolename "Warehouse Manager" `
--PrivilegeTypeAndLevel "[{ PrivilegeType: Read, Level: Global }, { PrivilegeType: Write, Level: Global }]" `
--allow-scripts yes

Plugins

Initialize a new plugin:

dotnet new pp-plugin `
--output "src/Plugins.Warehouse" `
--PublisherName "tomas" `
--SigningKeyFilePath "PluginKey.snk" `
--Company "NETWORG" `
--allow-scripts yes

Add new assembly:

dotnet new pp-plugin-assembly-step `
--output "src/Solutions.Logic" `
--PluginProjectRootPath "..\\Plugins.Warehouse" `
--SolutionRootPath "Declarations" `
--allow-scripts yes

Add new step to the assembly:

dotnet new pp-plugin-assembly-step `
--output "src/Solutions.Logic" `
--PrimaryEntity "tom_warehousetransaction" `
--PluginProjectName "Plugins.Warehouse" `
--PluginName "ValidateWarehouseTransactionPlugin" `
--Stage "Pre-validation" `
--SdkMessage "Create" `
--SolutionRootPath "Declarations" `
--FilteringAttributes "{tom_itemid, tom_quantity}" `
--AssemblyId "GUID to identifying your assembly" `
--allow-scripts yes

You can add component schema validation to your build process using Power Platform MSBuild targets.

Tools

Power Platform: Script Library template

A .NET project template for building Dataverse script libraries with TypeScript and Rollup. It scaffolds a net462 project that uses Rollup to bundle TypeScript source (including npm dependencies) into a single UMD JavaScript file for use as a web resource.

What you get

  • A .NET SDK project targeting net462 with ProjectType=ScriptLibrary
  • TypeScript + Rollup at the project root:
    • src/index.ts — entry point with a skeleton onLoad handler
    • rollup.config.mjs — bundles to build/<prefix>_<name>.js as UMD
    • tsconfig.json — ES6 modules, strict mode
    • package.json — Rollup plugins, TypeScript, @types/xrm
  • MSBuild target that runs npm install and npm run build (rollup) automatically on dotnet build
  • UMD output creates a global namespace for Dataverse form/button references

Prerequisites

  • .NET SDK 6+ (dotnet --version)
  • Node.js and npm (node -v, npm -v)

Create a new project

dotnet new pp-script-library `
    -n solution.scripts `
    --PublisherPrefix "tom" `
    --LibraryName "inventory"

Build

Build with dotnet build — the SDK runs npm install and Rollup automatically:

dotnet build

Outputs

After building, you will find:

  • build/<prefix>_<name>.js — UMD bundle (e.g., build/tom_inventory.js)
  • build/<prefix>_<name>.js.map — source map
  • bin/Debug/net462/<prefix>_<name>.js — copied by the Build SDK for deployment

Workflow chain

  1. Create script librarypp-script-library (this template)
  2. Add project reference — from the solution project: dotnet add reference ../solution.scripts/solution.scripts.csproj
  3. Build the solutiondotnet build (SDK auto-generates web resource data.xml and packs it)
  4. Reference from forms — use pp-form-event-handler with libraryName=<prefix>_<name>.js and functionName=<prefix>_<name>.ClassName.methodName
  5. Reference from ribbon — use pp-ribbon-button with LibraryLogicalName=<prefix>_<name>

Using npm packages

Unlike the previous AMD approach, this template uses Rollup to bundle npm dependencies into the output. Install packages normally and import them:

cd solution.scripts
npm install liquidjs
// src/index.ts
import { Liquid } from 'liquidjs';

export class DocumentGenerator {
    public static async generate(executionContext: Xrm.Events.EventContext): Promise<void> {
        const engine = new Liquid();
        // ...
    }
}

The dependency is bundled into the single output .js file automatically.

Troubleshooting

  • If npm is not found during dotnet build, ensure Node.js is installed and on PATH.
  • To force a clean build:
    rm -rf node_modules build
    dotnet build
    
  • For watch mode during development: npm run start

Power Platform: Script Test Template

This template creates a test project for Power Platform JavaScript/TypeScript web resources using Jest. It provides a complete testing infrastructure with Xrm API mocks, helper functions, and integration with .NET test framework.

Overview

The pp-test-script template generates a test project configured for testing Dataverse web resources (form scripts, ribbon commands, etc.). It includes:

  • Jest test framework with jsdom environment
  • Xrm API mocks for Dataverse client-side API
  • Helper functions for creating test objects (forms, attributes, controls)
  • Web resource loader utility for testing your scripts
  • .NET project integration for running tests via dotnet test
  • Automatic npm package installation

Basic Usage

Create a script test project:

dotnet new pp-test-script `
--output "tests/Script.Tests" `
--ScriptTestProjectName "Script.Tests" `
--ScriptLibraryPath "../src/Scripts.Warehouse" `
--allow-scripts yes

The template creates:

  1. .NET Test Project - A .NET 8.0 project confiured to run Jest tests via dotnet test
  2. Jest Confiuration - jest.config.js configured for jsdom environment
  3. Packae Configuration - package.json with Jest dependencies
  4. jest-core Directory - Reusable core library containing:
    • Xrm API mocks (setupXrm.js)
    • Helper functions (helpers.js)
    • Main export (index.js)
  5. Tests Directory - Sample test structure with utilities
  6. Web Resource Loader - Utility for loading and testing web resources

Project Structure

Script.Tests/
├── jest-core/
│   ├── index.js          # Main export for jest-core
│   ├── setupXrm.js       # Xrm API mock setup
│   ├── helpers.js        # Helper functions for test objects
│   └── package.json      # jest-core package definition
├── tests/
│   └── utils/
│       └── loadWebRes.js  # Web resource loader utility
├── jest.config.js        # Jest configuration
├── package.json          # Project npm dependencies
└── Script.Tests.csproj   # .NET project file

Basic Test Example

Create a test file in the tests directory (e.g., tests/myScript.test.js):

const { setupXrm, resetXrmMocks, makeForm, makeAttr, makeControl } = require('../jest-core');
const { loadWebResource } = require('./utils/loadWebRes');

describe('My Form Script', () => {
  beforeEach(() => {
    setupXrm();
  });

  afterEach(() => {
    resetXrmMocks();
  });

  test('should set field value on form load', () => {
    // Arrange
    const nameAttr = makeAttr('');
    const nameControl = makeControl();
    const formContext = makeForm(
      { name: nameAttr },
      { name: nameControl }
    );

    // Load your web resource
    const webRes = loadWebResource('path/to/your/script.js');
    
    // Act - Call your function
    webRes.onFormLoad(formContext);

    // Assert
    expect(nameAttr.setValue).toHaveBeenCalledWith('Default Value');
  });
});

Power Platform: Plugin Test Template

  • FakeXrmEasy v2 documentation: https://dynamicsvalue.github.io/fake-xrm-easy-docs/

What's included

  • FakeXrmEasyTestBase.cs: a base class that:
    • creates an IXrmFakedContext via MiddlewareBuilder (.AddCrud(), .UseCrud(), .UseMessages());
    • sets the license via SetLicense(...) (default is Commercial);
    • provides an IOrganizationService via _context.GetOrganizationService().

How to use FakeXrmEasyTestBase.cs

Inherit from the base class and use _context to seed data and _service to invoke the Organization Service.

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xrm.Sdk;

namespace Plugins.Tests
{
    [TestClass]
    public class AccountPluginTests : FakeXrmEasyTestBase
    {
        [TestMethod]
        public void Creates_account_successfully()
        {
            // Arrange: seed in-memory data
            var account = new Entity("account")
            {
                Id = Guid.NewGuid(),
                ["name"] = "Test Account"
            };
            _context.Initialize(new List<Entity> { account });

            // Act: use IOrganizationService from the base
            var createdId = _service.Create(new Entity("contact"));

            // Assert: verify with context storage
            var created = _context.Data["contact"][createdId];
            Assert.IsNotNull(created);
        }
    }
}

Important notes and dependencies

  • FakeXrmEasy license dependency: calling SetLicense(...) is required before .Build(). If needed, replace Commercial with RPL_1_5 or NonCommercial according to your usage terms.
  • Message executors dependency: if you need custom message executors, uncomment in the base class .AddFakeMessageExecutors(typeof(FakeXrmEasyTestBase).Assembly) before .UseMessages().
    • Dependency: custom executors become available only after their assembly is added.
    • Activation: .UseMessages() enables the message pipeline.

Collaboration

We are happy to collaborate with developers and contributors interested in enhancing Power Platform development processes. If you have feedback, suggestions, or would like to contribute, please feel free to submit issues or pull requests.

Local building and debugging

Build the template package locally:

dotnet pack --configuration Debug

The .nupkg is output to src/Dataverse/bin/Debug/. To use it with the CLI, add it as a local NuGet source (see cross-repo instructions).

For dotnet new (standalone template usage outside the CLI):

dotnet new install src/Dataverse/bin/Debug/TALXIS.DevKit.Templates.Dataverse.*.nupkg --force

Or use the VS Code task: Command Palette → "Tasks: Run Task" → "Update local templates".

Working with all three repos locally

See the tools-cli README for instructions on testing local versions of the CLI, templates, and build SDK together.

Versioning & Release

Releases are published through GitHub Releases:

  1. Go to ReleasesDraft a new release
  2. Create a tag in the format vX.Y.Z (e.g. v1.17.0)
  3. Write the changelog in the release body
  4. Click Publish release

The publish workflow builds the NuGet package with the tag version and pushes it to nuget.org. Release notes are embedded in the package.

Contact us

For further information or to discuss potential use cases for your team, please reach out to us at hello@networg.com.

  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.21.0 47 5/14/2026
1.20.0 44 5/14/2026
1.19.2 114 5/4/2026
1.19.1 97 5/4/2026
1.19.0 99 5/4/2026
1.18.0 108 5/1/2026
1.17.0 104 4/29/2026
1.16.0 106 4/28/2026
1.15.0 96 4/28/2026
1.14.0 105 4/28/2026
1.13.0 107 4/28/2026
1.12.0 113 4/27/2026
1.11.0 513 8/14/2025
1.10.0 524 8/6/2025
1.9.0 503 8/6/2025
1.8.0 556 8/2/2025
1.6.0 456 5/27/2025
1.5.0 433 3/22/2025
1.4.0 371 3/22/2025
Loading failed

1.21.0:
 Highlights

 - Redesigned pp-script-library around Rollup and UMD output so script libraries can use npm imports and still publish a Dataverse-compatible browser bundle.
 - Simplified script-library structure by removing the TS/ nesting, generating project-root TypeScript sources, and letting dotnet build run the npm/Rollup workflow end-to-end.
 - Replaced PowerShell-based flow GUID generation with template-engine symbols, including correct upper/lower casing for filenames and WorkflowId attributes.
 - Moved flow schemaVersion to the root level for Dataverse import compatibility.
 - Updated generated Dataverse projects to use TALXIS.DevKit.Build.Sdk/1.4.0.

 Full Changelog: https://github.com/TALXIS/tools-devkit-templates/compare/v1.20.0...v1.21.0

1.20.0:
 Highlights

 - Expanded pp-entity-attribute to cover full Maker/Dataverse/SDK parity, including additional text, numeric, money, datetime, lookup, option set, file, image, bigint, and JSON text scenarios.
 - Added support for Autonumber, RichText, and Money AccuracySource combinations in pp-entity-attribute.
 - Migrated SolutionRootPath handling to the __dunder__ convention and unified descriptions across templates.
 - Added componentType tags to templates for improved component classification.
 - Fixed pp-ribbon-button scaffolding by creating RibbonDiff.xml when missing, fixing location handling, and migrating to dunder placeholders.
 - Updated generated Dataverse projects to use TALXIS.DevKit.Build.Sdk/1.3.0.
 - Normalized the template package fallback version to 0.0.1; release package versions now come from the GitHub release tag.

 Full Changelog: https://github.com/TALXIS/tools-devkit-templates/compare/v1.19.2...v1.20.0

1.19.2:
 Fixes
 - pp-flow: post-action finds GenerateFlowId.ps1 correctly (53)
 - pp-entity-view: savedqueryid gets valid GUID with braces from template engine (54)
 - pp-solution: gracefully handles missing .sln/.slnx (52)
 - Build SDK bumped to v1.2.2

1.19.1:
 Fixes
 - pp-flow: post-action finds GenerateFlowId.ps1 correctly (53)
 - pp-entity-view: savedqueryid gets valid GUID with braces directly from template engine (54)
 - Build SDK reference bumped to v1.2.1

 Changes
 - View template uses {someexampleid} pattern in both filename and XML content
 - SetFormId.ps1 is now a safe fallback, not required for GUID generation

1.19.0:
 What's new

 pp-flow template
 New template for scaffolding Power Automate cloud flows with minimal structure (manual button trigger, no connector assumptions).
 Parameters: PublisherPrefix, LogicalName.

 Build SDK 1.2.0
 All solution project templates now reference TALXIS.DevKit.Build.Sdk 1.2.0 which includes the new ValidateWorkspace task and platform-metadata v0.4.0 XSD schemas.

1.18.0:
 .csx → .NET 10 file-based apps
 - All 7 .csx scripts migrated to .cs — dotnet-script no longer required
 - .NET 10 SDK runs them natively via dotnet run --file
 - All .ps1 callers use -PassThru + $proc.ExitCode for proper exit code handling

 View template GUID fix
 - pp-entity-view now auto-generates view GUID (type: generated with generator: guid)
 - Previously left {customViewIdexample} placeholder in filename and savedqueryid

 Plugin template improvements
 - pp-plugin-assembly: cross-platform Path.Combine, exit code check, RootComponent.xml pre-check
 - pp-plugin-assembly-step: AssemblyName parameter with auto-default, recursive assembly search, clean __dunder__ placeholders, null check on plugin type lookup
 - Removed unused AssemblyId parameter from step template

 Build SDK 1.0.0 reference
 - All template .csproj files reference TALXIS.DevKit.Build.Sdk/1.0.0

1.17.0:
 Plugin template improvements

 pp-plugin-assembly
 - Fixed cross-platform paths in GenerateAssembly.csx (Path.Combine)
 - GenerateAssembly.ps1: proper exit code check (-PassThru + $proc.ExitCode)
 - AddAssemblyToSolution.ps1: pre-check RootComponent.xml existence

 pp-plugin-assembly-step
 - Scripts search PluginAssemblies/ recursively — works with both build-generated and pp-plugin-assembly layouts
 - AssemblyName parameter with auto-default (PluginProjectName with dots removed)
 - SetPluginTypeId.ps1: null check on plugin type lookup
 - AddFilteringAttributes.ps1: exit code check
 - Removed unused AssemblyId parameter

 Placeholder cleanup (plugin templates only)
 Legacy example tokens in pp-plugin-assembly and pp-plugin-assembly-step replaced with __dunder__ convention for collision safety. Other templates not yet updated.

 Build SDK
 - Template .csproj files updated to reference TALXIS.DevKit.Build.Sdk/1.0.0

 Release process
 - Unified: GitHub Release trigger (was push to master), version from tag, release notes in NuGet package