Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 13 additions & 52 deletions src/Dataverse/GenPage/README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,24 @@
# TALXIS.DevKit.Build.Dataverse.GenPage
# TALXIS DevKit Dataverse GenPage build support

MSBuild integration for Dataverse generative page (GenPage) projects. Transpiles TSX source files via TypeScript, patches in RuntimeTypes, generates config metadata, and exposes output targets for Solution projects to discover and integrate GenPages as `uxagentprojects`.
MSBuild integration for Dataverse GenPage projects.

## Installation
A GenPage project is a project-type marker only. It carries no Dataverse IDs or page metadata. Every `*.tsx` file at the project root is treated as a page, and the page name is the file name without extension. Subfolders are regular source folders and never become pages.
Comment thread
TomProkop marked this conversation as resolved.
Outdated

```xml
<PackageReference Include="TALXIS.DevKit.Build.Dataverse.GenPage" Version="0.0.0.1" PrivateAssets="All" />
```

Or use the SDK approach:
## Project contract

```xml
<Project Sdk="TALXIS.DevKit.Build.Sdk/0.0.0.1">
<PropertyGroup>
<ProjectType>GenPage</ProjectType>
<GenPageId>{your-genpage-guid}</GenPageId>
<GenPageDataSources>datasource1,datasource2</GenPageDataSources>
</PropertyGroup>
</Project>
<PropertyGroup>
<ProjectType>GenPage</ProjectType>
</PropertyGroup>
```

## Prerequisites

- **Node.js** must be available on `PATH`
- **npx** must be available on `PATH`

The build will fail with a descriptive error if either is missing.

## How It Works

The package sets `ProjectType` to `GenPage` and disables `GenerateAssemblyInfo` by default since this is not a traditional .NET assembly project.

### Build-time targets

1. **CheckGenPagePrereqs** — validates that the main TSX file exists and `node`/`npx` are on `PATH`.
2. **TranspileGenPage** (runs before `Build`) — executes `tsc` via `npx` to transpile TSX to JS, then patches the output by stripping `RuntimeTypes` imports and prepending `RuntimeTypes.js` content if present.
3. **GenerateGenPageConfig** — generates `config.json` from project properties (`GenPageDataSources`).
4. **CopyGenPageOutputs** (runs after `Build`) — copies `page.tsx`, `page.compiled`, and `config.json` to the output directory.

### Integration targets

Called by `TALXIS.DevKit.Build.Dataverse.Solution` via `ProjectReference`:

- **GetProjectType** — returns `GenPage`.
- **GetGenPageOutputs** — exposes the compiled output folder and metadata for the solution to copy into `uxagentprojects/`.
Optional source files:

## MSBuild Properties
- `<PageName>.config.json`, otherwise shared `genpage.config.json`
- `<PageName>.firstPrompt.json`, otherwise shared `firstPrompt.json`
Comment thread
TomProkop marked this conversation as resolved.
Outdated

| Property | Default | Description |
|----------|---------|-------------|
| `ProjectType` | `GenPage` | Marks the project for reference discovery by Solution projects. |
| `GenPageMainFile` | `page.tsx` | Main TSX source file to transpile. |
| `GenPageName` | `$(MSBuildProjectName)` | Name used for the output folder and metadata. |
| `GenPageId` | _(required)_ | GUID identifying this GenPage in Dataverse. |
| `GenPageDataSources` | _(empty)_ | Comma-separated list of data source identifiers. |
| `LangVersion` | `latest` | C# language version for the project. |
| `GenerateAssemblyInfo` | `false` | Disables auto-generated assembly info. |
Build output is normalized to `$(TargetDir)<PageName>.js` for each root page.

## Related Packages
## Solution integration

- **Depends on**: `TALXIS.DevKit.Build.Dataverse.Tasks`
- **Consumed by**: `TALXIS.DevKit.Build.Dataverse.Solution` projects via `ProjectReference`
Solution projects discover referenced GenPage projects, call `GetGenPageOutputs`, ensure XML-only `uxagentprojects/<page-guid>/...` declarations exist in solution source, then project native `filecontent/` only into the SolutionPackager metadata working directory under `obj`.
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,125 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<GenPageMainFile Condition="'$(GenPageMainFile)'==''">page.tsx</GenPageMainFile>
<GenPageCompiledJsFile>$([System.IO.Path]::ChangeExtension('$(GenPageMainFile)', '.js'))</GenPageCompiledJsFile>
<GenPageName Condition="'$(GenPageName)'==''">$(MSBuildProjectName)</GenPageName>
<GenPageId Condition="'$(GenPageId)'==''"></GenPageId>
<_NormalizedGenPageId>$([System.String]::Copy('$(GenPageId)').Trim().Replace('{','').Replace('}','').ToLowerInvariant())</_NormalizedGenPageId>
<GenPageConfigFile Condition="'$(GenPageConfigFile)'==''">genpage.config.json</GenPageConfigFile>
<RunNodeBuild Condition="'$(RunNodeBuild)'=='' and Exists('$(MSBuildProjectDirectory)/package.json')">true</RunNodeBuild>
<RunNodeBuild Condition="'$(RunNodeBuild)'==''">false</RunNodeBuild>
<GenPageRuntimeTypesPath Condition="'$(GenPageRuntimeTypesPath)'==''">$(MSBuildProjectDirectory)/RuntimeTypes.ts</GenPageRuntimeTypesPath>
<GenPageAllowedBareImports Condition="'$(GenPageAllowedBareImports)'==''">react;react-dom;react-dom/client;react/jsx-runtime;@fluentui/react-components;@fluentui/react-icons</GenPageAllowedBareImports>
</PropertyGroup>

<Target Name="CheckGenPagePrereqs">
<Error Condition="'$(GenPageId)'=='' or '$(_NormalizedGenPageId)'=='' or $(_NormalizedGenPageId.Length)!=36"
Text="GenPageId must be a valid GUID. This GUID identifies the page in Dataverse and the solution." />
<Error Condition="!Exists('$(MSBuildProjectDirectory)/$(GenPageMainFile)')"
Text="GenPage main file not found: $(MSBuildProjectDirectory)/$(GenPageMainFile)" />
<Exec Command="npx --version" IgnoreExitCode="true" />
<Error Condition="'$(MSBuildLastTaskResult)'!='True'"
Text="npx not found in PATH. Install Node.js (includes npx) to build GenPage." />
<Target Name="GetProjectType" Returns="@(_ProjectType)" Condition="'$(ProjectType)'!=''">
Comment thread
TomProkop marked this conversation as resolved.
<ItemGroup>
<_ProjectType Include="$(MSBuildProjectFullPath)">
<ProjectType>$(ProjectType)</ProjectType>
</_ProjectType>
</ItemGroup>
</Target>

<Target Name="TranspileGenPage"
BeforeTargets="Build"
DependsOnTargets="CheckGenPagePrereqs">
<Message Text="Transpiling GenPage $(GenPageMainFile) in $(MSBuildProjectDirectory)" Importance="High" />
<Target Name="GenPageDiscoverEntries">
<ItemGroup>
<_DiscoveredGenPage Remove="@(_DiscoveredGenPage)" />
</ItemGroup>
<GenPageDiscoverPages ProjectDirectory="$(MSBuildProjectDirectory)">
<Output TaskParameter="GenPages" ItemName="_DiscoveredGenPage" />
</GenPageDiscoverPages>
</Target>

<MakeDir Directories="$(IntermediateOutputPath)" />
<Target Name="GenPageCheckPrereqs"
DependsOnTargets="GenPageDiscoverEntries"
Condition="'$(RunNodeBuild)'=='true'">
<Error Condition="!Exists('$(MSBuildProjectDirectory)/package.json')"
Text="GenPage package.json not found in $(MSBuildProjectDirectory)." />
<Exec Command="node --version" IgnoreExitCode="true" />
<Error Condition="'$(MSBuildLastTaskResult)'!='True'" Text="Node.js not found in PATH. Install Node.js to build GenPage." />
<Exec Command="npm --version" IgnoreExitCode="true" />
<Error Condition="'$(MSBuildLastTaskResult)'!='True'" Text="npm not found in PATH. Install Node.js (includes npm) to build GenPage." />
</Target>

<!-- Transpile TSX to JS (IgnoreExitCode: tsc returns non-zero for type errors but still produces output with noEmit=false) -->
<Target Name="GenPageInstallPackagesWithLock"
DependsOnTargets="GenPageCheckPrereqs"
Inputs="$(MSBuildProjectDirectory)/package-lock.json"
Outputs="$(MSBuildProjectDirectory)/node_modules/.package-lock.json"
Condition="'$(RunNodeBuild)'=='true' and Exists('$(MSBuildProjectDirectory)/package-lock.json')">
<Message Text="Installing GenPage npm packages in $(MSBuildProjectDirectory)" Importance="High" />
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="npm ci" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="_GenPageNpmCiExitCode" />
</Exec>
<Exec WorkingDirectory="$(MSBuildProjectDirectory)"
Command="npx --yes -p typescript@5.3.2 tsc &quot;$(GenPageMainFile)&quot; --target ES5 --module ES2020 --jsx react --lib ES2015,DOM --skipLibCheck --noEmit false --outDir &quot;$(IntermediateOutputPath)&quot;"
IgnoreExitCode="true" />
<Error Condition="!Exists('$(IntermediateOutputPath)$(GenPageCompiledJsFile)')"
Text="TypeScript transpilation failed to produce $(GenPageCompiledJsFile). Check tsc output above for errors." />

<!-- Patch: strip RuntimeTypes import and prepend RuntimeTypes.js content -->
<PatchGenPageCompiledCode
CompiledJsPath="$(IntermediateOutputPath)$(GenPageCompiledJsFile)"
RuntimeTypesJsPath="$(MSBuildProjectDirectory)/RuntimeTypes.js"
OutputPath="$(IntermediateOutputPath)page.compiled" />
Command="npm install"
Condition="'$(_GenPageNpmCiExitCode)'!='0'" />
</Target>

<Target Name="CopyGenPageOutputs"
AfterTargets="Build">
<MakeDir Directories="$(OutputPath)$(GenPageName)/" />

<Copy SourceFiles="$(MSBuildProjectDirectory)/$(GenPageMainFile)"
DestinationFiles="$(OutputPath)$(GenPageName)/page.tsx"
SkipUnchangedFiles="true" />
<Target Name="GenPageInstallPackagesWithoutLock"
Comment thread
TomProkop marked this conversation as resolved.
Outdated
DependsOnTargets="GenPageCheckPrereqs"
Inputs="$(MSBuildProjectDirectory)/package.json"
Outputs="$(MSBuildProjectDirectory)/node_modules/.package-lock.json"
Condition="'$(RunNodeBuild)'=='true' and !Exists('$(MSBuildProjectDirectory)/package-lock.json')">
<Message Text="Installing GenPage npm packages in $(MSBuildProjectDirectory)" Importance="High" />
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="npm install" />
</Target>

<Copy SourceFiles="$(IntermediateOutputPath)page.compiled"
DestinationFiles="$(OutputPath)$(GenPageName)/page.compiled"
SkipUnchangedFiles="true" />
<Target Name="GenPageGenerateRuntimeTypes"
DependsOnTargets="GenPageDiscoverEntries"
BeforeTargets="Build">
<GenPageGenerateRuntimeTypes ProjectDirectory="$(MSBuildProjectDirectory)"
OutputPath="$(GenPageRuntimeTypesPath)"
Command="$(GenPageRuntimeTypesCommand)"
Condition="'@(_DiscoveredGenPage)'!=''" />
</Target>

<Copy SourceFiles="$(MSBuildProjectDirectory)/$(GenPageConfigFile)"
DestinationFiles="$(OutputPath)$(GenPageName)/config.json"
SkipUnchangedFiles="true"
Condition="Exists('$(MSBuildProjectDirectory)/$(GenPageConfigFile)')" />
<Target Name="GenPageBuildBundles"
BeforeTargets="Build"
DependsOnTargets="GenPageDiscoverEntries;GenPageInstallPackagesWithLock;GenPageInstallPackagesWithoutLock;GenPageGenerateRuntimeTypes"
Condition="'$(RunNodeBuild)'=='true'">
<Message Text="Building GenPage project in $(MSBuildProjectDirectory)" Importance="High" Condition="'@(_DiscoveredGenPage)'!=''" />
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="npm run build" Condition="'@(_DiscoveredGenPage)'!=''" />
</Target>

<Target Name="GetProjectType" Returns="@(_ProjectType)" Condition="'$(ProjectType)'!=''">
<Target Name="GenPageNormalizeBundleOutputs"
AfterTargets="GenPageBuildBundles"
DependsOnTargets="GenPageDiscoverEntries">
<MakeDir Directories="$(TargetDir)" Condition="'@(_DiscoveredGenPage)'!=''" />
<ItemGroup>
<_ProjectType Include="$(MSBuildProjectFullPath)">
<ProjectType>$(ProjectType)</ProjectType>
</_ProjectType>
<_GenPageBundle Remove="@(_GenPageBundle)" />
</ItemGroup>

<Copy SourceFiles="$(MSBuildProjectDirectory)/dist/%(_DiscoveredGenPage.PageName).js"
Comment thread
TomProkop marked this conversation as resolved.
DestinationFiles="$(TargetDir)%(_DiscoveredGenPage.PageName).js"
SkipUnchangedFiles="true"
Condition="'@(_DiscoveredGenPage)'!='' and Exists('$(MSBuildProjectDirectory)/dist/%(_DiscoveredGenPage.PageName).js')" />

<Copy SourceFiles="$(MSBuildProjectDirectory)/build/%(_DiscoveredGenPage.PageName).js"
DestinationFiles="$(TargetDir)%(_DiscoveredGenPage.PageName).js"
SkipUnchangedFiles="true"
Condition="'@(_DiscoveredGenPage)'!='' and !Exists('$(MSBuildProjectDirectory)/dist/%(_DiscoveredGenPage.PageName).js') and Exists('$(MSBuildProjectDirectory)/build/%(_DiscoveredGenPage.PageName).js')" />

<Error Condition="'@(_DiscoveredGenPage)'!='' and !Exists('$(TargetDir)%(_DiscoveredGenPage.PageName).js')"
Text="GenPage build did not produce $(TargetDir)%(_DiscoveredGenPage.PageName).js (or dist/build equivalent)." />

<ItemGroup Condition="'@(_DiscoveredGenPage)'!=''">
<_GenPageBundle Include="$(TargetDir)%(_DiscoveredGenPage.PageName).js">
<PageName>%(_DiscoveredGenPage.PageName)</PageName>
<CompiledJsPath>$(TargetDir)%(_DiscoveredGenPage.PageName).js</CompiledJsPath>
</_GenPageBundle>
</ItemGroup>

<GenPageValidateBundle Bundles="@(_GenPageBundle)"
AllowedBareImports="$(GenPageAllowedBareImports)"
Condition="'@(_GenPageBundle)'!=''" />
</Target>

<Target Name="GetGenPageOutputs"
DependsOnTargets="Build"
<Target Name="GenPageGetOutputs"
DependsOnTargets="Build;GenPageDiscoverEntries"
Returns="@(_GenPageOutputs)">
<ItemGroup>
<_GenPageOutputs Include="$(MSBuildProjectDirectory)/$(OutputPath)$(GenPageName)">
<GenPageName>$(GenPageName)</GenPageName>
<GenPageId>$(_NormalizedGenPageId)</GenPageId>
<GenPageConfigFile>$(MSBuildProjectDirectory)/$(GenPageConfigFile)</GenPageConfigFile>
<_GenPageOutputs Remove="@(_GenPageOutputs)" />
<_GenPageOutputs Include="$(TargetDir)%(_DiscoveredGenPage.PageName).js">
<PageName>%(_DiscoveredGenPage.PageName)</PageName>
<CompiledJsPath>$(TargetDir)%(_DiscoveredGenPage.PageName).js</CompiledJsPath>
<EntrySourcePath>%(_DiscoveredGenPage.EntryFile)</EntrySourcePath>
<ConfigJsonPath>%(_DiscoveredGenPage.ConfigJsonPath)</ConfigJsonPath>
<FirstPromptJsonPath>%(_DiscoveredGenPage.FirstPromptJsonPath)</FirstPromptJsonPath>
<OwningProjectPath>$(MSBuildProjectFullPath)</OwningProjectPath>
</_GenPageOutputs>
</ItemGroup>
</Target>
Expand Down
Loading