Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@
<Target Name="CheckCodeAppPrereqs"
Condition="'$(RunNodeBuild)'=='true'">
<Error Condition="!Exists('$(MSBuildProjectDirectory)/package.json')" Text="CodeApp 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 CodeApp." />
<Exec Command="npm --version" IgnoreExitCode="true" />
<Error Condition="'$(MSBuildLastTaskResult)'!='True'" Text="npm not found in PATH. Install Node.js (includes npm) to build CodeApp." />
<RunNodeTool FileName="node" Arguments="--version" IgnoreExitCode="true">
Comment thread
TomProkop marked this conversation as resolved.
Outdated
<Output TaskParameter="ExitCode" PropertyName="_CodeAppNodeVersionExitCode" />
</RunNodeTool>
<Error Condition="'$(_CodeAppNodeVersionExitCode)'!='0'" Text="Node.js not found in PATH. Install Node.js to build CodeApp." />
<RunNodeTool FileName="npm" Arguments="--version" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="_CodeAppNpmVersionExitCode" />
</RunNodeTool>
<Error Condition="'$(_CodeAppNpmVersionExitCode)'!='0'" Text="npm not found in PATH. Install Node.js (includes npm) to build CodeApp." />
</Target>

<Target Name="BuildCodeApp"
DependsOnTargets="CheckCodeAppPrereqs"
BeforeTargets="Build"
Condition="'$(RunNodeBuild)'=='true'">
<Message Text="Running npm install in $(MSBuildProjectDirectory)" Importance="High" />
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="npm install" />
<RunNodeTool FileName="npm" Arguments="install" WorkingDirectory="$(MSBuildProjectDirectory)" />
Comment thread
TomProkop marked this conversation as resolved.
Outdated
<Message Text="Running npm run build in $(MSBuildProjectDirectory)" Importance="High" />
<Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="npm run build" />
<RunNodeTool FileName="npm" Arguments="run build" WorkingDirectory="$(MSBuildProjectDirectory)" />
Comment thread
TomProkop marked this conversation as resolved.
Outdated
</Target>

<Target Name="CopyCodeAppDist"
Expand Down
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,131 @@
<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" />

<MakeDir Directories="$(IntermediateOutputPath)" />

<!-- Transpile TSX to JS (IgnoreExitCode: tsc returns non-zero for type errors but still produces output with noEmit=false) -->
<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" />
<Target Name="GenPageDiscoverEntries">
<ItemGroup>
<_DiscoveredGenPage Remove="@(_DiscoveredGenPage)" />
</ItemGroup>
<GenPageDiscoverPages ProjectDirectory="$(MSBuildProjectDirectory)">
<Output TaskParameter="GenPages" ItemName="_DiscoveredGenPage" />
</GenPageDiscoverPages>
</Target>

<Target Name="CopyGenPageOutputs"
AfterTargets="Build">
<MakeDir Directories="$(OutputPath)$(GenPageName)/" />
<Target Name="GenPageCheckPrereqs"
DependsOnTargets="GenPageDiscoverEntries"
Condition="'$(RunNodeBuild)'=='true'">
<Error Condition="!Exists('$(MSBuildProjectDirectory)/package.json')"
Text="GenPage package.json not found in $(MSBuildProjectDirectory)." />
<RunNodeTool FileName="node" Arguments="--version" IgnoreExitCode="true">
Comment thread
TomProkop marked this conversation as resolved.
Outdated
<Output TaskParameter="ExitCode" PropertyName="_GenPageNodeVersionExitCode" />
</RunNodeTool>
<Error Condition="'$(_GenPageNodeVersionExitCode)'!='0'" Text="Node.js not found in PATH. Install Node.js to build GenPage." />
<RunNodeTool FileName="npm" Arguments="--version" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="_GenPageNpmVersionExitCode" />
Comment thread
TomProkop marked this conversation as resolved.
Outdated
</RunNodeTool>
<Error Condition="'$(_GenPageNpmVersionExitCode)'!='0'" Text="npm not found in PATH. Install Node.js (includes npm) to build GenPage." />
</Target>

<Copy SourceFiles="$(MSBuildProjectDirectory)/$(GenPageMainFile)"
DestinationFiles="$(OutputPath)$(GenPageName)/page.tsx"
SkipUnchangedFiles="true" />
<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" />
<RunNodeTool FileName="npm" Arguments="ci" WorkingDirectory="$(MSBuildProjectDirectory)" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="_GenPageNpmCiExitCode" />
</RunNodeTool>
<Warning Condition="'$(_GenPageNpmCiExitCode)'!='0'" Text="npm ci failed with exit code $(_GenPageNpmCiExitCode); falling back to npm install. Lockfile integrity problems may be hidden by the fallback." />
<RunNodeTool FileName="npm"
Arguments="install"
WorkingDirectory="$(MSBuildProjectDirectory)"
Condition="'$(_GenPageNpmCiExitCode)'!='0'" />
</Target>

<Copy SourceFiles="$(IntermediateOutputPath)page.compiled"
DestinationFiles="$(OutputPath)$(GenPageName)/page.compiled"
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" />
<RunNodeTool FileName="npm" Arguments="install" WorkingDirectory="$(MSBuildProjectDirectory)" />
</Target>

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

<Target Name="GetProjectType" Returns="@(_ProjectType)" Condition="'$(ProjectType)'!=''">
<Target Name="GenPageBuildBundles"
BeforeTargets="Build"
DependsOnTargets="GenPageDiscoverEntries;GenPageInstallPackagesWithLock;GenPageInstallPackagesWithoutLock;GenPageGenerateRuntimeTypes"
Condition="'$(RunNodeBuild)'=='true'">
<Message Text="Building GenPage project in $(MSBuildProjectDirectory)" Importance="High" Condition="'@(_DiscoveredGenPage)'!=''" />
<RunNodeTool FileName="npm" Arguments="run build" WorkingDirectory="$(MSBuildProjectDirectory)" Condition="'@(_DiscoveredGenPage)'!=''" />
</Target>

<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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets')" />

<Target Name="NpmInstall" BeforeTargets="BeforeBuild">
<Exec Command="npm install" WorkingDirectory="$(MSBuildProjectDirectory)" />
<RunNodeTool FileName="npm" Arguments="install" WorkingDirectory="$(MSBuildProjectDirectory)" />
</Target>

<!-- We need the build process to infer these numbers before BeforeBuild so that it generates correct header files -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@
BeforeTargets="Build"
Condition="'$(RunNodeBuild)'=='true'">
<Message Text="Building TypeScript in $(TypeScriptDir)" Importance="High" />
<Exec WorkingDirectory="$(TypeScriptDir)" Command="npm install" />
<Exec WorkingDirectory="$(TypeScriptDir)" Command="npm run build" />
<RunNodeTool FileName="npm" Arguments="install" WorkingDirectory="$(TypeScriptDir)" />
<RunNodeTool FileName="npm" Arguments="run build" WorkingDirectory="$(TypeScriptDir)" />
</Target>

<Target Name="CheckScriptLibraryPrereqs"
Condition="'$(RunNodeBuild)'=='true'">
<Error Condition="!Exists('$(TypeScriptDir)')" Text="ScriptLibrary TS directory not found: $(TypeScriptDir)" />
<Error Condition="!Exists('$(TypeScriptDir)/package.json')" Text="ScriptLibrary package.json not found in $(TypeScriptDir)" />
<Exec Command="where node" IgnoreExitCode="true" />
<Error Condition="'$(MSBuildLastTaskResult)'!='True'" Text="Node.js not found in PATH. Install Node.js to build ScriptLibrary." />
<Exec Command="where npm" IgnoreExitCode="true" />
<Error Condition="'$(MSBuildLastTaskResult)'!='True'" Text="npm not found in PATH. Install Node.js (includes npm) to build ScriptLibrary." />
<RunNodeTool FileName="node" Arguments="--version" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="_ScriptLibraryNodeVersionExitCode" />
</RunNodeTool>
<Error Condition="'$(_ScriptLibraryNodeVersionExitCode)'!='0'" Text="Node.js not found in PATH. Install Node.js to build ScriptLibrary." />
<RunNodeTool FileName="npm" Arguments="--version" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="_ScriptLibraryNpmVersionExitCode" />
</RunNodeTool>
<Error Condition="'$(_ScriptLibraryNpmVersionExitCode)'!='0'" Text="npm not found in PATH. Install Node.js (includes npm) to build ScriptLibrary." />
</Target>

<ItemGroup>
Expand Down
Loading