From 5dd9d0efe462d27c72f372f0df7621c816e6f140 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 25 Nov 2025 07:45:44 +0100 Subject: [PATCH 01/52] Solution and pdpackage targets overrode. ScriptLibrary targets added --- TALXIS.DevKit.Build.slnx | 3 + src/Dataverse/PDPackage/README.md | 3 + ...IS.DevKit.Build.Dataverse.PdPackage.csproj | 13 ++++ ...IS.DevKit.Build.Dataverse.PdPackage.nuspec | 27 +++++++ ...S.DevKit.Build.Dataverse.PdPackage.targets | 4 ++ ...S.DevKit.Build.Dataverse.PdPackage.targets | 19 +++++ src/Dataverse/ScriptLibrary/README.md | 3 + ...evKit.Build.Dataverse.ScriptLibrary.csproj | 13 ++++ ...evKit.Build.Dataverse.ScriptLibrary.nuspec | 27 +++++++ ...DevKit.Build.Dataverse.ScriptLibrary.props | 6 ++ ...vKit.Build.Dataverse.ScriptLibrary.targets | 5 ++ ...DevKit.Build.Dataverse.ScriptLibrary.props | 8 +++ ...vKit.Build.Dataverse.ScriptLibrary.targets | 26 +++++++ ...ild.Dataverse.Solution.OverridePAC.targets | 4 ++ ...Dataverse.Solution.ScriptLibraries.targets | 4 ++ ...ild.Dataverse.Solution.OverridePAC.targets | 43 +++++++++++ ...Dataverse.Solution.ScriptLibraries.targets | 72 +++++++++++++++++++ ...LXIS.DevKit.Build.Dataverse.Solution.props | 2 +- ...IS.DevKit.Build.Dataverse.Solution.targets | 42 +++++++---- 19 files changed, 310 insertions(+), 14 deletions(-) create mode 100644 src/Dataverse/PDPackage/README.md create mode 100644 src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj create mode 100644 src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec create mode 100644 src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets create mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets create mode 100644 src/Dataverse/ScriptLibrary/README.md create mode 100644 src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.csproj create mode 100644 src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.nuspec create mode 100644 src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props create mode 100644 src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets create mode 100644 src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props create mode 100644 src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets create mode 100644 src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets create mode 100644 src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets diff --git a/TALXIS.DevKit.Build.slnx b/TALXIS.DevKit.Build.slnx index 9c4b1bf..cbd11a5 100644 --- a/TALXIS.DevKit.Build.slnx +++ b/TALXIS.DevKit.Build.slnx @@ -6,4 +6,7 @@ + + + diff --git a/src/Dataverse/PDPackage/README.md b/src/Dataverse/PDPackage/README.md new file mode 100644 index 0000000..f1608d7 --- /dev/null +++ b/src/Dataverse/PDPackage/README.md @@ -0,0 +1,3 @@ +# TALXIS.DevKit.Build.Dataverse.Tasks + +See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj new file mode 100644 index 0000000..2cc6eab --- /dev/null +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + 0.0.0.1 + TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec + + Version=$(Version) + + + + \ No newline at end of file diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec new file mode 100644 index 0000000..1f8a647 --- /dev/null +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec @@ -0,0 +1,27 @@ + + + + TALXIS.DevKit.Build.Dataverse.PdPackage + $Version$ + TALXIS + true + false + MIT + https://licenses.nuget.org/MIT + README.md + https://github.com/TALXIS/tools-devkit-build + Dataverse MSBuild PDPackage + https://github.com/TALXIS/tools-devkit-build/releases + 2025 NETWORG + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets new file mode 100644 index 0000000..658e6ba --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets new file mode 100644 index 0000000..980ee19 --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dataverse/ScriptLibrary/README.md b/src/Dataverse/ScriptLibrary/README.md new file mode 100644 index 0000000..f1608d7 --- /dev/null +++ b/src/Dataverse/ScriptLibrary/README.md @@ -0,0 +1,3 @@ +# TALXIS.DevKit.Build.Dataverse.Tasks + +See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file diff --git a/src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.csproj b/src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.csproj new file mode 100644 index 0000000..6a1c63d --- /dev/null +++ b/src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + 0.0.0.1 + TALXIS.DevKit.Build.Dataverse.ScriptLibrary.nuspec + + Version=$(Version) + + + + \ No newline at end of file diff --git a/src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.nuspec b/src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.nuspec new file mode 100644 index 0000000..4317d12 --- /dev/null +++ b/src/Dataverse/ScriptLibrary/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.nuspec @@ -0,0 +1,27 @@ + + + + TALXIS.DevKit.Build.Dataverse.ScriptLibrary + $Version$ + TALXIS + true + false + MIT + https://licenses.nuget.org/MIT + README.md + https://github.com/TALXIS/tools-devkit-build + Dataverse MSBuild ScriptLibrary + https://github.com/TALXIS/tools-devkit-build/releases + 2025 NETWORG + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props b/src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props new file mode 100644 index 0000000..07cd5de --- /dev/null +++ b/src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets new file mode 100644 index 0000000..8a0424d --- /dev/null +++ b/src/Dataverse/ScriptLibrary/msbuild/build/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props new file mode 100644 index 0000000..4645b59 --- /dev/null +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props @@ -0,0 +1,8 @@ + + + + $(MSBuildProjectDirectory)\TS\build\main.js + + + + \ No newline at end of file diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets new file mode 100644 index 0000000..6208d8a --- /dev/null +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -0,0 +1,26 @@ + + + + + + <_IsScriptLibrary Include="$(MSBuildProjectFullPath)" /> + + + + + + <_ScriptLibraryOutputs Include="$(MSBuildProjectDirectory)\TS\build\main.js" /> + + + + + + <_ProjectOutputPath Include="$(TargetPath)" /> + + + + \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets new file mode 100644 index 0000000..658e6ba --- /dev/null +++ b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets new file mode 100644 index 0000000..658e6ba --- /dev/null +++ b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets new file mode 100644 index 0000000..1b1cccf --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets @@ -0,0 +1,43 @@ + + + + + + + + <_ScriptLibraryProjectsList>;@(_ScriptLibraryProjects->'%(Identity)'); + + + + <_CdsRefs Include="@(ProjectReference)" /> + + + + <_CdsRefs Remove="@(_CdsRefs)" + Condition="$([System.String]::Copy('$(_ScriptLibraryProjectsList)').Contains(';%(FullPath);'))" /> + + + + + + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets new file mode 100644 index 0000000..dbca952 --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + $(MSBuildProjectDirectory)\$(SolutionRootPath)\WebResources\ + + + + + + + + + <_ScriptFiles Include="@(_ScriptLibraryOutputs)" /> + <_ScriptFilesToCopy Include="@(_ScriptFiles)"> + + $(WebResourcesDir)$(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')) + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props index a10194c..763fffb 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index 72ecfe8..e049176 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -1,15 +1,31 @@ - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + From b5d0c576bcb84fd552c52200a35db40304d05db4 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Wed, 26 Nov 2025 10:01:49 +0100 Subject: [PATCH 02/52] Script library build update --- ...vKit.Build.Dataverse.ScriptLibrary.targets | 20 +++++++++++++++++++ ...Dataverse.Solution.ScriptLibraries.targets | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 6208d8a..172d38a 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -1,6 +1,26 @@ + + false + + $(MSBuildProjectDirectory)\TS + + + + + + + + + + + PreserveNewest + + + <_IsScriptLibrary Include="$(MSBuildProjectFullPath)" /> diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index dbca952..daa3570 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -26,7 +26,7 @@ From a0507f726af6b26704fd5b64f60c6518cfb051b6 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Thu, 4 Dec 2025 16:19:26 +0100 Subject: [PATCH 03/52] sdk base created --- ...DevKit.Build.Dataverse.ScriptLibrary.props | 14 +++++----- ...vKit.Build.Dataverse.ScriptLibrary.targets | 8 +++--- src/Dataverse/Sdk/README.md | 2 ++ src/Dataverse/Sdk/Sdk/Sdk.props | 20 ++++++++++++++ .../Sdk/TALXIS.DevKit.Build.Sdk.csproj | 13 ++++++++++ .../Sdk/TALXIS.DevKit.Build.Sdk.nuspec | 26 +++++++++++++++++++ ...Dataverse.Solution.ScriptLibraries.targets | 10 +++++-- 7 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 src/Dataverse/Sdk/README.md create mode 100644 src/Dataverse/Sdk/Sdk/Sdk.props create mode 100644 src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.csproj create mode 100644 src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.nuspec diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props index 4645b59..196c76e 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props @@ -1,8 +1,8 @@ - - - $(MSBuildProjectDirectory)\TS\build\main.js - - - - \ No newline at end of file + + ScriptLibrary + + $(MSBuildProjectDirectory)\TS\build\main.js + + + diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 172d38a..876d5ea 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -21,9 +21,11 @@ - + - <_IsScriptLibrary Include="$(MSBuildProjectFullPath)" /> + <_ProjectType Include="$(MSBuildProjectFullPath)"> + $(ProjectType) + @@ -43,4 +45,4 @@ - \ No newline at end of file + diff --git a/src/Dataverse/Sdk/README.md b/src/Dataverse/Sdk/README.md new file mode 100644 index 0000000..5a1ce0a --- /dev/null +++ b/src/Dataverse/Sdk/README.md @@ -0,0 +1,2 @@ +# TALXIS.DevKit.Build.Sdk + diff --git a/src/Dataverse/Sdk/Sdk/Sdk.props b/src/Dataverse/Sdk/Sdk/Sdk.props new file mode 100644 index 0000000..9d20a1e --- /dev/null +++ b/src/Dataverse/Sdk/Sdk/Sdk.props @@ -0,0 +1,20 @@ + + + + + TALXIS.DevKit.Build.Dataverse + 0.0.0.1 + $(TALXISDevKitDataversePackageBase).$(ProjectType) + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.csproj b/src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.csproj new file mode 100644 index 0000000..229c117 --- /dev/null +++ b/src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + 0.0.0.1 + TALXIS.DevKit.Build.Sdk.nuspec + + Version=$(Version) + + + + diff --git a/src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.nuspec b/src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.nuspec new file mode 100644 index 0000000..e39f9e7 --- /dev/null +++ b/src/Dataverse/Sdk/TALXIS.DevKit.Build.Sdk.nuspec @@ -0,0 +1,26 @@ + + + + TALXIS.DevKit.Build.Sdk + $Version$ + TALXIS + true + false + MIT + https://licenses.nuget.org/MIT + README.md + https://github.com/TALXIS/tools-devkit-build + Dataverse MSBuild SDK + https://github.com/TALXIS/tools-devkit-build/releases + 2025 NETWORG + + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index daa3570..2dfd997 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -7,13 +7,19 @@ Condition="'@(ProjectReference)'!=''"> - + + + <_ScriptLibraryProjects Remove="@(_ScriptLibraryProjects)" /> + <_ScriptLibraryProjects Include="@(_ProjectTypeFromReferences)" + Condition="'%(ProjectType)'=='ScriptLibrary'" /> + + From 253cb05a85458c7c46df751fc63ed430b1e57b6b Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 5 Dec 2025 13:55:51 +0100 Subject: [PATCH 04/52] sdk complited --- src/Dataverse/Sdk/Sdk/Sdk.props | 3 ++- src/Dataverse/Sdk/Sdk/Sdk.targets | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 src/Dataverse/Sdk/Sdk/Sdk.targets diff --git a/src/Dataverse/Sdk/Sdk/Sdk.props b/src/Dataverse/Sdk/Sdk/Sdk.props index 9d20a1e..c0e3716 100644 --- a/src/Dataverse/Sdk/Sdk/Sdk.props +++ b/src/Dataverse/Sdk/Sdk/Sdk.props @@ -1,6 +1,8 @@ + + TALXIS.DevKit.Build.Dataverse 0.0.0.1 @@ -15,6 +17,5 @@ - diff --git a/src/Dataverse/Sdk/Sdk/Sdk.targets b/src/Dataverse/Sdk/Sdk/Sdk.targets new file mode 100644 index 0000000..8c119d5 --- /dev/null +++ b/src/Dataverse/Sdk/Sdk/Sdk.targets @@ -0,0 +1,2 @@ + + From 11a48e65dea8e7e5b58c28f68721348f76fa3cc8 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 5 Dec 2025 14:56:40 +0100 Subject: [PATCH 05/52] sdk complited --- src/Dataverse/Sdk/Sdk/Sdk.props | 22 +++++----------------- src/Dataverse/Sdk/Sdk/Sdk.targets | 13 +++++++++++++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Dataverse/Sdk/Sdk/Sdk.props b/src/Dataverse/Sdk/Sdk/Sdk.props index c0e3716..6ac8f96 100644 --- a/src/Dataverse/Sdk/Sdk/Sdk.props +++ b/src/Dataverse/Sdk/Sdk/Sdk.props @@ -2,20 +2,8 @@ - - - TALXIS.DevKit.Build.Dataverse - 0.0.0.1 - $(TALXISDevKitDataversePackageBase).$(ProjectType) - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + TALXIS.DevKit.Build.Dataverse + 0.0.0.1 + + diff --git a/src/Dataverse/Sdk/Sdk/Sdk.targets b/src/Dataverse/Sdk/Sdk/Sdk.targets index 8c119d5..8abf675 100644 --- a/src/Dataverse/Sdk/Sdk/Sdk.targets +++ b/src/Dataverse/Sdk/Sdk/Sdk.targets @@ -1,2 +1,15 @@ + + + + $(TALXISDevKitDataversePackageBase).$(ProjectType) + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + From 7a0d42cf56c68ee8feb22cc8612407d2bf972348 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 5 Dec 2025 18:58:49 +0100 Subject: [PATCH 06/52] branches merged --- ...S.DevKit.Build.Dataverse.PdPackage.targets | 8 ++- ...DevKit.Build.Dataverse.DataPackage.targets | 52 +++++++++++++++++++ ...Build.Dataverse.PdPackage.ILRepack.targets | 40 ++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets create mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.ILRepack.targets diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index 658e6ba..5569d13 100644 --- a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -1,4 +1,8 @@ - - \ No newline at end of file + + + + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets new file mode 100644 index 0000000..e7ce5eb --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets @@ -0,0 +1,52 @@ + + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)')) + + + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(TargetDir)'))','DataPackages')) + + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(OutputPath)'))','DataPackages')) + + + $([System.Text.RegularExpressions.Regex]::Replace('$(DataPackageOutputDir)', '[\\/]*$', ''))\ + + + + + <_DataPackageCandidates Include="$(DataPackageSearchRoot)**\[Content_Types].xml" + Condition="Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data_schema.xml'))') and + Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data.xml'))')"> + $([System.IO.Path]::GetDirectoryName('%(FullPath)')) + + + + + <_DataPackageDirs Include="@(_DataPackageCandidates->'%(PackageDir)')" Distinct="true" /> + + + + + + + + + + + <_DataPackageZips Include="@(_DataPackageDirs)"> + $(DataPackageOutputDir)$([System.IO.Path]::GetFileName('%(Identity)')).zip + + + + + + + + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.ILRepack.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.ILRepack.targets new file mode 100644 index 0000000..d63fe14 --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.ILRepack.targets @@ -0,0 +1,40 @@ + + + 2.0.18 + $(NuGetPackageRoot)ilrepack\$(ILRepackVersion)\tools\ILRepack.exe + true + $(TargetDir) + $([System.Text.RegularExpressions.Regex]::Replace('$(ReferencedAssembliesDir)', '[\\/]+$', '')) + + + + + $(TargetPath) + <_KeyFileSwitch Condition="Exists('$(DataversePackageILRepackKeyFile)')">/keyfile:"$(DataversePackageILRepackKeyFile)" + + + + + + + + + + + + + + + + + + + + + + + + From d8332846923286eeec3e680da74ef8ad22a4703a Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 30 Dec 2025 08:22:42 +0100 Subject: [PATCH 07/52] data.xml files generating added --- .gitignore | 8 ++ ...IS.DevKit.Build.Dataverse.PdPackage.nuspec | 3 +- ...vKit.Build.Dataverse.ScriptLibrary.targets | 11 +++ ...Dataverse.Solution.ScriptLibraries.targets | 18 ++++ .../Tasks/Tasks/EnsureWebResourceDataXml.cs | 83 +++++++++++++++++++ .../Tasks/Tasks/GenerateGitVersion.cs | 29 ++++++- ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 3 +- 7 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs diff --git a/.gitignore b/.gitignore index 8a30d25..5842824 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,11 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + + +# TALXIS DevKit temp folders +pack-dataverse-latest-to-local.ps1 +pack-dataverse-selected.ps1 +_temp/r +_export/**/*.* +_temp/**/*.* \ No newline at end of file diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec index 1f8a647..ccdb9c6 100644 --- a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec @@ -17,6 +17,7 @@ commit="c941d55d453c1c64a1e248a240b0327d2dcd76ee" /> + @@ -24,4 +25,4 @@ - \ No newline at end of file + diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 876d5ea..8116df0 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -8,12 +8,23 @@ + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index 2dfd997..5634a9a 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -63,6 +63,9 @@ <_ScriptFilesToCopy Include="@(_ScriptFiles)"> $(WebResourcesDir)$(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')) + $(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')) + $([System.IO.Path]::GetFileName('%(Identity)')) + $(WebResourcesDir)$(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')).data.xml @@ -73,6 +76,21 @@ DestinationFiles="%(DestFile)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" /> + + + <_ScriptFilesMissingDataXml Include="@(_ScriptFilesToCopy)" + Condition="!Exists('%(DataXmlFile)')"> + + + + + + diff --git a/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs new file mode 100644 index 0000000..04cf6b9 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class EnsureWebResourceDataXml : Task +{ + [Required] + public string DataXmlFile { get; set; } + + [Required] + public string WebResourceName { get; set; } + + [Required] + public string DisplayName { get; set; } + + public string WebResourceType { get; set; } = "3"; + + public string IntroducedVersion { get; set; } = "1.0.0.0"; + + public override bool Execute() + { + try + { + if (File.Exists(DataXmlFile)) + { + return true; + } + + var directory = Path.GetDirectoryName(DataXmlFile); + if (!string.IsNullOrWhiteSpace(directory)) + { + Directory.CreateDirectory(directory); + } + + var guid = Guid.NewGuid(); + var guidLower = guid.ToString(); + var guidUpper = guid.ToString().ToUpperInvariant(); + + var doc = new XDocument( + new XDeclaration("1.0", "utf-8", null), + new XElement("WebResource", + new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"), + new XElement("WebResourceId", $"{{{guidLower}}}"), + new XElement("Name", WebResourceName), + new XElement("DisplayName", DisplayName), + new XElement("WebResourceType", string.IsNullOrWhiteSpace(WebResourceType) ? "3" : WebResourceType), + new XElement("IntroducedVersion", string.IsNullOrWhiteSpace(IntroducedVersion) ? "1.0.0.0" : IntroducedVersion), + new XElement("IsEnabledForMobileClient", "0"), + new XElement("IsAvailableForMobileOffline", "0"), + new XElement("IsCustomizable", "1"), + new XElement("CanBeDeleted", "1"), + new XElement("IsHidden", "0"), + new XElement("FileName", $"/WebResources/{WebResourceName}{guidUpper}") + ) + ); + + var settings = new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), + Indent = true, + NewLineChars = Environment.NewLine, + NewLineHandling = NewLineHandling.Replace + }; + + using (var writer = XmlWriter.Create(DataXmlFile, settings)) + { + doc.Save(writer); + } + + Log.LogMessage(MessageImportance.High, $"Generated webresource data.xml: {DataXmlFile}"); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true); + return false; + } + } +} diff --git a/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs b/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs index fe79fe0..6052781 100644 --- a/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs +++ b/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs @@ -41,8 +41,21 @@ public override bool Execute() // Prepare for running git commands var gitInfo = CreateGitProcessInfo(ProjectPath); + if (!IsGitRepository(gitInfo)) + { + Log.LogWarning($"Git repository not found for ProjectPath '{ProjectPath}'. Falling back to LocalBranchBuildVersionNumber."); + VersionOutput = LocalBranchBuildVersionNumber; + return true; + } var currentBranch = GetCurrentBranch(gitInfo); + if (string.IsNullOrWhiteSpace(ApplyToBranches)) + { + Log.LogWarning("ApplyToBranches is empty. Falling back to LocalBranchBuildVersionNumber."); + VersionOutput = LocalBranchBuildVersionNumber; + return true; + } + _branches = ApplyToBranches.Split(';').Select(BranchVersioning.Parse); if (_branches == null || !_branches.Any()) { @@ -214,6 +227,20 @@ private string ExecuteGitCommand(ProcessStartInfo gitInfo, string command) } } } + + private bool IsGitRepository(ProcessStartInfo gitInfo) + { + try + { + string output = ExecuteGitCommand(gitInfo, "rev-parse --is-inside-work-tree"); + return output.Trim().Equals("true", StringComparison.OrdinalIgnoreCase); + } + catch (Exception ex) + { + Log.LogMessage(MessageImportance.Low, $"Git repository check failed: {ex.Message}"); + return false; + } + } private void RetrieveAllProjectReferences(string projectPath, List projects) { var projectFile = ""; @@ -286,4 +313,4 @@ public static BranchVersioning Parse(string branchDefinition) return new BranchVersioning { BranchName = branchName, Prefix = prefix }; } } -} \ No newline at end of file +} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index 5d062d3..1c0dfc8 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -23,4 +23,5 @@ - \ No newline at end of file + + From 24a9b9dd14b5bcedae43fb159369dfcad024701b Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 30 Dec 2025 13:39:39 +0100 Subject: [PATCH 08/52] rootcomponet generating --- .gitignore | 5 +- ...it.Build.Dataverse.Solution.Plugin.targets | 46 ++++++ ...Dataverse.Solution.ScriptLibraries.targets | 13 ++ .../Tasks/EnsurePluginAssemblyDataXml.cs | 0 .../Tasks/EnsureSolutionRootComponents.cs | 137 ++++++++++++++++++ .../Tasks/Tasks/EnsureWebResourceDataXml.cs | 2 - ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 1 + 7 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets create mode 100644 src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs create mode 100644 src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs diff --git a/.gitignore b/.gitignore index 5842824..997d0c9 100644 --- a/.gitignore +++ b/.gitignore @@ -398,9 +398,6 @@ FodyWeavers.xsd *.sln.iml -# TALXIS DevKit temp folders pack-dataverse-latest-to-local.ps1 pack-dataverse-selected.ps1 -_temp/r -_export/**/*.* -_temp/**/*.* \ No newline at end of file +Pack-Dataverse.ps1 diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets new file mode 100644 index 0000000..c3596d3 --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -0,0 +1,46 @@ + + + + + + + + + + + + <_PluginProjects Remove="@(_PluginProjects)" /> + <_PluginProjects Include="@(_ProjectTypeFromReferences)" + Condition="'%(ProjectType)'=='Plugin'" /> + + + + + + + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index 5634a9a..6751975 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -93,4 +93,17 @@ Condition="'@(_ScriptFilesMissingDataXml)'!=''" /> + + + + <_ScriptWebResources Include="@(_ScriptFilesToCopy)" + Condition="'%(_ScriptFilesToCopy.WebResourceName)'!=''" /> + + + + + diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs new file mode 100644 index 0000000..e69de29 diff --git a/src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs b/src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs new file mode 100644 index 0000000..c4296d5 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class EnsureSolutionRootComponents : Task +{ + [Required] + public ITaskItem SolutionXml { get; set; } + + [Required] + public ITaskItem[] WebResources { get; set; } + + public string RootComponentType { get; set; } = "61"; + + public string Behavior { get; set; } = "0"; + + public override bool Execute() + { + try + { + var solutionPath = SolutionXml?.ItemSpec; + if (string.IsNullOrWhiteSpace(solutionPath) || !File.Exists(solutionPath)) + { + Log.LogError($"Solution.xml not found: {solutionPath}"); + return false; + } + + var webResourceNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var item in WebResources ?? Array.Empty()) + { + var name = item.GetMetadata("WebResourceName"); + if (string.IsNullOrWhiteSpace(name)) + { + name = Path.GetFileName(item.ItemSpec); + } + + if (!string.IsNullOrWhiteSpace(name)) + { + webResourceNames.Add(name); + } + } + + if (webResourceNames.Count == 0) + { + Log.LogMessage(MessageImportance.Low, "No web resources to add to Solution.xml."); + return true; + } + + var document = XDocument.Load(solutionPath); + var solutionManifest = document.Root?.Elements() + .FirstOrDefault(e => e.Name.LocalName == "SolutionManifest"); + if (solutionManifest == null) + { + Log.LogError($"SolutionManifest element not found in {solutionPath}"); + return false; + } + + var ns = solutionManifest.Name.Namespace; + var rootComponents = solutionManifest.Elements() + .FirstOrDefault(e => e.Name.LocalName == "RootComponents"); + if (rootComponents == null) + { + rootComponents = new XElement(ns + "RootComponents"); + var missingDependencies = solutionManifest.Elements() + .FirstOrDefault(e => e.Name.LocalName == "MissingDependencies"); + if (missingDependencies != null) + { + missingDependencies.AddBeforeSelf(rootComponents); + } + else + { + solutionManifest.Add(rootComponents); + } + } + + var existing = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var element in rootComponents.Elements().Where(e => e.Name.LocalName == "RootComponent")) + { + var typeValue = element.Attribute("type")?.Value; + var schemaName = element.Attribute("schemaName")?.Value; + if (string.Equals(typeValue, RootComponentType, StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrWhiteSpace(schemaName)) + { + existing.Add(schemaName); + } + } + + var changed = false; + foreach (var name in webResourceNames) + { + if (existing.Contains(name)) + { + continue; + } + + rootComponents.Add(new XElement(ns + "RootComponent", + new XAttribute("type", RootComponentType), + new XAttribute("schemaName", name), + new XAttribute("behavior", Behavior))); + changed = true; + } + + if (!changed) + { + Log.LogMessage(MessageImportance.Low, "Solution.xml already contains all web resource root components."); + return true; + } + + var settings = new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), + Indent = true, + NewLineChars = Environment.NewLine, + NewLineHandling = NewLineHandling.Replace + }; + + using (var writer = XmlWriter.Create(solutionPath, settings)) + { + document.Save(writer); + } + + Log.LogMessage(MessageImportance.High, $"Updated Solution.xml root components: {solutionPath}"); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true); + return false; + } + } +} diff --git a/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs index 04cf6b9..c3c87af 100644 --- a/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsureWebResourceDataXml.cs @@ -16,9 +16,7 @@ public class EnsureWebResourceDataXml : Task [Required] public string DisplayName { get; set; } - public string WebResourceType { get; set; } = "3"; - public string IntroducedVersion { get; set; } = "1.0.0.0"; public override bool Execute() diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index 1c0dfc8..22b6530 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -24,4 +24,5 @@ + From 9d16131fcf5013109518b7c09f1d85752e37645c Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 30 Dec 2025 14:27:41 +0100 Subject: [PATCH 09/52] pluginassembly.data.xml generatinng added --- .../Tasks/EnsurePluginAssemblyDataXml.cs | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index e69de29..28cb236 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -0,0 +1,272 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using System.Collections.Generic; + +public sealed class EnsurePluginAssemblyDataXml : Task +{ + [Required] + public string PluginRootPath { get; set; } = ""; + + [Required] + public string PluginAssemblyId { get; set; } = ""; + + public string? RepositoryRoot { get; set; } + + public string Configuration { get; set; } = "Debug"; + + public string TargetFramework { get; set; } = "net462"; + + public string PublishFolderName { get; set; } = "publish"; + + public override bool Execute() + { + try + { + if (string.IsNullOrWhiteSpace(PluginRootPath)) + throw new ArgumentException("PluginRootPath is empty"); + + if (!Directory.Exists(PluginRootPath)) + throw new DirectoryNotFoundException($"PluginRootPath not found: {PluginRootPath}"); + + var normalizedGuid = NormalizeGuid(PluginAssemblyId); + var repoRoot = !string.IsNullOrWhiteSpace(RepositoryRoot) + ? RepositoryRoot! + : Directory.GetCurrentDirectory(); + + string csprojPath = Directory.GetFiles(PluginRootPath, "*.csproj").FirstOrDefault() + ?? throw new Exception("csproj not found"); + + string projectDirectory = Path.GetDirectoryName(csprojPath) + ?? throw new Exception("ProjectDirectory not resolved"); + + string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); + + (string assemblyName, string fileVersion) = ReadProjectMetadata(csprojPath, csprojFileName); + + string xmlPath = Path.Combine( + repoRoot, + "SolutionDeclarationsRoot", + "PluginAssemblies", + $"{assemblyName}-{normalizedGuid.ToUpperInvariant()}", + $"{assemblyName}.dll.data.xml" + ); + + string dllPath = Path.Combine( + PluginRootPath, + "bin", + Configuration, + TargetFramework, + PublishFolderName, + $"{assemblyName}.dll" + ); + + if (!File.Exists(dllPath)) + throw new FileNotFoundException("Build not found", dllPath); + + var probeDirs = new HashSet(StringComparer.OrdinalIgnoreCase) + { + Path.GetDirectoryName(dllPath)!, // publish + Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework), // output + projectDirectory, + }; + + ResolveEventHandler handler = (sender, args) => + { + var name = new AssemblyName(args.Name).Name; + if (string.IsNullOrWhiteSpace(name)) return null; + + foreach (var dir in probeDirs) + { + var candidate = Path.Combine(dir, name + ".dll"); + if (File.Exists(candidate)) + { + try { return Assembly.LoadFrom(candidate); } + catch { /* ignore */ } + } + } + return null; + }; + + AppDomain.CurrentDomain.AssemblyResolve += handler; + + try + { + string sdkPath = Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework, "Microsoft.Xrm.Sdk.dll"); + if (File.Exists(sdkPath)) + { + TryLoadAssemblyNoThrow(sdkPath); + probeDirs.Add(Path.GetDirectoryName(sdkPath)!); + } + + Assembly pluginAssembly = Assembly.LoadFrom(dllPath); + + byte[] token = pluginAssembly.GetName().GetPublicKeyToken(); + if (token == null || token.Length == 0) + throw new Exception("Build not signed"); + + string publicKeyToken = BitConverter.ToString(token).Replace("-", "").ToLowerInvariant(); + + var classList = GetPluginTypesSafe(pluginAssembly) + .Where(t => t.IsClass && t.IsPublic) + .Where(t => ImplementsInterfaceByName(t, "Microsoft.Xrm.Sdk.IPlugin")) + .Select(t => t.FullName) + .Where(n => !string.IsNullOrWhiteSpace(n)) + .Cast() + .ToList(); + + if (!classList.Any()) + throw new Exception("Plugins not found"); + + Directory.CreateDirectory(Path.GetDirectoryName(xmlPath)!); + + var pluginDoc = new XmlDocument(); + var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); + pluginDoc.AppendChild(xmlDecl); + + XmlElement root = pluginDoc.CreateElement("PluginAssembly"); + root.SetAttribute("FullName", $"{assemblyName}, Version={fileVersion}, Culture=neutral, PublicKeyToken={publicKeyToken}"); + root.SetAttribute("PluginAssemblyId", normalizedGuid); + root.SetAttribute("CustomizationLevel", "1"); + root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + pluginDoc.AppendChild(root); + + XmlElement isolationMode = pluginDoc.CreateElement("IsolationMode"); + isolationMode.InnerText = "2"; + root.AppendChild(isolationMode); + + XmlElement sourceType = pluginDoc.CreateElement("SourceType"); + sourceType.InnerText = "0"; + root.AppendChild(sourceType); + + XmlElement fileName = pluginDoc.CreateElement("FileName"); + fileName.InnerText = $"/PluginAssemblies/{assemblyName}-{normalizedGuid.ToUpperInvariant()}/{assemblyName}.dll"; + root.AppendChild(fileName); + + XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); + root.AppendChild(pluginTypes); + + foreach (var className in classList) + { + if (className == $"{csprojFileName}.PluginBase") + continue; + + XmlElement pluginType = pluginDoc.CreateElement("PluginType"); + pluginType.SetAttribute("AssemblyQualifiedName", + $"{className}, {assemblyName}, Version={fileVersion}, Culture=neutral, PublicKeyToken={publicKeyToken}"); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", className); + + XmlElement friendlyName = pluginDoc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); + + pluginTypes.AppendChild(pluginType); + } + + pluginDoc.Save(xmlPath); + + string destDllPath = Path.Combine(Path.GetDirectoryName(xmlPath)!, $"{assemblyName}.dll"); + File.Copy(dllPath, destDllPath, overwrite: true); + + var solutionDoc = new XmlDocument(); + XmlElement solutionRoot = solutionDoc.CreateElement("RootComponent"); + solutionRoot.SetAttribute("type", "91"); + solutionRoot.SetAttribute("id", $"{{{normalizedGuid}}}"); + solutionRoot.SetAttribute("schemaName", $"{assemblyName}, Version={fileVersion}, Culture=neutral, PublicKeyToken={publicKeyToken}"); + solutionRoot.SetAttribute("behavior", "0"); + + solutionDoc.AppendChild(solutionRoot); + + string tempDir = Path.Combine(repoRoot, ".template.temp"); + Directory.CreateDirectory(tempDir); + + solutionDoc.Save(Path.Combine(tempDir, "RootComponent.xml")); + + Log.LogMessage(MessageImportance.High, $"PluginAssembly data xml generated: {xmlPath}"); + return true; + } + finally + { + AppDomain.CurrentDomain.AssemblyResolve -= handler; + } + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, showStackTrace: true, showDetail: true, file: null); + return false; + } + } + + private static void TryLoadAssemblyNoThrow(string path) + { + try { Assembly.LoadFrom(path); } catch { /* ignore */ } + } + + private static string NormalizeGuid(string guidText) + { + if (string.IsNullOrWhiteSpace(guidText)) + throw new ArgumentException("PluginAssemblyId is empty"); + + var trimmed = guidText.Trim().Trim('{', '}'); + + if (!Guid.TryParse(trimmed, out var g)) + throw new ArgumentException($"PluginAssemblyId is not a valid GUID: {guidText}"); + + return g.ToString("D"); + } + + private static (string AssemblyName, string FileVersion) ReadProjectMetadata(string csprojPath, string fallbackAssemblyName) + { + var xdoc = XDocument.Load(csprojPath); + + string? assemblyName = + xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "AssemblyName")?.Value?.Trim(); + + string? fileVersion = + xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "FileVersion")?.Value?.Trim(); + + if (string.IsNullOrWhiteSpace(assemblyName)) + assemblyName = fallbackAssemblyName; + + if (string.IsNullOrWhiteSpace(fileVersion)) + fileVersion = "1.0.0.0"; + + return (assemblyName, fileVersion); + } + + private static IEnumerable GetPluginTypesSafe(Assembly asm) + { + try + { + return asm.GetTypes(); + } + catch (ReflectionTypeLoadException rtle) + { + var loaderErrors = rtle.LoaderExceptions? + .Where(e => e != null) + .Select(e => e!.Message) + .Distinct() + .ToArray() ?? Array.Empty(); + + return rtle.Types.Where(t => t != null).Cast(); + } + } + + private static bool ImplementsInterfaceByName(Type t, string interfaceFullName) + { + try + { + return t.GetInterfaces().Any(i => string.Equals(i.FullName, interfaceFullName, StringComparison.Ordinal)); + } + catch + { + return false; + } + } +} From 75019b73669f4b96e469aca3ef697b094b719493 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 2 Jan 2026 08:42:16 +0100 Subject: [PATCH 10/52] data.xml files generating added (v2) --- pack-set.ps1 | 2 + ...S.DevKit.Build.Dataverse.PdPackage.targets | 4 +- ....DevKit.Build.Dataverse.CmtPackage.targets | 52 ++++++++ ...DevKit.Build.Dataverse.DataPackage.targets | 52 -------- ...TALXIS.DevKit.Build.Dataverse.Plugin.props | 5 +- ...LXIS.DevKit.Build.Dataverse.Plugin.targets | 36 ++++- ...ild.Dataverse.Solution.OverridePAC.targets | 21 +++ ...it.Build.Dataverse.Solution.Plugin.targets | 79 ++++++++--- ...IS.DevKit.Build.Dataverse.Solution.targets | 4 + .../Tasks/EnsurePluginAssemblyDataXml.cs | 126 ++++++++++-------- ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 1 + 11 files changed, 258 insertions(+), 124 deletions(-) create mode 100644 pack-set.ps1 create mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets delete mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets diff --git a/pack-set.ps1 b/pack-set.ps1 new file mode 100644 index 0000000..f70e293 --- /dev/null +++ b/pack-set.ps1 @@ -0,0 +1,2 @@ +./pack-dataverse-selected.ps1 +./pack-dataverse-latest-to-local.ps1 \ No newline at end of file diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index 5569d13..c193770 100644 --- a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -1,8 +1,8 @@ - + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets new file mode 100644 index 0000000..5921ff2 --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets @@ -0,0 +1,52 @@ + + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)')) + + + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(TargetDir)'))','CmtPackages')) + + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(OutputPath)'))','CmtPackages')) + + + $([System.Text.RegularExpressions.Regex]::Replace('$(CmtPackageOutputDir)', '[\\/]*$', ''))\ + + + + + <_CmtPackageCandidates Include="$(CmtPackageSearchRoot)**\[Content_Types].xml" + Condition="Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data_schema.xml'))') and + Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data.xml'))')"> + $([System.IO.Path]::GetDirectoryName('%(FullPath)')) + + + + + <_CmtPackageDirs Include="@(_CmtPackageCandidates->'%(PackageDir)')" Distinct="true" /> + + + + + + + + + + + <_CmtPackageZips Include="@(_CmtPackageDirs)"> + $(CmtPackageOutputDir)$([System.IO.Path]::GetFileName('%(Identity)')).zip + + + + + + + + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets deleted file mode 100644 index e7ce5eb..0000000 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.DataPackage.targets +++ /dev/null @@ -1,52 +0,0 @@ - - - - $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)')) - - - - $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(TargetDir)'))','DataPackages')) - - - $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(OutputPath)'))','DataPackages')) - - - $([System.Text.RegularExpressions.Regex]::Replace('$(DataPackageOutputDir)', '[\\/]*$', ''))\ - - - - - <_DataPackageCandidates Include="$(DataPackageSearchRoot)**\[Content_Types].xml" - Condition="Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data_schema.xml'))') and - Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data.xml'))')"> - $([System.IO.Path]::GetDirectoryName('%(FullPath)')) - - - - - <_DataPackageDirs Include="@(_DataPackageCandidates->'%(PackageDir)')" Distinct="true" /> - - - - - - - - - - - <_DataPackageZips Include="@(_DataPackageDirs)"> - $(DataPackageOutputDir)$([System.IO.Path]::GetFileName('%(Identity)')).zip - - - - - - - - diff --git a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props index a10194c..2029a55 100644 --- a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props +++ b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props @@ -1,4 +1,7 @@ - \ No newline at end of file + + Plugin + + diff --git a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets index fcdee6d..2b155b3 100644 --- a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets +++ b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets @@ -6,4 +6,38 @@ - \ No newline at end of file + + + + <_ProjectType Include="$(MSBuildProjectFullPath)"> + $(ProjectType) + + + + + + + <_PluginTargetFramework Condition="'$(TargetFramework)'!=''">$(TargetFramework) + <_PluginTargetFramework Condition="'$(_PluginTargetFramework)'=='' and '$(PluginTargetFramework)'!=''">$(PluginTargetFramework) + <_PluginTargetFramework Condition="'$(_PluginTargetFramework)'==''">net462 + + <_PluginPublishFolderName Condition="'$(PluginPublishFolderName)'!=''">$(PluginPublishFolderName) + <_PluginPublishFolderName Condition="'$(_PluginPublishFolderName)'==''">publish + + <_PluginAssemblyName Condition="'$(AssemblyName)'!=''">$(AssemblyName) + <_PluginAssemblyName Condition="'$(_PluginAssemblyName)'==''">$(MSBuildProjectName) + + + + <_PluginAssemblyInfo Include="$(MSBuildProjectFullPath)"> + $(MSBuildProjectDirectory) + $(PluginAssemblyId) + $(_PluginTargetFramework) + $(_PluginPublishFolderName) + $(_PluginAssemblyName) + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets index 1b1cccf..712e64e 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets @@ -7,8 +7,23 @@ DependsOnTargets="ProbeScriptLibraries" Condition="'@(ProjectReference)'!=''"> + + + + + + <_PluginProjects Remove="@(_PluginProjects)" /> + <_PluginProjects Include="@(_ProjectTypeFromReferences)" + Condition="'%(ProjectType)'=='Plugin'" /> + + <_ScriptLibraryProjectsList>;@(_ScriptLibraryProjects->'%(Identity)'); + <_PluginProjectsList>;@(_PluginProjects->'%(Identity)'); @@ -18,12 +33,18 @@ <_CdsRefs Remove="@(_CdsRefs)" Condition="$([System.String]::Copy('$(_ScriptLibraryProjectsList)').Contains(';%(FullPath);'))" /> + <_CdsRefs Remove="@(_CdsRefs)" + Condition="$([System.String]::Copy('$(_PluginProjectsList)').Contains(';%(FullPath);'))" /> + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index c3596d3..d574130 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -2,8 +2,8 @@ - - <_PluginProjects Remove="@(_PluginProjects)" /> - <_PluginProjects Include="@(_ProjectTypeFromReferences)" - Condition="'%(ProjectType)'=='Plugin'" /> + <_PluginLibraryProjects Remove="@(_PluginLibraryProjects)" /> + <_PluginLibraryProjects Include="@(_ProjectTypeFromReferences)" + Condition="'%(ProjectType)'=='Plugin'" /> + Text="Detected Plugin projects: @(_PluginLibraryProjects)" + Condition="'@(_PluginLibraryProjects)'!=''" /> - + - + + - + + + <_PluginAssemblyInfo Remove="@(_PluginAssemblyInfo)" + Condition="'%(_PluginAssemblyInfo.PluginRootPath)'=='' or '%(_PluginAssemblyInfo.AssemblyName)'==''" /> + <_PluginAssemblyInfo Update="@(_PluginAssemblyInfo)" + Condition="'%(_PluginAssemblyInfo.PluginAssemblyId)'==''"> + $([System.Guid]::NewGuid().ToString('D')) + + + + + <_PluginAssembliesRoot>$(DataverseSolutionSourceFolderFullPath)\$(SolutionRootPath)\PluginAssemblies + + + + <_PluginAssemblyInfo Update="@(_PluginAssemblyInfo)" + Condition="Exists('$(_PluginAssembliesRoot)')"> + $([System.IO.Directory]::GetDirectories('$(_PluginAssembliesRoot)', '*%(_PluginAssemblyInfo.AssemblyName)*').Length) + + <_PluginAssemblyInfo Update="@(_PluginAssemblyInfo)" + Condition="!Exists('$(_PluginAssembliesRoot)')"> + 0 + + + + + + + <_PluginAssemblyInfoExisting Include="@(_PluginAssemblyInfo)" + Condition="'%(_PluginAssemblyInfo.PluginAssemblyFolderMatchCount)'!='0'" /> + + + Text="Skipping plugin data.xml generation (folder exists): @(_PluginAssemblyInfoExisting->'%(AssemblyName)', ', ')" + Condition="'@(_PluginAssemblyInfoExisting)'!=''" /> + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index e049176..c11acef 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -24,6 +24,10 @@ + + + diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 28cb236..c7cb035 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -16,12 +16,10 @@ public sealed class EnsurePluginAssemblyDataXml : Task [Required] public string PluginAssemblyId { get; set; } = ""; - public string? RepositoryRoot { get; set; } + public string RepositoryRoot { get; set; } = ""; public string Configuration { get; set; } = "Debug"; - public string TargetFramework { get; set; } = "net462"; - public string PublishFolderName { get; set; } = "publish"; public override bool Execute() @@ -32,29 +30,33 @@ public override bool Execute() throw new ArgumentException("PluginRootPath is empty"); if (!Directory.Exists(PluginRootPath)) - throw new DirectoryNotFoundException($"PluginRootPath not found: {PluginRootPath}"); + throw new DirectoryNotFoundException("PluginRootPath not found: " + PluginRootPath); var normalizedGuid = NormalizeGuid(PluginAssemblyId); + var repoRoot = !string.IsNullOrWhiteSpace(RepositoryRoot) - ? RepositoryRoot! + ? RepositoryRoot : Directory.GetCurrentDirectory(); - string csprojPath = Directory.GetFiles(PluginRootPath, "*.csproj").FirstOrDefault() - ?? throw new Exception("csproj not found"); + string csprojPath = Directory.GetFiles(PluginRootPath, "*.csproj").FirstOrDefault(); + if (csprojPath == null) + throw new Exception("csproj not found"); - string projectDirectory = Path.GetDirectoryName(csprojPath) - ?? throw new Exception("ProjectDirectory not resolved"); + string projectDirectory = Path.GetDirectoryName(csprojPath); + if (string.IsNullOrEmpty(projectDirectory)) + throw new Exception("ProjectDirectory not resolved"); string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); - (string assemblyName, string fileVersion) = ReadProjectMetadata(csprojPath, csprojFileName); + var meta = ReadProjectMetadata(csprojPath, csprojFileName); + string assemblyName = meta.Item1; + string fileVersion = meta.Item2; string xmlPath = Path.Combine( repoRoot, - "SolutionDeclarationsRoot", "PluginAssemblies", - $"{assemblyName}-{normalizedGuid.ToUpperInvariant()}", - $"{assemblyName}.dll.data.xml" + assemblyName + "-" + normalizedGuid.ToUpperInvariant(), + assemblyName + ".dll.data.xml" ); string dllPath = Path.Combine( @@ -63,23 +65,33 @@ public override bool Execute() Configuration, TargetFramework, PublishFolderName, - $"{assemblyName}.dll" + assemblyName + ".dll" ); if (!File.Exists(dllPath)) throw new FileNotFoundException("Build not found", dllPath); - var probeDirs = new HashSet(StringComparer.OrdinalIgnoreCase) - { - Path.GetDirectoryName(dllPath)!, // publish - Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework), // output - projectDirectory, - }; + string dllDir = Path.GetDirectoryName(dllPath); + if (string.IsNullOrEmpty(dllDir)) + throw new Exception("dll directory not resolved"); + + var probeDirs = new HashSet(StringComparer.OrdinalIgnoreCase); + probeDirs.Add(dllDir); // publish + probeDirs.Add(Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework)); // output + probeDirs.Add(projectDirectory); ResolveEventHandler handler = (sender, args) => { - var name = new AssemblyName(args.Name).Name; - if (string.IsNullOrWhiteSpace(name)) return null; + string name = null; + try + { + var an = new AssemblyName(args.Name); + name = an.Name; + } + catch { /* ignore */ } + + if (string.IsNullOrWhiteSpace(name)) + return null; foreach (var dir in probeDirs) { @@ -101,7 +113,10 @@ public override bool Execute() if (File.Exists(sdkPath)) { TryLoadAssemblyNoThrow(sdkPath); - probeDirs.Add(Path.GetDirectoryName(sdkPath)!); + + var sdkDir = Path.GetDirectoryName(sdkPath); + if (!string.IsNullOrEmpty(sdkDir)) + probeDirs.Add(sdkDir); } Assembly pluginAssembly = Assembly.LoadFrom(dllPath); @@ -117,20 +132,23 @@ public override bool Execute() .Where(t => ImplementsInterfaceByName(t, "Microsoft.Xrm.Sdk.IPlugin")) .Select(t => t.FullName) .Where(n => !string.IsNullOrWhiteSpace(n)) - .Cast() .ToList(); if (!classList.Any()) throw new Exception("Plugins not found"); - Directory.CreateDirectory(Path.GetDirectoryName(xmlPath)!); + string xmlDir = Path.GetDirectoryName(xmlPath); + if (string.IsNullOrEmpty(xmlDir)) + throw new Exception("xml directory not resolved"); + + Directory.CreateDirectory(xmlDir); var pluginDoc = new XmlDocument(); var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); pluginDoc.AppendChild(xmlDecl); XmlElement root = pluginDoc.CreateElement("PluginAssembly"); - root.SetAttribute("FullName", $"{assemblyName}, Version={fileVersion}, Culture=neutral, PublicKeyToken={publicKeyToken}"); + root.SetAttribute("FullName", assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken); root.SetAttribute("PluginAssemblyId", normalizedGuid); root.SetAttribute("CustomizationLevel", "1"); root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); @@ -145,7 +163,7 @@ public override bool Execute() root.AppendChild(sourceType); XmlElement fileName = pluginDoc.CreateElement("FileName"); - fileName.InnerText = $"/PluginAssemblies/{assemblyName}-{normalizedGuid.ToUpperInvariant()}/{assemblyName}.dll"; + fileName.InnerText = "/PluginAssemblies/" + assemblyName + "-" + normalizedGuid.ToUpperInvariant() + "/" + assemblyName + ".dll"; root.AppendChild(fileName); XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); @@ -153,12 +171,14 @@ public override bool Execute() foreach (var className in classList) { - if (className == $"{csprojFileName}.PluginBase") + if (className == csprojFileName + ".PluginBase") continue; XmlElement pluginType = pluginDoc.CreateElement("PluginType"); - pluginType.SetAttribute("AssemblyQualifiedName", - $"{className}, {assemblyName}, Version={fileVersion}, Culture=neutral, PublicKeyToken={publicKeyToken}"); + pluginType.SetAttribute( + "AssemblyQualifiedName", + className + ", " + assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken + ); pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); pluginType.SetAttribute("Name", className); @@ -171,16 +191,15 @@ public override bool Execute() pluginDoc.Save(xmlPath); - string destDllPath = Path.Combine(Path.GetDirectoryName(xmlPath)!, $"{assemblyName}.dll"); - File.Copy(dllPath, destDllPath, overwrite: true); + string destDllPath = Path.Combine(xmlDir, assemblyName + ".dll"); + File.Copy(dllPath, destDllPath, true); var solutionDoc = new XmlDocument(); XmlElement solutionRoot = solutionDoc.CreateElement("RootComponent"); solutionRoot.SetAttribute("type", "91"); - solutionRoot.SetAttribute("id", $"{{{normalizedGuid}}}"); - solutionRoot.SetAttribute("schemaName", $"{assemblyName}, Version={fileVersion}, Culture=neutral, PublicKeyToken={publicKeyToken}"); + solutionRoot.SetAttribute("id", "{" + normalizedGuid + "}"); + solutionRoot.SetAttribute("schemaName", assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken); solutionRoot.SetAttribute("behavior", "0"); - solutionDoc.AppendChild(solutionRoot); string tempDir = Path.Combine(repoRoot, ".template.temp"); @@ -188,7 +207,7 @@ public override bool Execute() solutionDoc.Save(Path.Combine(tempDir, "RootComponent.xml")); - Log.LogMessage(MessageImportance.High, $"PluginAssembly data xml generated: {xmlPath}"); + Log.LogMessage(MessageImportance.High, "PluginAssembly data xml generated: " + xmlPath); return true; } finally @@ -198,14 +217,15 @@ public override bool Execute() } catch (Exception ex) { - Log.LogErrorFromException(ex, showStackTrace: true, showDetail: true, file: null); + Log.LogErrorFromException(ex, true, true, null); return false; } } private static void TryLoadAssemblyNoThrow(string path) { - try { Assembly.LoadFrom(path); } catch { /* ignore */ } + try { Assembly.LoadFrom(path); } + catch { /* ignore */ } } private static string NormalizeGuid(string guidText) @@ -215,21 +235,28 @@ private static string NormalizeGuid(string guidText) var trimmed = guidText.Trim().Trim('{', '}'); - if (!Guid.TryParse(trimmed, out var g)) - throw new ArgumentException($"PluginAssemblyId is not a valid GUID: {guidText}"); + Guid g; + if (!Guid.TryParse(trimmed, out g)) + throw new ArgumentException("PluginAssemblyId is not a valid GUID: " + guidText); return g.ToString("D"); } - private static (string AssemblyName, string FileVersion) ReadProjectMetadata(string csprojPath, string fallbackAssemblyName) + // C# 7.3: вместо value tuple -> Tuple + private static Tuple ReadProjectMetadata(string csprojPath, string fallbackAssemblyName) { var xdoc = XDocument.Load(csprojPath); - string? assemblyName = - xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "AssemblyName")?.Value?.Trim(); + string assemblyName = xdoc.Descendants() + .FirstOrDefault(e => e.Name.LocalName == "AssemblyName") + ?.Value; + + string fileVersion = xdoc.Descendants() + .FirstOrDefault(e => e.Name.LocalName == "FileVersion") + ?.Value; - string? fileVersion = - xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "FileVersion")?.Value?.Trim(); + assemblyName = (assemblyName ?? "").Trim(); + fileVersion = (fileVersion ?? "").Trim(); if (string.IsNullOrWhiteSpace(assemblyName)) assemblyName = fallbackAssemblyName; @@ -237,7 +264,7 @@ private static (string AssemblyName, string FileVersion) ReadProjectMetadata(str if (string.IsNullOrWhiteSpace(fileVersion)) fileVersion = "1.0.0.0"; - return (assemblyName, fileVersion); + return Tuple.Create(assemblyName, fileVersion); } private static IEnumerable GetPluginTypesSafe(Assembly asm) @@ -248,12 +275,7 @@ private static IEnumerable GetPluginTypesSafe(Assembly asm) } catch (ReflectionTypeLoadException rtle) { - var loaderErrors = rtle.LoaderExceptions? - .Where(e => e != null) - .Select(e => e!.Message) - .Distinct() - .ToArray() ?? Array.Empty(); - + // просто возвращаем то, что загрузилось return rtle.Types.Where(t => t != null).Cast(); } } diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index 22b6530..ddcfa7e 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -25,4 +25,5 @@ + From 450b3ffdf01b671a4f9e983ae28a85bb0d2f558a Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 2 Jan 2026 21:49:25 +0100 Subject: [PATCH 11/52] data.xml files generating added (v3) --- ...it.Build.Dataverse.Solution.Plugin.targets | 26 +- .../Tasks/EnsurePluginAssemblyDataXml.cs | 525 +++++++++++++----- 2 files changed, 376 insertions(+), 175 deletions(-) diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index d574130..5853e27 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -53,41 +53,17 @@ - - <_PluginAssembliesRoot>$(DataverseSolutionSourceFolderFullPath)\$(SolutionRootPath)\PluginAssemblies - - - - <_PluginAssemblyInfo Update="@(_PluginAssemblyInfo)" - Condition="Exists('$(_PluginAssembliesRoot)')"> - $([System.IO.Directory]::GetDirectories('$(_PluginAssembliesRoot)', '*%(_PluginAssemblyInfo.AssemblyName)*').Length) - - <_PluginAssemblyInfo Update="@(_PluginAssemblyInfo)" - Condition="!Exists('$(_PluginAssembliesRoot)')"> - 0 - - - - - <_PluginAssemblyInfoExisting Include="@(_PluginAssemblyInfo)" - Condition="'%(_PluginAssemblyInfo.PluginAssemblyFolderMatchCount)'!='0'" /> - - - - + Condition="'@(_PluginAssemblyInfo)'!=''" /> diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index c7cb035..028ed1a 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -26,200 +26,416 @@ public override bool Execute() { try { - if (string.IsNullOrWhiteSpace(PluginRootPath)) - throw new ArgumentException("PluginRootPath is empty"); + ValidatePluginRootPath(); + string repoRoot = GetRepositoryRoot(); - if (!Directory.Exists(PluginRootPath)) - throw new DirectoryNotFoundException("PluginRootPath not found: " + PluginRootPath); + string csprojPath = FindProjectFile(PluginRootPath); + string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); + Tuple meta = ReadProjectMetadata(csprojPath, csprojFileName); + string assemblyName = meta.Item1; - var normalizedGuid = NormalizeGuid(PluginAssemblyId); + string existingId = FindPluginAssemblyId(repoRoot, assemblyName); + string effectiveId = !string.IsNullOrWhiteSpace(existingId) ? existingId : PluginAssemblyId; + if (string.IsNullOrWhiteSpace(effectiveId)) + effectiveId = Guid.NewGuid().ToString("D"); - var repoRoot = !string.IsNullOrWhiteSpace(RepositoryRoot) - ? RepositoryRoot - : Directory.GetCurrentDirectory(); + string normalizedGuid = NormalizeGuid(effectiveId); + PluginAssemblyId = normalizedGuid; - string csprojPath = Directory.GetFiles(PluginRootPath, "*.csproj").FirstOrDefault(); - if (csprojPath == null) - throw new Exception("csproj not found"); + PluginProjectInfo info = BuildProjectInfo(repoRoot, normalizedGuid); - string projectDirectory = Path.GetDirectoryName(csprojPath); - if (string.IsNullOrEmpty(projectDirectory)) - throw new Exception("ProjectDirectory not resolved"); + GeneratePluginAssemblyData(info, normalizedGuid); - string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); + Log.LogMessage(MessageImportance.High, "PluginAssembly data xml generated: " + info.XmlPath); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true, true, null); + return false; + } + } - var meta = ReadProjectMetadata(csprojPath, csprojFileName); - string assemblyName = meta.Item1; - string fileVersion = meta.Item2; + private void ValidatePluginRootPath() + { + if (string.IsNullOrWhiteSpace(PluginRootPath)) + throw new ArgumentException("PluginRootPath is empty"); + + if (!Directory.Exists(PluginRootPath)) + throw new DirectoryNotFoundException("PluginRootPath not found: " + PluginRootPath); + } + + private string GetRepositoryRoot() + { + return !string.IsNullOrWhiteSpace(RepositoryRoot) + ? RepositoryRoot + : Directory.GetCurrentDirectory(); + } + + private PluginProjectInfo BuildProjectInfo(string repoRoot, string normalizedGuid) + { + string csprojPath = FindProjectFile(PluginRootPath); + string projectDirectory = GetProjectDirectory(csprojPath); + string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); + + Tuple meta = ReadProjectMetadata(csprojPath, csprojFileName); + string assemblyName = meta.Item1; + string fileVersion = meta.Item2; + + string xmlPath = BuildPluginDataXmlPath(repoRoot, assemblyName, normalizedGuid); + string dllPath = BuildPluginDllPath(assemblyName); + + return new PluginProjectInfo + { + RepositoryRoot = repoRoot, + ProjectDirectory = projectDirectory, + CsprojFileName = csprojFileName, + AssemblyName = assemblyName, + FileVersion = fileVersion, + XmlPath = xmlPath, + DllPath = dllPath + }; + } + + private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalizedGuid) + { + if (!File.Exists(info.DllPath)) + throw new FileNotFoundException("Build not found", info.DllPath); + + HashSet probeDirs = BuildProbeDirectories(info.DllPath, info.ProjectDirectory); + ResolveEventHandler handler = CreateAssemblyResolveHandler(probeDirs); + + AppDomain.CurrentDomain.AssemblyResolve += handler; + + try + { + TryAddSdkAssemblyProbe(probeDirs); + + Assembly pluginAssembly = Assembly.LoadFrom(info.DllPath); + string publicKeyToken = GetPublicKeyToken(pluginAssembly); + + List classList = GetPluginClassNames(pluginAssembly); + if (!classList.Any()) + throw new Exception("Plugins not found"); - string xmlPath = Path.Combine( - repoRoot, - "PluginAssemblies", - assemblyName + "-" + normalizedGuid.ToUpperInvariant(), - assemblyName + ".dll.data.xml" + string xmlDir = EnsureDirectoryForFile(info.XmlPath); + + XmlDocument pluginDoc = CreatePluginAssemblyDocument( + info.AssemblyName, + info.FileVersion, + publicKeyToken, + normalizedGuid, + classList, + info.CsprojFileName ); - string dllPath = Path.Combine( - PluginRootPath, - "bin", - Configuration, - TargetFramework, - PublishFolderName, - assemblyName + ".dll" + pluginDoc.Save(info.XmlPath); + + string destDllPath = Path.Combine(xmlDir, info.AssemblyName + ".dll"); + File.Copy(info.DllPath, destDllPath, true); + + UpsertRootComponentIntoSolutionXml( + info.RepositoryRoot, + normalizedGuid, + info.AssemblyName, + info.FileVersion, + publicKeyToken ); + } + finally + { + AppDomain.CurrentDomain.AssemblyResolve -= handler; + } + } + + private static string FindProjectFile(string pluginRootPath) + { + string csprojPath = Directory.GetFiles(pluginRootPath, "*.csproj").FirstOrDefault(); + if (csprojPath == null) + throw new Exception("csproj not found"); - if (!File.Exists(dllPath)) - throw new FileNotFoundException("Build not found", dllPath); + return csprojPath; + } - string dllDir = Path.GetDirectoryName(dllPath); - if (string.IsNullOrEmpty(dllDir)) - throw new Exception("dll directory not resolved"); + private static string GetProjectDirectory(string csprojPath) + { + string projectDirectory = Path.GetDirectoryName(csprojPath); + if (string.IsNullOrEmpty(projectDirectory)) + throw new Exception("ProjectDirectory not resolved"); - var probeDirs = new HashSet(StringComparer.OrdinalIgnoreCase); - probeDirs.Add(dllDir); // publish - probeDirs.Add(Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework)); // output - probeDirs.Add(projectDirectory); + return projectDirectory; + } - ResolveEventHandler handler = (sender, args) => - { - string name = null; - try - { - var an = new AssemblyName(args.Name); - name = an.Name; - } - catch { /* ignore */ } + private static string BuildPluginDataXmlPath(string repoRoot, string assemblyName, string normalizedGuid) + { + return Path.Combine( + repoRoot, + "PluginAssemblies", + assemblyName + "-" + normalizedGuid.ToUpperInvariant(), + assemblyName + ".dll.data.xml" + ); + } - if (string.IsNullOrWhiteSpace(name)) - return null; + private string FindPluginAssemblyId(string repoRoot, string assemblyName) + { + string pluginAssembliesRoot = Path.Combine(repoRoot, "PluginAssemblies"); - foreach (var dir in probeDirs) - { - var candidate = Path.Combine(dir, name + ".dll"); - if (File.Exists(candidate)) - { - try { return Assembly.LoadFrom(candidate); } - catch { /* ignore */ } - } - } - return null; - }; + if (!Directory.Exists(pluginAssembliesRoot)) return ""; + + var matchDirs = Directory.GetDirectories(pluginAssembliesRoot, "*" + assemblyName + "*"); + + if (matchDirs.Length == 0) return ""; + + var xmlPath = matchDirs.FirstOrDefault() == null ? null : Directory.GetFiles(matchDirs.FirstOrDefault(), "*.xml").FirstOrDefault(); - AppDomain.CurrentDomain.AssemblyResolve += handler; + if (xmlPath == null) return ""; + + var doc = XDocument.Load(xmlPath); + var root = doc.Root; + if (root == null) return ""; + + var idAttr = root.Attribute("PluginAssemblyId"); + return idAttr == null ? "" : idAttr.Value; + } + + private string BuildPluginDllPath(string assemblyName) + { + return Path.Combine( + PluginRootPath, + "bin", + Configuration, + TargetFramework, + PublishFolderName, + assemblyName + ".dll" + ); + } + + private HashSet BuildProbeDirectories(string dllPath, string projectDirectory) + { + string dllDir = Path.GetDirectoryName(dllPath); + if (string.IsNullOrEmpty(dllDir)) + throw new Exception("dll directory not resolved"); + var probeDirs = new HashSet(StringComparer.OrdinalIgnoreCase); + probeDirs.Add(dllDir); + probeDirs.Add(Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework)); + probeDirs.Add(projectDirectory); + + return probeDirs; + } + + private static ResolveEventHandler CreateAssemblyResolveHandler(HashSet probeDirs) + { + return (sender, args) => + { + string name = null; try { - string sdkPath = Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework, "Microsoft.Xrm.Sdk.dll"); - if (File.Exists(sdkPath)) - { - TryLoadAssemblyNoThrow(sdkPath); + var an = new AssemblyName(args.Name); + name = an.Name; + } + catch { /* ignore */ } + + if (string.IsNullOrWhiteSpace(name)) + return null; - var sdkDir = Path.GetDirectoryName(sdkPath); - if (!string.IsNullOrEmpty(sdkDir)) - probeDirs.Add(sdkDir); + foreach (var dir in probeDirs) + { + var candidate = Path.Combine(dir, name + ".dll"); + if (File.Exists(candidate)) + { + try { return Assembly.LoadFrom(candidate); } + catch { /* ignore */ } } + } + return null; + }; + } + + private void TryAddSdkAssemblyProbe(HashSet probeDirs) + { + string sdkPath = Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework, "Microsoft.Xrm.Sdk.dll"); + if (!File.Exists(sdkPath)) + return; - Assembly pluginAssembly = Assembly.LoadFrom(dllPath); + TryLoadAssemblyNoThrow(sdkPath); - byte[] token = pluginAssembly.GetName().GetPublicKeyToken(); - if (token == null || token.Length == 0) - throw new Exception("Build not signed"); + string sdkDir = Path.GetDirectoryName(sdkPath); + if (!string.IsNullOrEmpty(sdkDir)) + probeDirs.Add(sdkDir); + } - string publicKeyToken = BitConverter.ToString(token).Replace("-", "").ToLowerInvariant(); + private static string GetPublicKeyToken(Assembly pluginAssembly) + { + byte[] token = pluginAssembly.GetName().GetPublicKeyToken(); + if (token == null || token.Length == 0) + throw new Exception("Build not signed"); - var classList = GetPluginTypesSafe(pluginAssembly) - .Where(t => t.IsClass && t.IsPublic) - .Where(t => ImplementsInterfaceByName(t, "Microsoft.Xrm.Sdk.IPlugin")) - .Select(t => t.FullName) - .Where(n => !string.IsNullOrWhiteSpace(n)) - .ToList(); + return BitConverter.ToString(token).Replace("-", "").ToLowerInvariant(); + } - if (!classList.Any()) - throw new Exception("Plugins not found"); + private static List GetPluginClassNames(Assembly pluginAssembly) + { + return GetPluginTypesSafe(pluginAssembly) + .Where(t => t.IsClass && t.IsPublic) + .Where(t => ImplementsInterfaceByName(t, "Microsoft.Xrm.Sdk.IPlugin")) + .Select(t => t.FullName) + .Where(n => !string.IsNullOrWhiteSpace(n)) + .ToList(); + } - string xmlDir = Path.GetDirectoryName(xmlPath); - if (string.IsNullOrEmpty(xmlDir)) - throw new Exception("xml directory not resolved"); + private static string EnsureDirectoryForFile(string filePath) + { + string dir = Path.GetDirectoryName(filePath); + if (string.IsNullOrEmpty(dir)) + throw new Exception("xml directory not resolved"); - Directory.CreateDirectory(xmlDir); + Directory.CreateDirectory(dir); + return dir; + } - var pluginDoc = new XmlDocument(); - var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); - pluginDoc.AppendChild(xmlDecl); + private static XmlDocument CreatePluginAssemblyDocument( + string assemblyName, + string fileVersion, + string publicKeyToken, + string normalizedGuid, + IEnumerable classList, + string csprojFileName) + { + var pluginDoc = new XmlDocument(); + var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); + pluginDoc.AppendChild(xmlDecl); - XmlElement root = pluginDoc.CreateElement("PluginAssembly"); - root.SetAttribute("FullName", assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken); - root.SetAttribute("PluginAssemblyId", normalizedGuid); - root.SetAttribute("CustomizationLevel", "1"); - root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); - pluginDoc.AppendChild(root); + XmlElement root = pluginDoc.CreateElement("PluginAssembly"); + root.SetAttribute("FullName", BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken)); + root.SetAttribute("PluginAssemblyId", normalizedGuid); + root.SetAttribute("CustomizationLevel", "1"); + root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + pluginDoc.AppendChild(root); - XmlElement isolationMode = pluginDoc.CreateElement("IsolationMode"); - isolationMode.InnerText = "2"; - root.AppendChild(isolationMode); + XmlElement isolationMode = pluginDoc.CreateElement("IsolationMode"); + isolationMode.InnerText = "2"; + root.AppendChild(isolationMode); - XmlElement sourceType = pluginDoc.CreateElement("SourceType"); - sourceType.InnerText = "0"; - root.AppendChild(sourceType); + XmlElement sourceType = pluginDoc.CreateElement("SourceType"); + sourceType.InnerText = "0"; + root.AppendChild(sourceType); - XmlElement fileName = pluginDoc.CreateElement("FileName"); - fileName.InnerText = "/PluginAssemblies/" + assemblyName + "-" + normalizedGuid.ToUpperInvariant() + "/" + assemblyName + ".dll"; - root.AppendChild(fileName); + XmlElement fileName = pluginDoc.CreateElement("FileName"); + fileName.InnerText = "/PluginAssemblies/" + assemblyName + "-" + normalizedGuid.ToUpperInvariant() + "/" + assemblyName + ".dll"; + root.AppendChild(fileName); - XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); - root.AppendChild(pluginTypes); + XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); + root.AppendChild(pluginTypes); - foreach (var className in classList) - { - if (className == csprojFileName + ".PluginBase") - continue; - - XmlElement pluginType = pluginDoc.CreateElement("PluginType"); - pluginType.SetAttribute( - "AssemblyQualifiedName", - className + ", " + assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken - ); - pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); - pluginType.SetAttribute("Name", className); - - XmlElement friendlyName = pluginDoc.CreateElement("FriendlyName"); - friendlyName.InnerText = Guid.NewGuid().ToString("D"); - pluginType.AppendChild(friendlyName); - - pluginTypes.AppendChild(pluginType); - } + string pluginBaseName = string.IsNullOrEmpty(csprojFileName) ? "" : csprojFileName + ".PluginBase"; - pluginDoc.Save(xmlPath); + foreach (var className in classList) + { + if (className == pluginBaseName) + continue; - string destDllPath = Path.Combine(xmlDir, assemblyName + ".dll"); - File.Copy(dllPath, destDllPath, true); + XmlElement pluginType = pluginDoc.CreateElement("PluginType"); + pluginType.SetAttribute( + "AssemblyQualifiedName", + className + ", " + assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken + ); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", className); - var solutionDoc = new XmlDocument(); - XmlElement solutionRoot = solutionDoc.CreateElement("RootComponent"); - solutionRoot.SetAttribute("type", "91"); - solutionRoot.SetAttribute("id", "{" + normalizedGuid + "}"); - solutionRoot.SetAttribute("schemaName", assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken); - solutionRoot.SetAttribute("behavior", "0"); - solutionDoc.AppendChild(solutionRoot); + XmlElement friendlyName = pluginDoc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); - string tempDir = Path.Combine(repoRoot, ".template.temp"); - Directory.CreateDirectory(tempDir); + pluginTypes.AppendChild(pluginType); + } - solutionDoc.Save(Path.Combine(tempDir, "RootComponent.xml")); + return pluginDoc; + } - Log.LogMessage(MessageImportance.High, "PluginAssembly data xml generated: " + xmlPath); - return true; - } - finally - { - AppDomain.CurrentDomain.AssemblyResolve -= handler; - } + private static void UpsertRootComponentIntoSolutionXml( + string repoRoot, + string normalizedGuid, + string assemblyName, + string fileVersion, + string publicKeyToken) + { + var solutionPath = Path.Combine(repoRoot, "Other", "Solution.xml"); + if (!File.Exists(solutionPath)) + throw new FileNotFoundException("Solution.xml not found", solutionPath); + + var doc = new XmlDocument + { + // Чтобы не разнести форматирование полностью (Doc.Save постарается сохранить пробелы как есть) + PreserveWhitespace = true + }; + + doc.Load(solutionPath); + + // Находим/создаём + XmlElement rootComponents = doc.SelectSingleNode("//RootComponents") as XmlElement; + if (rootComponents == null) + { + if (doc.DocumentElement == null) + throw new Exception("Solution.xml has no document element"); + + rootComponents = doc.CreateElement("RootComponents"); + doc.DocumentElement.AppendChild(rootComponents); } - catch (Exception ex) + + // Собираем нужный id в формате {guid} + var desiredIdBraced = "{" + normalizedGuid + "}"; + + // Ищем существующий RootComponent (type=91 + тот же id), чтобы не плодить дубли + XmlElement existing = null; + foreach (XmlNode n in rootComponents.ChildNodes) { - Log.LogErrorFromException(ex, true, true, null); - return false; + var el = n as XmlElement; + if (el == null) continue; + if (!string.Equals(el.Name, "RootComponent", StringComparison.Ordinal)) continue; + + var typeAttr = el.GetAttribute("type"); + if (!string.Equals(typeAttr, "91", StringComparison.Ordinal)) continue; + + var idAttr = el.GetAttribute("id"); + if (IsSameGuidBraced(idAttr, desiredIdBraced)) + { + existing = el; + break; + } } + + // Создаём/обновляем элемент + XmlElement rc = existing ?? doc.CreateElement("RootComponent"); + rc.SetAttribute("type", "91"); + rc.SetAttribute("id", desiredIdBraced); + rc.SetAttribute("schemaName", BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken)); + rc.SetAttribute("behavior", "0"); + + if (existing == null) + rootComponents.AppendChild(rc); + + doc.Save(solutionPath); + } + + private static bool IsSameGuidBraced(string a, string b) + { + // сравнение GUID без учёта регистра/скобок + string na = NormalizeGuidBraces(a); + string nb = NormalizeGuidBraces(b); + return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase); + } + + private static string NormalizeGuidBraces(string s) + { + if (string.IsNullOrWhiteSpace(s)) return ""; + return s.Trim().Trim('{', '}'); + } + + + private static string BuildAssemblyFullName(string assemblyName, string fileVersion, string publicKeyToken) + { + return assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken; } private static void TryLoadAssemblyNoThrow(string path) @@ -242,7 +458,6 @@ private static string NormalizeGuid(string guidText) return g.ToString("D"); } - // C# 7.3: вместо value tuple -> Tuple private static Tuple ReadProjectMetadata(string csprojPath, string fallbackAssemblyName) { var xdoc = XDocument.Load(csprojPath); @@ -275,7 +490,6 @@ private static IEnumerable GetPluginTypesSafe(Assembly asm) } catch (ReflectionTypeLoadException rtle) { - // просто возвращаем то, что загрузилось return rtle.Types.Where(t => t != null).Cast(); } } @@ -291,4 +505,15 @@ private static bool ImplementsInterfaceByName(Type t, string interfaceFullName) return false; } } + + private sealed class PluginProjectInfo + { + public string RepositoryRoot { get; set; } = ""; + public string ProjectDirectory { get; set; } = ""; + public string CsprojFileName { get; set; } = ""; + public string AssemblyName { get; set; } = ""; + public string FileVersion { get; set; } = ""; + public string XmlPath { get; set; } = ""; + public string DllPath { get; set; } = ""; + } } From 675e0df23b75f5052cf356215ed7ffd9b3237baf Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 2 Jan 2026 21:55:42 +0100 Subject: [PATCH 12/52] data.xml files generating added (done) --- ...XIS.DevKit.Build.Dataverse.Solution.Plugin.targets | 2 +- .../Tasks/Tasks/EnsurePluginAssemblyDataXml.cs | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index 5853e27..85af0b6 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -26,7 +26,7 @@ diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 028ed1a..72fa415 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -364,15 +364,10 @@ private static void UpsertRootComponentIntoSolutionXml( if (!File.Exists(solutionPath)) throw new FileNotFoundException("Solution.xml not found", solutionPath); - var doc = new XmlDocument - { - // Чтобы не разнести форматирование полностью (Doc.Save постарается сохранить пробелы как есть) - PreserveWhitespace = true - }; + var doc = new XmlDocument(); doc.Load(solutionPath); - // Находим/создаём XmlElement rootComponents = doc.SelectSingleNode("//RootComponents") as XmlElement; if (rootComponents == null) { @@ -383,10 +378,8 @@ private static void UpsertRootComponentIntoSolutionXml( doc.DocumentElement.AppendChild(rootComponents); } - // Собираем нужный id в формате {guid} var desiredIdBraced = "{" + normalizedGuid + "}"; - // Ищем существующий RootComponent (type=91 + тот же id), чтобы не плодить дубли XmlElement existing = null; foreach (XmlNode n in rootComponents.ChildNodes) { @@ -405,7 +398,6 @@ private static void UpsertRootComponentIntoSolutionXml( } } - // Создаём/обновляем элемент XmlElement rc = existing ?? doc.CreateElement("RootComponent"); rc.SetAttribute("type", "91"); rc.SetAttribute("id", desiredIdBraced); @@ -420,7 +412,6 @@ private static void UpsertRootComponentIntoSolutionXml( private static bool IsSameGuidBraced(string a, string b) { - // сравнение GUID без учёта регистра/скобок string na = NormalizeGuidBraces(a); string nb = NormalizeGuidBraces(b); return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase); From 9bbab8ea668657e59ece9fa1fb10a759899bbde8 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 5 Jan 2026 07:07:08 +0100 Subject: [PATCH 13/52] Solution.xml management added --- ...vKit.Build.Dataverse.Solution.Data.targets | 4 + ...vKit.Build.Dataverse.Solution.Data.targets | 13 ++ ...IS.DevKit.Build.Dataverse.Solution.targets | 4 + ...TALXIS.DevKit.Build.Dataverse.Tasks.csproj | 3 +- .../Tasks/EnsurePluginAssemblyDataXml.cs | 6 + src/Dataverse/Tasks/Tasks/PatchSolutionXml.cs | 220 ++++++++++++++++++ ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 1 + 7 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets create mode 100644 src/Dataverse/Tasks/Tasks/PatchSolutionXml.cs diff --git a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets new file mode 100644 index 0000000..763fffb --- /dev/null +++ b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets new file mode 100644 index 0000000..6e9b99e --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index c11acef..9639b19 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -20,6 +20,10 @@ + + + diff --git a/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj b/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj index 2e0561b..838a3c9 100644 --- a/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj +++ b/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj @@ -16,6 +16,7 @@ NU1604 portable true + latest false true @@ -58,4 +59,4 @@ - \ No newline at end of file + diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 72fa415..3b87ed3 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -255,12 +255,14 @@ private static ResolveEventHandler CreateAssemblyResolveHandler(HashSet private void TryAddSdkAssemblyProbe(HashSet probeDirs) { string sdkPath = Path.Combine(PluginRootPath, "bin", Configuration, TargetFramework, "Microsoft.Xrm.Sdk.dll"); + if (!File.Exists(sdkPath)) return; TryLoadAssemblyNoThrow(sdkPath); string sdkDir = Path.GetDirectoryName(sdkPath); + if (!string.IsNullOrEmpty(sdkDir)) probeDirs.Add(sdkDir); } @@ -268,6 +270,7 @@ private void TryAddSdkAssemblyProbe(HashSet probeDirs) private static string GetPublicKeyToken(Assembly pluginAssembly) { byte[] token = pluginAssembly.GetName().GetPublicKeyToken(); + if (token == null || token.Length == 0) throw new Exception("Build not signed"); @@ -291,6 +294,7 @@ private static string EnsureDirectoryForFile(string filePath) throw new Exception("xml directory not resolved"); Directory.CreateDirectory(dir); + return dir; } @@ -414,12 +418,14 @@ private static bool IsSameGuidBraced(string a, string b) { string na = NormalizeGuidBraces(a); string nb = NormalizeGuidBraces(b); + return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase); } private static string NormalizeGuidBraces(string s) { if (string.IsNullOrWhiteSpace(s)) return ""; + return s.Trim().Trim('{', '}'); } diff --git a/src/Dataverse/Tasks/Tasks/PatchSolutionXml.cs b/src/Dataverse/Tasks/Tasks/PatchSolutionXml.cs new file mode 100644 index 0000000..6446d12 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/PatchSolutionXml.cs @@ -0,0 +1,220 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +#nullable enable + +public sealed class PatchSolutionXml : Task +{ + [Required] public string ProjectDir { get; set; } = ""; + + public string? Version { get; set; } + public string? Managed { get; set; } + public string? PublisherName { get; set; } + public string? PublisherPrefix { get; set; } + + public bool FailOnManyMatches { get; set; } = true; + public int MaxMatches { get; set; } = 5; + + public override bool Execute() + { + var solutionXmlPath = FindSolutionXml(ProjectDir); + if (solutionXmlPath == null) + { + Log.LogMessage(MessageImportance.Low, $"solution.xml not found under '{ProjectDir}'. Skip."); + return true; + } + + var originalText = File.ReadAllText(solutionXmlPath); + var encoding = DetectEncoding(originalText) ?? Encoding.UTF8; + + var doc = new XmlDocument { PreserveWhitespace = true }; + try + { + doc.LoadXml(originalText); + } + catch (Exception ex) + { + Log.LogError($"Failed to load XML '{solutionXmlPath}': {ex.Message}"); + return false; + } + + bool changed = false; + + if (!string.IsNullOrWhiteSpace(Version)) + changed |= PatchInnerText(doc, "//*[local-name()='Version']", Version!.Trim()); + + if (!string.IsNullOrWhiteSpace(Managed)) + changed |= PatchInnerText(doc, "//*[local-name()='Managed']", Managed!.Trim()); + + if (!string.IsNullOrWhiteSpace(PublisherName)) + { + var name = PublisherName!.Trim(); + + changed |= PatchInnerText(doc, + "//*[local-name()='Publisher']/*[local-name()='UniqueName']", + name); + + changed |= PatchAttribute(doc, + "//*[local-name()='Publisher']//*[local-name()='LocalizedName']/@description", + name); + + changed |= PatchAttribute(doc, + "//*[local-name()='Publisher']//*[local-name()='Description']/@description", + name); + } + + if (!string.IsNullOrWhiteSpace(PublisherPrefix)) + { + var prefix = PublisherPrefix!.Trim().ToLowerInvariant(); + + changed |= PatchInnerText(doc, + "//*[local-name()='Publisher']/*[local-name()='CustomizationPrefix']", + prefix); + } + + if (!changed) + return !Log.HasLoggedErrors; + + var settings = new XmlWriterSettings + { + Indent = false, + NewLineHandling = NewLineHandling.None, + OmitXmlDeclaration = false, + Encoding = encoding + }; + + try + { + using var fs = new FileStream(solutionXmlPath, FileMode.Create, FileAccess.Write, FileShare.Read); + using var xw = XmlWriter.Create(fs, settings); + doc.Save(xw); + } + catch (Exception ex) + { + Log.LogError($"Failed to save XML '{solutionXmlPath}': {ex.Message}"); + return false; + } + + Log.LogMessage(MessageImportance.High, $"Patched solution.xml: {solutionXmlPath}"); + return !Log.HasLoggedErrors; + } + + private bool PatchInnerText(XmlDocument doc, string xpath, string value) + { + var nodes = doc.SelectNodes(xpath); + if (nodes == null || nodes.Count == 0) + return false; + + EnforceSafety(xpath, nodes.Count); + + bool changed = false; + foreach (XmlNode n in nodes) + { + if (n.InnerText != value) + { + n.InnerText = value; + changed = true; + } + } + return changed; + } + + private bool PatchAttribute(XmlDocument doc, string xpath, string value) + { + var nodes = doc.SelectNodes(xpath); + if (nodes == null || nodes.Count == 0) + return false; + + EnforceSafety(xpath, nodes.Count); + + bool changed = false; + foreach (XmlNode n in nodes) + { + if (n is XmlAttribute a && a.Value != value) + { + a.Value = value; + changed = true; + } + } + return changed; + } + + private void EnforceSafety(string xpath, int matches) + { + if (matches <= MaxMatches) return; + + var msg = $"Too many matches ({matches}) for XPath: {xpath}. MaxMatches={MaxMatches}. Fix mapping."; + if (FailOnManyMatches) Log.LogError(msg); + else Log.LogWarning(msg); + } + + private static Encoding? DetectEncoding(string xmlText) + { + var m = Regex.Match(xmlText, + @"<\?xml\s+version\s*=\s*[""'][^""']+[""']\s+encoding\s*=\s*[""'](?[^""']+)[""']", + RegexOptions.IgnoreCase); + if (!m.Success) return null; + + try { return Encoding.GetEncoding(m.Groups["e"].Value); } + catch { return null; } + } + + private string? FindSolutionXml(string projectDir) + { + var candidates = new[] + { + Path.Combine(projectDir, "solution.xml"), + Path.Combine(projectDir, "Solution", "solution.xml"), + Path.Combine(projectDir, "Solution", "Other", "solution.xml"), + Path.Combine(projectDir, "Other", "solution.xml"), + }; + + foreach (var c in candidates) + if (File.Exists(c)) return c; + + return FindByScan(projectDir, "solution.xml", maxDepth: 4); + } + + private static string? FindByScan(string root, string fileName, int maxDepth) + { + bool SkipDir(string d) + { + var name = Path.GetFileName(d); + return name.Equals("bin", StringComparison.OrdinalIgnoreCase) + || name.Equals("obj", StringComparison.OrdinalIgnoreCase) + || name.Equals(".git", StringComparison.OrdinalIgnoreCase) + || name.Equals("node_modules", StringComparison.OrdinalIgnoreCase); + } + + string? Scan(string dir, int depth) + { + if (depth > maxDepth) return null; + + try + { + foreach (var f in Directory.EnumerateFiles(dir, fileName, SearchOption.TopDirectoryOnly)) + return f; + + foreach (var sub in Directory.EnumerateDirectories(dir)) + { + if (SkipDir(sub)) continue; + var found = Scan(sub, depth + 1); + if (found != null) return found; + } + } + catch + { + // ignore access errors + } + + return null; + } + + return Scan(root, 0); + } +} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index ddcfa7e..ccbb8a8 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -26,4 +26,5 @@ + From 0a5ed7f7853035098da2f1532fa78a1961e9fdd6 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 5 Jan 2026 16:28:36 +0100 Subject: [PATCH 14/52] Plugins and script library update --- ...XIS.DevKit.Build.Dataverse.PdPackage.props | 12 +++ ...S.DevKit.Build.Dataverse.PdPackage.targets | 9 ++ ...DevKit.Build.Dataverse.ScriptLibrary.props | 2 + ...vKit.Build.Dataverse.Solution.Data.targets | 17 +++- ...Dataverse.Solution.ScriptLibraries.targets | 6 ++ .../Tasks/Tasks/EnsureCustomizationsNode.cs | 94 +++++++++++++++++++ ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 1 + 7 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props create mode 100644 src/Dataverse/Tasks/Tasks/EnsureCustomizationsNode.cs diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props new file mode 100644 index 0000000..b07f5d7 --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props @@ -0,0 +1,12 @@ + + + + + 1.50.1 + <_PdPackageMsBuildProps Condition="'$(_PdPackageMsBuildProps)'==''"> + $(NuGetPackageRoot)microsoft.powerapps.msbuild.pdpackage\$(PdPackageMsBuildVersion)\build\Microsoft.PowerApps.MSBuild.PDPackage.props + + + + + diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index c193770..a8098f1 100644 --- a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -1,5 +1,14 @@ + + 1.50.1 + <_PdPackageMsBuildTargets Condition="'$(_PdPackageMsBuildTargets)'==''"> + $(NuGetPackageRoot)microsoft.powerapps.msbuild.pdpackage\$(PdPackageMsBuildVersion)\build\Microsoft.PowerApps.MSBuild.PDPackage.targets + + + + + diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props index 196c76e..b357e3b 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props @@ -4,5 +4,7 @@ $(MSBuildProjectDirectory)\TS\build\main.js + latest + false diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets index 6e9b99e..04348fa 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Data.targets @@ -1,6 +1,6 @@ - + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index 6751975..6f9903a 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -106,4 +106,10 @@ WebResources="@(_ScriptWebResources)" /> + + + + diff --git a/src/Dataverse/Tasks/Tasks/EnsureCustomizationsNode.cs b/src/Dataverse/Tasks/Tasks/EnsureCustomizationsNode.cs new file mode 100644 index 0000000..f755885 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/EnsureCustomizationsNode.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class EnsureCustomizationsNode : Task +{ + [Required] + public string CustomizationsXmlFile { get; set; } + + [Required] + public string NodeName { get; set; } + + public override bool Execute() + { + try + { + if (string.IsNullOrWhiteSpace(CustomizationsXmlFile) || !File.Exists(CustomizationsXmlFile)) + { + Log.LogError($"Customizations.xml not found: {CustomizationsXmlFile}"); + + return false; + } + + var nodeName = (NodeName ?? "").Trim(); + + if (string.IsNullOrWhiteSpace(nodeName)) + { + Log.LogError("NodeName is empty."); + + return false; + } + + try + { + XmlConvert.VerifyNCName(nodeName); + } + catch (Exception ex) + { + Log.LogError($"NodeName is not a valid XML name: {nodeName}. {ex.Message}"); + + return false; + } + + var document = XDocument.Load(CustomizationsXmlFile); + var root = document.Root; + + if (root == null) + { + Log.LogError($"Customizations.xml has no document element: {CustomizationsXmlFile}"); + + return false; + } + + var elementName = root.Name.Namespace + nodeName; + + if (root.Elements(elementName).Any()) + { + Log.LogMessage(MessageImportance.Low, $"Customizations.xml already contains node '{nodeName}'."); + + return true; + } + + root.Add(new XElement(elementName)); + + var settings = new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), + Indent = true, + NewLineChars = Environment.NewLine, + NewLineHandling = NewLineHandling.Replace + }; + + using (var writer = XmlWriter.Create(CustomizationsXmlFile, settings)) + { + document.Save(writer); + } + + Log.LogMessage(MessageImportance.High, $"Added node '{nodeName}' to Customizations.xml: {CustomizationsXmlFile}"); + + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true); + + return false; + } + } +} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index ccbb8a8..1a69ebb 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -26,5 +26,6 @@ + From e603a67b03db402a5ea65137e51b0e664f2ef218 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Thu, 8 Jan 2026 13:32:26 +0100 Subject: [PATCH 15/52] Update --- ....DevKit.Build.Dataverse.CmtPackage.targets | 55 ++++-- ...S.DevKit.Build.Dataverse.PdPackage.targets | 60 +++++-- ...it.Build.Dataverse.Solution.Plugin.targets | 4 + ...Dataverse.Solution.ScriptLibraries.targets | 24 +-- .../Tasks/Tasks/AddRootComponentToSolution.cs | 128 ++++++++++++++ .../Tasks/Tasks/ApplyVersionNumber.cs | 44 +---- .../Tasks/EnsurePluginAssemblyDataXml.cs | 160 ++++++++++++++++-- .../Tasks/EnsureSolutionRootComponents.cs | 137 --------------- ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 2 +- 9 files changed, 372 insertions(+), 242 deletions(-) create mode 100644 src/Dataverse/Tasks/Tasks/AddRootComponentToSolution.cs delete mode 100644 src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets index 5921ff2..73766c7 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets @@ -11,42 +11,65 @@ $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(OutputPath)'))','CmtPackages')) - $([System.Text.RegularExpressions.Regex]::Replace('$(CmtPackageOutputDir)', '[\\/]*$', ''))\ + $([System.String]::Copy('$([System.Text.RegularExpressions.Regex]::Replace('$(CmtPackageOutputDir)', + '[\\/]*$', ''))').Trim()) - <_CmtPackageCandidates Include="$(CmtPackageSearchRoot)**\[Content_Types].xml" - Condition="Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data_schema.xml'))') and - Exists('$([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(FullPath)'))','data.xml'))')"> - $([System.IO.Path]::GetDirectoryName('%(FullPath)')) + <_CmtPackageCandidates Include="@(None->'%(FullPath)')" + Condition="'%(Filename)%(Extension)'=='[Content_Types].xml'"> + $([System.IO.Path]::GetDirectoryName('%(Identity)')) + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(Identity)'))','data_schema.xml')) + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(Identity)'))','data.xml')) + <_CmtPackageCandidates Include="@(Content->'%(FullPath)')" + Condition="'%(Filename)%(Extension)'=='[Content_Types].xml'"> + $([System.IO.Path]::GetDirectoryName('%(Identity)')) + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(Identity)'))','data_schema.xml')) + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetDirectoryName('%(Identity)'))','data.xml')) + + + + + <_CmtPackageDirsRaw Include="@(_CmtPackageCandidates)" + Condition="Exists('%(_CmtPackageCandidates.DataSchemaPath)') and + Exists('%(_CmtPackageCandidates.DataPath)')"> + %(PackageDir) + - <_CmtPackageDirs Include="@(_CmtPackageCandidates->'%(PackageDir)')" Distinct="true" /> + <_CmtPackageDirs Include="@(_CmtPackageDirsRaw->'%(PackageDir)')" Distinct="true" /> - + + + AfterTargets="Build" + DependsOnTargets="TalxisDiscoverCmtPackages"> <_CmtPackageZips Include="@(_CmtPackageDirs)"> - $(CmtPackageOutputDir)$([System.IO.Path]::GetFileName('%(Identity)')).zip + $([System.IO.Path]::Combine('$(CmtPackageOutputDir)','%(Filename).zip')) - + - + - + \ No newline at end of file diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index 980ee19..fc375b9 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -1,19 +1,49 @@ - - - - - - - - + + true + - \ No newline at end of file + + + + + + + + false + + + + + + + + + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index 85af0b6..980eb7e 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -51,6 +51,9 @@ Condition="'%(_PluginAssemblyInfo.PluginAssemblyId)'==''"> $([System.Guid]::NewGuid().ToString('D')) + <_PluginAssemblyInfo Update="@(_PluginAssemblyInfo)"> + $([System.IO.Path]::Combine('%(_PluginAssemblyInfo.PluginRootPath)','bin','$(Configuration)','%(_PluginAssemblyInfo.TargetFramework)','%(_PluginAssemblyInfo.PublishFolderName)','%(_PluginAssemblyInfo.AssemblyName).dll')) + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index 6f9903a..5d8130f 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -91,25 +91,13 @@ WebResourceName="%(_ScriptFilesMissingDataXml.WebResourceName)" DisplayName="%(_ScriptFilesMissingDataXml.DisplayName)" Condition="'@(_ScriptFilesMissingDataXml)'!=''" /> - - - - - - <_ScriptWebResources Include="@(_ScriptFilesToCopy)" - Condition="'%(_ScriptFilesToCopy.WebResourceName)'!=''" /> - - - - - - + diff --git a/src/Dataverse/Tasks/Tasks/AddRootComponentToSolution.cs b/src/Dataverse/Tasks/Tasks/AddRootComponentToSolution.cs new file mode 100644 index 0000000..094b1e5 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/AddRootComponentToSolution.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Xml; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public sealed class AddRootComponentToSolution : Task +{ + [Required] + public string SolutionPath { get; set; } = ""; + + [Required] + public string Type { get; set; } = ""; + + public string Id { get; set; } = ""; + + public string SchemaName { get; set; } = ""; + + public string Behavior { get; set; } = "0"; + + public override bool Execute() + { + try + { + ValidateInputs(); + + var fullPath = Path.GetFullPath(SolutionPath); + var doc = new XmlDocument(); + doc.Load(fullPath); + + var rootComponents = doc.SelectSingleNode("//RootComponents") as XmlElement; + if (rootComponents == null) + { + if (doc.DocumentElement == null) + throw new InvalidOperationException("Solution.xml is missing a document element."); + + rootComponents = doc.CreateElement("RootComponents"); + doc.DocumentElement.AppendChild(rootComponents); + } + + if (!ExistsAlready(rootComponents)) + { + var rc = doc.CreateElement("RootComponent"); + rc.SetAttribute("type", Type.Trim()); + + if (!string.IsNullOrWhiteSpace(Id)) + rc.SetAttribute("id", Normalize(Id)); + + if (!string.IsNullOrWhiteSpace(SchemaName)) + rc.SetAttribute("schemaName", SchemaName.Trim()); + + if (!string.IsNullOrWhiteSpace(Behavior)) + rc.SetAttribute("behavior", Behavior.Trim()); + + rootComponents.AppendChild(rc); + Log.LogMessage(MessageImportance.High, $"RootComponent added to {fullPath}"); + } + else + { + Log.LogMessage(MessageImportance.Low, "RootComponent already present, no changes written."); + } + + doc.Save(fullPath); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true, true, null); + return false; + } + } + + private void ValidateInputs() + { + if (string.IsNullOrWhiteSpace(SolutionPath)) + throw new ArgumentException("SolutionPath is required."); + + if (!File.Exists(SolutionPath)) + throw new FileNotFoundException("Solution.xml not found", SolutionPath); + + if (string.IsNullOrWhiteSpace(Type)) + throw new ArgumentException("Type is required."); + + if (string.IsNullOrWhiteSpace(Id) && string.IsNullOrWhiteSpace(SchemaName)) + throw new ArgumentException("Either Id or SchemaName must be provided."); + } + + private bool ExistsAlready(XmlElement rootComponents) + { + foreach (XmlNode node in rootComponents.ChildNodes) + { + if (node is not XmlElement el) + continue; + + if (!string.Equals(el.Name, "RootComponent", StringComparison.Ordinal)) + continue; + + var typeAttr = el.GetAttribute("type"); + if (!string.Equals(typeAttr, Type.Trim(), StringComparison.Ordinal)) + continue; + + var idAttr = el.GetAttribute("id"); + var schemaAttr = el.GetAttribute("schemaName"); + + bool idMatches = !string.IsNullOrWhiteSpace(Id) && + string.Equals(Normalize(idAttr), Normalize(Id), StringComparison.OrdinalIgnoreCase); + + bool schemaMatches = !string.IsNullOrWhiteSpace(SchemaName) && + string.Equals(schemaAttr?.Trim(), SchemaName.Trim(), StringComparison.Ordinal); + + if ((idMatches && !string.IsNullOrWhiteSpace(Id)) || + (schemaMatches && !string.IsNullOrWhiteSpace(SchemaName))) + { + return true; + } + } + + return false; + } + + private static string Normalize(string guidLike) + { + if (string.IsNullOrWhiteSpace(guidLike)) + return ""; + + return guidLike.Trim().Trim('{', '}'); + } +} diff --git a/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs b/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs index 9a35694..56f03f7 100644 --- a/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs +++ b/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs @@ -27,49 +27,7 @@ public class ApplyVersionNumber : Task public override bool Execute() { - UpdateVersionInSolutionXmlFile(SolutionXml.ItemSpec, Version); - if (PluginAssembliesFolder != null && Directory.Exists(PluginAssembliesFolder.ItemSpec)) - { - var pluginAssemblies = Directory.EnumerateFiles(PluginAssembliesFolder.ItemSpec, "*.dll.data.xml", SearchOption.AllDirectories); - foreach (var pluginAssemblyXmlPath in pluginAssemblies) - { - var pluginAssemblyDocument = XDocument.Load(pluginAssemblyXmlPath); - var fullNameAttributeValue = pluginAssemblyDocument.Root.Attribute("FullName")?.Value; - var assemblyName = fullNameAttributeValue?.Split(',')[0].Trim(); - var assembly = Assembly.LoadFrom(pluginAssemblyXmlPath.Replace(".data.xml", "")); - _assemblies.Add(assembly); - - Log.LogMessage(MessageImportance.High, $" > Discovered {assembly.FullName} at {pluginAssemblyXmlPath}"); - - var solutionXml = XDocument.Load(SolutionXml.ItemSpec); - // Find all RootComponents of type PluginAssembly (91) and update schemaName to match assembly name - match based on startsWith assemblyName - var rootComponents = solutionXml.Descendants("RootComponent") - .Where(rc => rc.Attribute("type")?.Value == "91" && rc.Attribute("schemaName")?.Value.StartsWith(assemblyName, StringComparison.OrdinalIgnoreCase) == true); - foreach (var rootComponent in rootComponents) - { - rootComponent.Attribute("schemaName").SetValue(assembly.FullName); - } - File.WriteAllText(SolutionXml.ItemSpec, solutionXml.ToString()); - } - } - if (WorkflowsFolder != null && Directory.Exists(WorkflowsFolder.ItemSpec)) - { - var workflows = Directory.EnumerateFiles(WorkflowsFolder.ItemSpec, "*.xml", SearchOption.AllDirectories); - foreach (var workflowXmlPath in workflows) - { - Log.LogMessage(MessageImportance.High, $"Processing {workflowXmlPath}"); - UpdateVersionInWorkflowFiles(workflowXmlPath); - } - } - if(SdkMessageProcessingStepsFolder != null && Directory.Exists(SdkMessageProcessingStepsFolder.ItemSpec)) - { - var sdkMessageProcessingSteps = Directory.EnumerateFiles(SdkMessageProcessingStepsFolder.ItemSpec, "*.xml", SearchOption.AllDirectories); - foreach (var sdkMessageProcessingStepXmlPath in sdkMessageProcessingSteps) - { - Log.LogMessage(MessageImportance.High, $"Processing {sdkMessageProcessingStepXmlPath}"); - UpdateVersionInSdkMessageProcessingStepFiles(sdkMessageProcessingStepXmlPath); - } - } + return true; } diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 3b87ed3..0a8d377 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -21,6 +21,8 @@ public sealed class EnsurePluginAssemblyDataXml : Task public string Configuration { get; set; } = "Debug"; public string TargetFramework { get; set; } = "net462"; public string PublishFolderName { get; set; } = "publish"; + public string PluginDllPath { get; set; } = ""; + public bool CopyPluginDll { get; set; } = true; public override bool Execute() { @@ -82,8 +84,9 @@ private PluginProjectInfo BuildProjectInfo(string repoRoot, string normalizedGui string assemblyName = meta.Item1; string fileVersion = meta.Item2; + string sourceDllPath = ResolvePluginDllPath(assemblyName); + string dllPath = PrepareWorkingPluginDll(assemblyName, normalizedGuid); string xmlPath = BuildPluginDataXmlPath(repoRoot, assemblyName, normalizedGuid); - string dllPath = BuildPluginDllPath(assemblyName); return new PluginProjectInfo { @@ -93,7 +96,8 @@ private PluginProjectInfo BuildProjectInfo(string repoRoot, string normalizedGui AssemblyName = assemblyName, FileVersion = fileVersion, XmlPath = xmlPath, - DllPath = dllPath + DllPath = dllPath, + SourceDllPath = sourceDllPath }; } @@ -126,13 +130,17 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize publicKeyToken, normalizedGuid, classList, - info.CsprojFileName + info.CsprojFileName, + info.XmlPath ); pluginDoc.Save(info.XmlPath); - string destDllPath = Path.Combine(xmlDir, info.AssemblyName + ".dll"); - File.Copy(info.DllPath, destDllPath, true); + if (CopyPluginDll) + { + string destDllPath = Path.Combine(xmlDir, info.AssemblyName + ".dll"); + File.Copy(info.DllPath, destDllPath, true); + } UpsertRootComponentIntoSolutionXml( info.RepositoryRoot, @@ -210,6 +218,44 @@ private string BuildPluginDllPath(string assemblyName) ); } + private string ResolvePluginDllPath(string assemblyName) + { + if (!string.IsNullOrWhiteSpace(PluginDllPath)) + { + var candidate = PluginDllPath; + if (!Path.IsPathRooted(candidate)) + candidate = Path.Combine(PluginRootPath, candidate); + + return Path.GetFullPath(candidate); + } + + return BuildPluginDllPath(assemblyName); + } + + private string PrepareWorkingPluginDll(string assemblyName, string normalizedGuid) + { + string sourceDllPath = ResolvePluginDllPath(assemblyName); + + if (!File.Exists(sourceDllPath)) + throw new FileNotFoundException("Build not found", sourceDllPath); + + string metadataDir = Path.Combine( + PluginRootPath, + "obj", + Configuration, + TargetFramework, + "Metadata", + "PluginAssemblies", + assemblyName + "-" + normalizedGuid.ToUpperInvariant()); + + Directory.CreateDirectory(metadataDir); + + string workingDllPath = Path.Combine(metadataDir, assemblyName + ".dll"); + File.Copy(sourceDllPath, workingDllPath, true); + + return workingDllPath; + } + private HashSet BuildProbeDirectories(string dllPath, string projectDirectory) { string dllDir = Path.GetDirectoryName(dllPath); @@ -304,7 +350,8 @@ private static XmlDocument CreatePluginAssemblyDocument( string publicKeyToken, string normalizedGuid, IEnumerable classList, - string csprojFileName) + string csprojFileName, + string existingXmlPath) { var pluginDoc = new XmlDocument(); var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); @@ -332,24 +379,48 @@ private static XmlDocument CreatePluginAssemblyDocument( XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); root.AppendChild(pluginTypes); + var existingPluginTypes = LoadExistingPluginTypeMap(existingXmlPath, pluginDoc); + string pluginBaseName = string.IsNullOrEmpty(csprojFileName) ? "" : csprojFileName + ".PluginBase"; + var seen = new HashSet(StringComparer.Ordinal); foreach (var className in classList) { if (className == pluginBaseName) continue; - XmlElement pluginType = pluginDoc.CreateElement("PluginType"); + if (!seen.Add(className)) + continue; + + XmlElement pluginType; + + if (existingPluginTypes.TryGetValue(className, out var existingPluginType)) + { + pluginType = existingPluginType; + } + else + { + pluginType = CreatePluginTypeElement(pluginDoc); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", className); + } + pluginType.SetAttribute( "AssemblyQualifiedName", - className + ", " + assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken + BuildAssemblyQualifiedTypeName(className, assemblyName, fileVersion, publicKeyToken) ); - pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); pluginType.SetAttribute("Name", className); - XmlElement friendlyName = pluginDoc.CreateElement("FriendlyName"); - friendlyName.InnerText = Guid.NewGuid().ToString("D"); - pluginType.AppendChild(friendlyName); + var friendlyName = pluginType.SelectSingleNode("FriendlyName") as XmlElement; + if (friendlyName == null) + { + friendlyName = pluginDoc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); + } + + if (string.IsNullOrWhiteSpace(pluginType.GetAttribute("PluginTypeId"))) + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); pluginTypes.AppendChild(pluginType); } @@ -435,6 +506,11 @@ private static string BuildAssemblyFullName(string assemblyName, string fileVers return assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken; } + private static string BuildAssemblyQualifiedTypeName(string className, string assemblyName, string fileVersion, string publicKeyToken) + { + return className + ", " + BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken); + } + private static void TryLoadAssemblyNoThrow(string path) { try { Assembly.LoadFrom(path); } @@ -503,6 +579,65 @@ private static bool ImplementsInterfaceByName(Type t, string interfaceFullName) } } + private static Dictionary LoadExistingPluginTypeMap(string xmlPath, XmlDocument targetDoc) + { + var result = new Dictionary(StringComparer.Ordinal); + + if (!File.Exists(xmlPath)) + return result; + + var existingDoc = new XmlDocument(); + existingDoc.Load(xmlPath); + + var pluginTypesNode = existingDoc.SelectSingleNode("//PluginAssembly/PluginTypes") as XmlElement; + if (pluginTypesNode == null) + return result; + + foreach (var node in pluginTypesNode.ChildNodes) + { + var el = node as XmlElement; + if (el == null) + continue; + + if (!string.Equals(el.Name, "PluginType", StringComparison.Ordinal)) + continue; + + string className = GetPluginTypeClassName(el); + if (string.IsNullOrWhiteSpace(className)) + continue; + + if (result.ContainsKey(className)) + continue; + + var imported = (XmlElement)targetDoc.ImportNode(el, true); + result[className] = imported; + } + + return result; + } + + private static string GetPluginTypeClassName(XmlElement pluginTypeElement) + { + string nameAttr = pluginTypeElement.GetAttribute("Name"); + if (!string.IsNullOrWhiteSpace(nameAttr)) + return nameAttr.Trim(); + + string aqn = pluginTypeElement.GetAttribute("AssemblyQualifiedName"); + if (string.IsNullOrWhiteSpace(aqn)) + return ""; + + int commaIndex = aqn.IndexOf(','); + if (commaIndex < 0) + return aqn.Trim(); + + return aqn.Substring(0, commaIndex).Trim(); + } + + private static XmlElement CreatePluginTypeElement(XmlDocument doc) + { + return doc.CreateElement("PluginType"); + } + private sealed class PluginProjectInfo { public string RepositoryRoot { get; set; } = ""; @@ -512,5 +647,6 @@ private sealed class PluginProjectInfo public string FileVersion { get; set; } = ""; public string XmlPath { get; set; } = ""; public string DllPath { get; set; } = ""; + public string SourceDllPath { get; set; } = ""; } } diff --git a/src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs b/src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs deleted file mode 100644 index c4296d5..0000000 --- a/src/Dataverse/Tasks/Tasks/EnsureSolutionRootComponents.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using System.Xml.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -public class EnsureSolutionRootComponents : Task -{ - [Required] - public ITaskItem SolutionXml { get; set; } - - [Required] - public ITaskItem[] WebResources { get; set; } - - public string RootComponentType { get; set; } = "61"; - - public string Behavior { get; set; } = "0"; - - public override bool Execute() - { - try - { - var solutionPath = SolutionXml?.ItemSpec; - if (string.IsNullOrWhiteSpace(solutionPath) || !File.Exists(solutionPath)) - { - Log.LogError($"Solution.xml not found: {solutionPath}"); - return false; - } - - var webResourceNames = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var item in WebResources ?? Array.Empty()) - { - var name = item.GetMetadata("WebResourceName"); - if (string.IsNullOrWhiteSpace(name)) - { - name = Path.GetFileName(item.ItemSpec); - } - - if (!string.IsNullOrWhiteSpace(name)) - { - webResourceNames.Add(name); - } - } - - if (webResourceNames.Count == 0) - { - Log.LogMessage(MessageImportance.Low, "No web resources to add to Solution.xml."); - return true; - } - - var document = XDocument.Load(solutionPath); - var solutionManifest = document.Root?.Elements() - .FirstOrDefault(e => e.Name.LocalName == "SolutionManifest"); - if (solutionManifest == null) - { - Log.LogError($"SolutionManifest element not found in {solutionPath}"); - return false; - } - - var ns = solutionManifest.Name.Namespace; - var rootComponents = solutionManifest.Elements() - .FirstOrDefault(e => e.Name.LocalName == "RootComponents"); - if (rootComponents == null) - { - rootComponents = new XElement(ns + "RootComponents"); - var missingDependencies = solutionManifest.Elements() - .FirstOrDefault(e => e.Name.LocalName == "MissingDependencies"); - if (missingDependencies != null) - { - missingDependencies.AddBeforeSelf(rootComponents); - } - else - { - solutionManifest.Add(rootComponents); - } - } - - var existing = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var element in rootComponents.Elements().Where(e => e.Name.LocalName == "RootComponent")) - { - var typeValue = element.Attribute("type")?.Value; - var schemaName = element.Attribute("schemaName")?.Value; - if (string.Equals(typeValue, RootComponentType, StringComparison.OrdinalIgnoreCase) - && !string.IsNullOrWhiteSpace(schemaName)) - { - existing.Add(schemaName); - } - } - - var changed = false; - foreach (var name in webResourceNames) - { - if (existing.Contains(name)) - { - continue; - } - - rootComponents.Add(new XElement(ns + "RootComponent", - new XAttribute("type", RootComponentType), - new XAttribute("schemaName", name), - new XAttribute("behavior", Behavior))); - changed = true; - } - - if (!changed) - { - Log.LogMessage(MessageImportance.Low, "Solution.xml already contains all web resource root components."); - return true; - } - - var settings = new XmlWriterSettings - { - Encoding = new UTF8Encoding(false), - Indent = true, - NewLineChars = Environment.NewLine, - NewLineHandling = NewLineHandling.Replace - }; - - using (var writer = XmlWriter.Create(solutionPath, settings)) - { - document.Save(writer); - } - - Log.LogMessage(MessageImportance.High, $"Updated Solution.xml root components: {solutionPath}"); - return true; - } - catch (Exception ex) - { - Log.LogErrorFromException(ex, true); - return false; - } - } -} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index 1a69ebb..5af4d1d 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -24,7 +24,7 @@ - + From 0059f8f66ad92cf3206c2a369d7c0c78489e543c Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 9 Jan 2026 16:22:12 +0100 Subject: [PATCH 16/52] Cmt package build --- ...S.DevKit.Build.Dataverse.PdPackage.targets | 4 + ...it.Build.Dataverse.CmtPackage.Main.targets | 147 ++++++++++ ....DevKit.Build.Dataverse.CmtPackage.targets | 35 ++- ...IS.DevKit.Build.Dataverse.Solution.targets | 12 + .../Tasks/AppendCmtDataFileToImportConfig.cs | 106 ++++++++ .../Tasks/Tasks/MergeCmtDataSchemaXml.cs | 256 ++++++++++++++++++ src/Dataverse/Tasks/Tasks/MergeCmtDataXml.cs | 246 +++++++++++++++++ ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 3 + 8 files changed, 807 insertions(+), 2 deletions(-) create mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets create mode 100644 src/Dataverse/Tasks/Tasks/AppendCmtDataFileToImportConfig.cs create mode 100644 src/Dataverse/Tasks/Tasks/MergeCmtDataSchemaXml.cs create mode 100644 src/Dataverse/Tasks/Tasks/MergeCmtDataXml.cs diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index a8098f1..09bf47b 100644 --- a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -10,8 +10,12 @@ + + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets new file mode 100644 index 0000000..2fd8273 --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets @@ -0,0 +1,147 @@ + + + 0.0.0.1 + + $([System.IO.Path]::Combine('$(NuGetPackageRoot)','talxis.devkit.build.dataverse.tasks','$(TalxisDevKitTasksVersion)','tasks','TALXIS.DevKit.Build.Dataverse.Tasks.targets')) + + + $(MSBuildThisFileDirectory)..\..\Tasks\msbuild\tasks\TALXIS.DevKit.Build.Dataverse.Tasks.targets + + + + + + + <_CmtMetadataImportConfigDir Condition="'$(_CmtMetadataImportConfigDir)'=='' and '$(_GeneratedPdImportConfig)'!=''">$([System.IO.Path]::GetDirectoryName('$(_GeneratedPdImportConfig)')) + <_CmtMetadataImportConfigDir Condition="'$(_CmtMetadataImportConfigDir)'==''">$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)')) + + + $([System.IO.Path]::Combine('$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)'))','CmtMetadata','$(CmtMetadataZipName)')) + + $([System.String]::Copy('$([System.Text.RegularExpressions.Regex]::Replace('$(CmtMetadataOutputDir)','[\\/]+$', ''))').Trim()) + + <_CmtMetadataDataXml>$([System.IO.Path]::Combine('$(CmtMetadataOutputDir)','data.xml')) + <_CmtMetadataDataSchemaXml>$([System.IO.Path]::Combine('$(CmtMetadataOutputDir)','data_schema.xml')) + <_CmtMetadataContentTypes>$([System.IO.Path]::Combine('$(CmtMetadataOutputDir)','[Content_Types].xml')) + $(CmtPackageName) + MainCmtPackage + $([System.IO.Path]::Combine('$(CmtPackageOutputDir)','$(CmtMetadataZipName).zip')) + $([System.IO.Path]::GetFileName('$(CmtMetadataZipFile)')) + + + + + <_CmtMetadataContentTypesContent>]]> + + + + + + <_CmtPdImportConfig>@(PdImportConfig->'%(FullPath)') + $(_GeneratedPdImportConfig) + $([System.IO.Path]::GetFullPath('$(_CmtPdImportConfig)')) + $([System.IO.Path]::Combine('$(MSBuildProjectDirectory)','PkgAssets','ImportConfig.xml')) + <_CmtWorkingImportConfig Condition="'$(_CmtWorkingImportConfig)'==''">$([System.IO.Path]::Combine('$(_CmtMetadataImportConfigDir)','ImportConfig.cmt.g.xml')) + <_HasCmtPackages>@(_CmtPackageDirs) + + + + + + + + + + + + + + + $(_CmtWorkingImportConfig) + + + + + + <_CmtPackageMetadata Include="@(_CmtPackageDirs)"> + $([System.IO.Path]::Combine('%(_CmtPackageDirs.Identity)','data.xml')) + $([System.IO.Path]::Combine('%(_CmtPackageDirs.Identity)','data_schema.xml')) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_TalxisCmtMetadataPrepared>true + + + + + + + + + $(PdAssetsTargetFolder)\$(CmtMetadataZipFileName) + PreserveNewest + + + + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets index 73766c7..d5b2c02 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets @@ -16,6 +16,20 @@ + + + + <_IncludedCmtPackage Include="$(IncludedCmtPackages)" Condition="'$(IncludedCmtPackages)'!=''"> + $([System.String]::Copy('%(Identity)').Trim()) + $([System.String]::Copy('%(Normalized)').ToLowerInvariant()) + + <_ExcludedCmtPackage Include="$(ExcludedCmtPackages)" Condition="'$(ExcludedCmtPackages)'!=''"> + $([System.String]::Copy('%(Identity)').Trim()) + $([System.String]::Copy('%(Normalized)').ToLowerInvariant()) + + + <_CmtPackageCandidates Include="@(None->'%(FullPath)')" Condition="'%(Filename)%(Extension)'=='[Content_Types].xml'"> @@ -44,7 +58,24 @@ - <_CmtPackageDirs Include="@(_CmtPackageDirsRaw->'%(PackageDir)')" Distinct="true" /> + <_CmtPackageDirsWithMeta Include="@(_CmtPackageDirsRaw->'%(PackageDir)')" Distinct="true"> + $([System.IO.Path]::GetFileName('%(Identity)')) + $([System.String]::Copy('$([System.IO.Path]::GetFileName('%(Identity)'))').ToLowerInvariant()) + + + + + <_IncludedCmtPackageSet>@(_IncludedCmtPackage->'%(NormalizedLower)') + <_ExcludedCmtPackageSet>@(_ExcludedCmtPackage->'%(NormalizedLower)') + + + + <_CmtPackageDirs Include="@(_CmtPackageDirsWithMeta)" + Condition="('$(_IncludedCmtPackageSet)'=='' or $([System.String]::Copy(';$(_IncludedCmtPackageSet);').Contains(';%(PackageNameLower);'))) + and ('$(_ExcludedCmtPackageSet)'=='' or !$([System.String]::Copy(';$(_ExcludedCmtPackageSet);').Contains(';%(PackageNameLower);')))"> + %(PackageName) + %(PackageNameLower) + - \ No newline at end of file + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index 9639b19..6f5345e 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -1,6 +1,18 @@ + + Solution + + + + + <_ProjectType Include="$(MSBuildProjectFullPath)"> + $(ProjectType) + + + + diff --git a/src/Dataverse/Tasks/Tasks/AppendCmtDataFileToImportConfig.cs b/src/Dataverse/Tasks/Tasks/AppendCmtDataFileToImportConfig.cs new file mode 100644 index 0000000..5c3bb46 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/AppendCmtDataFileToImportConfig.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Xml; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class AppendCmtDataFileToImportConfig : Task +{ + [Required] + public string ImportConfigPath { get; set; } = ""; + + [Required] + public string FileName { get; set; } = ""; + + public string Lcid { get; set; } = ""; + + public string UserMapFileName { get; set; } = ""; + + [Output] + public string UpdatedImportConfig { get; private set; } = ""; + + public override bool Execute() + { + try + { + var importConfig = NormalizePath(ImportConfigPath); + var fileName = (FileName ?? "").Trim(); + var lcid = (Lcid ?? "").Trim(); + var userMap = (UserMapFileName ?? "").Trim(); + + if (string.IsNullOrWhiteSpace(importConfig)) + { + Log.LogError("ImportConfigPath is empty."); + return false; + } + + if (string.IsNullOrWhiteSpace(fileName)) + { + Log.LogError("FileName is empty."); + return false; + } + + if (!File.Exists(importConfig)) + { + Log.LogError($"ImportConfig file not found: {importConfig}"); + return false; + } + + var doc = new XmlDocument + { + PreserveWhitespace = true + }; + doc.Load(importConfig); + + var root = doc.DocumentElement; + if (root == null) + { + Log.LogError("ImportConfig has no root element."); + return false; + } + + var cmtNode = root.SelectSingleNode("cmtdatafiles") as XmlElement; + if (cmtNode == null) + { + cmtNode = doc.CreateElement("cmtdatafiles"); + root.AppendChild(cmtNode); + } + + var existing = cmtNode.SelectSingleNode($"cmtdatafile[@filename='{fileName}']") as XmlElement; + if (existing == null) + { + var item = doc.CreateElement("cmtdatafile"); + item.SetAttribute("filename", fileName); + if (!string.IsNullOrWhiteSpace(lcid)) + item.SetAttribute("lcid", lcid); + item.SetAttribute("usermapfilename", userMap); + cmtNode.AppendChild(item); + Log.LogMessage(MessageImportance.Low, $"Added cmtdatafile '{fileName}' to ImportConfig."); + } + else + { + if (!string.IsNullOrWhiteSpace(lcid)) + existing.SetAttribute("lcid", lcid); + if (!string.IsNullOrWhiteSpace(userMap) || existing.GetAttribute("usermapfilename") == "") + existing.SetAttribute("usermapfilename", userMap); + } + + doc.Save(importConfig); + UpdatedImportConfig = importConfig; + return !Log.HasLoggedErrors; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true, true, null); + return false; + } + } + + private static string NormalizePath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return ""; + + return Path.GetFullPath(path.Trim()); + } +} diff --git a/src/Dataverse/Tasks/Tasks/MergeCmtDataSchemaXml.cs b/src/Dataverse/Tasks/Tasks/MergeCmtDataSchemaXml.cs new file mode 100644 index 0000000..746674f --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/MergeCmtDataSchemaXml.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class MergeCmtDataSchemaXml : Task +{ + [Required] + public ITaskItem[] DataSchemaFiles { get; set; } = Array.Empty(); + + public string CmtPackageName { get; set; } = ""; + + public string ProjectDirectory { get; set; } = ""; + + public string OutputDirectory { get; set; } = ""; + + [Output] + public string OutputDataSchemaXml { get; private set; } = ""; + + public override bool Execute() + { + try + { + var files = NormalizeFiles(DataSchemaFiles); + if (files.Count == 0) + { + Log.LogError("No data_schema.xml files were provided."); + return false; + } + + var missing = files.Where(f => !File.Exists(f)).ToList(); + if (missing.Any()) + { + foreach (var path in missing) + { + Log.LogError($"data_schema.xml not found: {path}"); + } + return false; + } + + var packageName = GetPackageName(); + var baseDir = ResolveOutputDirectory(packageName); + Directory.CreateDirectory(baseDir); + + OutputDataSchemaXml = Path.Combine(baseDir, "data_schema.xml"); + + MergeFiles(files, OutputDataSchemaXml); + + Log.LogMessage(MessageImportance.High, + $"Merged {files.Count} data_schema.xml file(s) into {OutputDataSchemaXml}"); + + return !Log.HasLoggedErrors; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true, true, null); + return false; + } + } + + private List NormalizeFiles(ITaskItem[] items) + { + return (items ?? Array.Empty()) + .Select(i => i?.ItemSpec) + .Where(p => !string.IsNullOrWhiteSpace(p)) + .Select(Path.GetFullPath) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private string GetPackageName() + { + var name = string.IsNullOrWhiteSpace(CmtPackageName) + ? "MainCmtPackage" + : CmtPackageName.Trim(); + + var invalid = Path.GetInvalidFileNameChars(); + var sanitized = new string(name.Select(ch => invalid.Contains(ch) ? '_' : ch).ToArray()).Trim(); + + return string.IsNullOrWhiteSpace(sanitized) ? "MainCmtPackage" : sanitized; + } + + private string ResolveOutputDirectory(string packageName) + { + if (!string.IsNullOrWhiteSpace(OutputDirectory)) + return Path.GetFullPath(OutputDirectory); + + var root = string.IsNullOrWhiteSpace(ProjectDirectory) + ? Directory.GetCurrentDirectory() + : ProjectDirectory; + + return Path.GetFullPath(Path.Combine(root, "obj", "metadata", packageName)); + } + + private void MergeFiles(IReadOnlyCollection files, string outputPath) + { + XDocument outputDoc = null; + XElement outputRoot = null; + var entities = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var file in files) + { + var doc = XDocument.Load(file, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); + var root = doc.Root ?? throw new InvalidDataException($"Root element is missing in {file}"); + + if (outputDoc == null) + { + outputDoc = CreateOutputDocument(root); + outputRoot = outputDoc.Root ?? throw new InvalidDataException("Failed to initialize merged document root."); + } + else + { + AddMissingAttributes(outputRoot, root); + } + + foreach (var entity in root.Elements("entity")) + { + var entityName = entity.Attribute("name")?.Value?.Trim(); + if (string.IsNullOrWhiteSpace(entityName)) + { + Log.LogWarning($"Entity without a name skipped in {file}."); + continue; + } + + if (!entities.TryGetValue(entityName, out var targetEntity)) + { + var cloned = new XElement(entity); + entities[entityName] = cloned; + outputRoot.Add(cloned); + } + else + { + MergeEntity(targetEntity, entity); + } + } + } + + if (outputDoc == null || outputRoot == null) + throw new InvalidOperationException("No entities were merged."); + + WriteDocument(outputDoc, outputPath); + } + + private static XDocument CreateOutputDocument(XElement templateRoot) + { + var outputRoot = new XElement(templateRoot.Name); + foreach (var attr in templateRoot.Attributes()) + { + outputRoot.Add(attr); + } + + var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), outputRoot); + return doc; + } + + private void AddMissingAttributes(XElement targetRoot, XElement sourceRoot) + { + foreach (var attr in sourceRoot.Attributes()) + { + if (attr.IsNamespaceDeclaration) + { + var existing = targetRoot.Attributes() + .FirstOrDefault(a => a.IsNamespaceDeclaration && a.Name == attr.Name); + if (existing == null) + targetRoot.Add(attr); + } + else if (targetRoot.Attribute(attr.Name) == null) + { + targetRoot.SetAttributeValue(attr.Name, attr.Value); + } + } + } + + private void MergeEntity(XElement targetEntity, XElement sourceEntity) + { + MergeEntityAttributes(targetEntity, sourceEntity); + MergeChildElements(targetEntity, sourceEntity, "fields", "field", "name"); + MergeChildElements(targetEntity, sourceEntity, "relationships", "relationship", "name"); + } + + private void MergeEntityAttributes(XElement targetEntity, XElement sourceEntity) + { + foreach (var attr in sourceEntity.Attributes()) + { + if (attr.IsNamespaceDeclaration) + { + var existing = targetEntity.Attributes() + .FirstOrDefault(a => a.IsNamespaceDeclaration && a.Name == attr.Name); + if (existing == null) + targetEntity.Add(attr); + } + else if (targetEntity.Attribute(attr.Name) == null) + { + targetEntity.SetAttributeValue(attr.Name, attr.Value); + } + } + } + + private void MergeChildElements( + XElement targetEntity, + XElement sourceEntity, + string containerName, + string itemName, + string keyAttribute) + { + var sourceContainer = sourceEntity.Element(containerName); + if (sourceContainer == null) + return; + + var targetContainer = targetEntity.Element(containerName); + if (targetContainer == null) + { + targetContainer = new XElement(containerName); + targetEntity.Add(targetContainer); + } + + var existing = targetContainer.Elements(itemName) + .Select(e => new { Element = e, Key = e.Attribute(keyAttribute)?.Value?.Trim() }) + .Where(e => !string.IsNullOrWhiteSpace(e.Key)) + .ToDictionary(e => e.Key, e => e.Element, StringComparer.OrdinalIgnoreCase); + + foreach (var item in sourceContainer.Elements(itemName)) + { + var key = item.Attribute(keyAttribute)?.Value?.Trim(); + if (!string.IsNullOrWhiteSpace(key) && existing.ContainsKey(key)) + continue; + + var cloned = new XElement(item); + targetContainer.Add(cloned); + + if (!string.IsNullOrWhiteSpace(key)) + existing[key] = cloned; + } + } + + private static void WriteDocument(XDocument doc, string outputPath) + { + var settings = new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), + Indent = true, + NewLineChars = Environment.NewLine, + NewLineHandling = NewLineHandling.Replace + }; + + using (var writer = XmlWriter.Create(outputPath, settings)) + { + doc.Save(writer); + } + } +} diff --git a/src/Dataverse/Tasks/Tasks/MergeCmtDataXml.cs b/src/Dataverse/Tasks/Tasks/MergeCmtDataXml.cs new file mode 100644 index 0000000..835cd41 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/MergeCmtDataXml.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class MergeCmtDataXml : Task +{ + [Required] + public ITaskItem[] DataXmlFiles { get; set; } = Array.Empty(); + + public string CmtPackageName { get; set; } = ""; + + public string ProjectDirectory { get; set; } = ""; + + public string OutputDirectory { get; set; } = ""; + + [Output] + public string OutputDataXml { get; private set; } = ""; + + public override bool Execute() + { + try + { + var files = NormalizeFiles(DataXmlFiles); + if (files.Count == 0) + { + Log.LogError("No data.xml files were provided."); + return false; + } + + var missing = files.Where(f => !File.Exists(f)).ToList(); + if (missing.Any()) + { + foreach (var path in missing) + { + Log.LogError($"data.xml not found: {path}"); + } + return false; + } + + var packageName = GetPackageName(); + var baseDir = ResolveOutputDirectory(packageName); + Directory.CreateDirectory(baseDir); + + OutputDataXml = Path.Combine(baseDir, "data.xml"); + + MergeFiles(files, OutputDataXml); + + Log.LogMessage(MessageImportance.High, + $"Merged {files.Count} data.xml file(s) into {OutputDataXml}"); + + return !Log.HasLoggedErrors; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true, true, null); + return false; + } + } + + private List NormalizeFiles(ITaskItem[] items) + { + return (items ?? Array.Empty()) + .Select(i => i?.ItemSpec) + .Where(p => !string.IsNullOrWhiteSpace(p)) + .Select(Path.GetFullPath) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private string GetPackageName() + { + var name = string.IsNullOrWhiteSpace(CmtPackageName) + ? "MainCmtPackage" + : CmtPackageName.Trim(); + + var invalid = Path.GetInvalidFileNameChars(); + var sanitized = new string(name.Select(ch => invalid.Contains(ch) ? '_' : ch).ToArray()).Trim(); + + return string.IsNullOrWhiteSpace(sanitized) ? "MainCmtPackage" : sanitized; + } + + private string ResolveOutputDirectory(string packageName) + { + if (!string.IsNullOrWhiteSpace(OutputDirectory)) + return Path.GetFullPath(OutputDirectory); + + var root = string.IsNullOrWhiteSpace(ProjectDirectory) + ? Directory.GetCurrentDirectory() + : ProjectDirectory; + + return Path.GetFullPath(Path.Combine(root, "obj", "metadata", packageName)); + } + + private void MergeFiles(IReadOnlyCollection files, string outputPath) + { + XDocument outputDoc = null; + XElement outputRoot = null; + var entities = new Dictionary(StringComparer.OrdinalIgnoreCase); + var recordKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var file in files) + { + var doc = XDocument.Load(file, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); + var root = doc.Root ?? throw new InvalidDataException($"Root element is missing in {file}"); + + if (outputDoc == null) + { + outputDoc = CreateOutputDocument(root); + outputRoot = outputDoc.Root ?? throw new InvalidDataException("Failed to initialize merged document root."); + } + + foreach (var entity in root.Elements()) + { + if (entity.NodeType != XmlNodeType.Element) + continue; + + var entityName = entity.Attribute("name")?.Value?.Trim(); + var effectiveEntityName = string.IsNullOrWhiteSpace(entityName) + ? Guid.NewGuid().ToString("N") + : entityName; + + if (!entities.TryGetValue(effectiveEntityName, out var targetEntity)) + { + var cloned = new XElement(entity); + entities[effectiveEntityName] = cloned; + outputRoot.Add(cloned); + RegisterRecordKeys(cloned, effectiveEntityName, recordKeys); + } + else + { + MergeEntityRecords(targetEntity, entity, effectiveEntityName, recordKeys); + } + } + } + + if (outputDoc == null || outputRoot == null) + throw new InvalidOperationException("No entities were merged."); + + outputRoot.SetAttributeValue("timestamp", DateTime.UtcNow.ToString("o")); + + WriteDocument(outputDoc, outputPath); + } + + private static XDocument CreateOutputDocument(XElement templateRoot) + { + var outputRoot = new XElement(templateRoot.Name); + foreach (var attr in templateRoot.Attributes()) + { + if (attr.IsNamespaceDeclaration) + { + outputRoot.Add(attr); + } + else + { + outputRoot.SetAttributeValue(attr.Name, attr.Value); + } + } + + var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), outputRoot); + return doc; + } + + private void MergeEntityRecords( + XElement targetEntity, + XElement sourceEntity, + string entityName, + HashSet recordKeys) + { + var targetRecords = EnsureRecordsContainer(targetEntity); + var sourceRecords = sourceEntity.Element("records"); + if (sourceRecords == null) + return; + + foreach (var record in sourceRecords.Elements("record")) + { + var recordId = record.Attribute("id")?.Value?.Trim(); + var recordKey = string.IsNullOrWhiteSpace(recordId) ? null : BuildRecordKey(entityName, recordId); + + if (recordKey != null && recordKeys.Contains(recordKey)) + continue; + + var cloned = new XElement(record); + targetRecords.Add(cloned); + + if (recordKey != null) + recordKeys.Add(recordKey); + } + } + + private void RegisterRecordKeys( + XElement entity, + string entityName, + HashSet recordKeys) + { + var records = entity.Element("records"); + if (records == null) + return; + + foreach (var record in records.Elements("record")) + { + var recordId = record.Attribute("id")?.Value?.Trim(); + if (string.IsNullOrWhiteSpace(recordId)) + continue; + + recordKeys.Add(BuildRecordKey(entityName, recordId)); + } + } + + private static XElement EnsureRecordsContainer(XElement entity) + { + var records = entity.Element("records"); + if (records == null) + { + records = new XElement("records"); + entity.Add(records); + } + return records; + } + + private static string BuildRecordKey(string entityName, string recordId) + { + return entityName + "|" + recordId; + } + + private static void WriteDocument(XDocument doc, string outputPath) + { + var settings = new XmlWriterSettings + { + Encoding = new UTF8Encoding(false), + Indent = true, + NewLineChars = Environment.NewLine, + NewLineHandling = NewLineHandling.Replace + }; + + using (var writer = XmlWriter.Create(outputPath, settings)) + { + doc.Save(writer); + } + } +} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index 5af4d1d..1de8a98 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -28,4 +28,7 @@ + + + From c5042735d5c7ff107cbef7e60898270a58118569 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Wed, 14 Jan 2026 09:32:39 +0100 Subject: [PATCH 17/52] Cmt package include exclude --- ....DevKit.Build.Dataverse.CmtPackage.targets | 36 +++--- .../Tasks/EnsurePluginAssemblyDataXml.cs | 110 +++++++++++++++++- 2 files changed, 122 insertions(+), 24 deletions(-) diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets index d5b2c02..34fa33d 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets @@ -19,16 +19,10 @@ - - <_IncludedCmtPackage Include="$(IncludedCmtPackages)" Condition="'$(IncludedCmtPackages)'!=''"> - $([System.String]::Copy('%(Identity)').Trim()) - $([System.String]::Copy('%(Normalized)').ToLowerInvariant()) - - <_ExcludedCmtPackage Include="$(ExcludedCmtPackages)" Condition="'$(ExcludedCmtPackages)'!=''"> - $([System.String]::Copy('%(Identity)').Trim()) - $([System.String]::Copy('%(Normalized)').ToLowerInvariant()) - - + + <_IncludedCmtPackagesNormalized Condition="'$(IncludedCmtPackages)'!=''">$(IncludedCmtPackages.ToLowerInvariant().Replace(' ', '')) + <_ExcludedCmtPackagesNormalized Condition="'$(ExcludedCmtPackages)'!=''">$(ExcludedCmtPackages.ToLowerInvariant().Replace(' ', '')) + <_CmtPackageCandidates Include="@(None->'%(FullPath)')" @@ -59,25 +53,27 @@ <_CmtPackageDirsWithMeta Include="@(_CmtPackageDirsRaw->'%(PackageDir)')" Distinct="true"> - $([System.IO.Path]::GetFileName('%(Identity)')) - $([System.String]::Copy('$([System.IO.Path]::GetFileName('%(Identity)'))').ToLowerInvariant()) + %(_CmtPackageDirsRaw.PackageDir) + $([System.IO.Path]::GetFileName('%(_CmtPackageDirsRaw.PackageDir)')) + $([System.String]::Copy('$([System.IO.Path]::GetFileName('%(_CmtPackageDirsRaw.PackageDir)'))').ToLowerInvariant()) - - <_IncludedCmtPackageSet>@(_IncludedCmtPackage->'%(NormalizedLower)') - <_ExcludedCmtPackageSet>@(_ExcludedCmtPackage->'%(NormalizedLower)') - - - <_CmtPackageDirs Include="@(_CmtPackageDirsWithMeta)" - Condition="('$(_IncludedCmtPackageSet)'=='' or $([System.String]::Copy(';$(_IncludedCmtPackageSet);').Contains(';%(PackageNameLower);'))) - and ('$(_ExcludedCmtPackageSet)'=='' or !$([System.String]::Copy(';$(_ExcludedCmtPackageSet);').Contains(';%(PackageNameLower);')))"> + <_CmtPackageDirs Include="@(_CmtPackageDirsWithMeta)"> %(PackageName) %(PackageNameLower) + + <_CmtPackageDirs Remove="@(_CmtPackageDirs)" Condition="!$([System.String]::Copy(';$(_IncludedCmtPackagesNormalized);').Contains(';%(PackageNameLower);'))" /> + + + + <_CmtPackageDirs Remove="@(_CmtPackageDirs)" Condition="$([System.String]::Copy(';$(_ExcludedCmtPackagesNormalized);').Contains(';%(PackageNameLower);'))" /> + + classList = GetPluginClassNames(pluginAssembly); @@ -250,10 +254,10 @@ private string PrepareWorkingPluginDll(string assemblyName, string normalizedGui Directory.CreateDirectory(metadataDir); - string workingDllPath = Path.Combine(metadataDir, assemblyName + ".dll"); - File.Copy(sourceDllPath, workingDllPath, true); + string preferredDllPath = Path.Combine(metadataDir, assemblyName + ".dll"); - return workingDllPath; + // If the destination is in use (e.g., another parallel task loaded it), fall back to a unique path under the same metadata directory. + return CopyPluginDllWithFallback(sourceDllPath, preferredDllPath); } private HashSet BuildProbeDirectories(string dllPath, string projectDirectory) @@ -517,6 +521,104 @@ private static void TryLoadAssemblyNoThrow(string path) catch { /* ignore */ } } + private Assembly LoadPluginAssembly(string dllPath, string assemblyName, HashSet probeDirs) + { + var alreadyLoaded = FindLoadedAssembly(assemblyName); + if (alreadyLoaded != null) + return alreadyLoaded; + +#if NET6_0_OR_GREATER + try + { + var alc = new AssemblyLoadContext("PluginAssembly-" + Guid.NewGuid().ToString("N"), isCollectible: true); + alc.Resolving += (context, name) => + { + foreach (var dir in probeDirs) + { + var candidate = Path.Combine(dir, name.Name + ".dll"); + if (File.Exists(candidate)) + return context.LoadFromAssemblyPath(candidate); + } + return null; + }; + + var bytes = File.ReadAllBytes(dllPath); + var asm = alc.LoadFromStream(new MemoryStream(bytes)); + return asm; + } + catch (FileLoadException) + { + var loaded = FindLoadedAssembly(assemblyName); + if (loaded != null) + return loaded; + throw; + } +#else + try + { + return Assembly.LoadFrom(dllPath); + } + catch (FileLoadException) + { + var loaded = FindLoadedAssembly(assemblyName); + if (loaded != null) + return loaded; + + string uniqueDir = Path.Combine(Path.GetDirectoryName(dllPath) ?? Path.GetTempPath(), "run-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(uniqueDir); + string altPath = Path.Combine(uniqueDir, Path.GetFileName(dllPath)); + File.Copy(dllPath, altPath, true); + return Assembly.LoadFrom(altPath); + } +#endif + } + + private static Assembly FindLoadedAssembly(string assemblyName) + { + return AppDomain.CurrentDomain + .GetAssemblies() + .FirstOrDefault(a => + { + var name = a.GetName(); + return name != null && string.Equals(name.Name, assemblyName, StringComparison.OrdinalIgnoreCase); + }); + } + + private string CopyPluginDllWithFallback(string sourceDllPath, string preferredPath) + { + try + { + CopyFileWithRetry(sourceDllPath, preferredPath, overwrite: true); + return preferredPath; + } + catch (IOException) + { + string parentDir = Path.GetDirectoryName(preferredPath) ?? Path.GetTempPath(); + string altDir = Path.Combine(parentDir, "run-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(altDir); + + string altPath = Path.Combine(altDir, Path.GetFileName(preferredPath)); + CopyFileWithRetry(sourceDllPath, altPath, overwrite: true); + return altPath; + } + } + + private static void CopyFileWithRetry(string source, string destination, bool overwrite, int maxAttempts = 3, int delayMs = 200) + { + for (int attempt = 1; ; attempt++) + { + try + { + File.Copy(source, destination, overwrite); + return; + } + catch (IOException) when (attempt < maxAttempts) + { + Thread.Sleep(delayMs); + } + } + } + private static string NormalizeGuid(string guidText) { if (string.IsNullOrWhiteSpace(guidText)) From 9d618d3f518c96e4482e129710f7b9f090eb4841 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Wed, 14 Jan 2026 15:50:37 +0100 Subject: [PATCH 18/52] Readme added --- src/Dataverse/PDPackage/README.md | 33 +++++++++++++++++++++++++-- src/Dataverse/Pcf/README.md | 7 +++++- src/Dataverse/Plugin/README.md | 11 ++++++++- src/Dataverse/ScriptLibrary/README.md | 12 ++++++++-- src/Dataverse/Sdk/README.md | 7 ++++++ src/Dataverse/Solution/README.md | 19 ++++++++++++++- src/Dataverse/Tasks/README.md | 18 +++++++++++++-- 7 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/Dataverse/PDPackage/README.md b/src/Dataverse/PDPackage/README.md index f1608d7..d796afc 100644 --- a/src/Dataverse/PDPackage/README.md +++ b/src/Dataverse/PDPackage/README.md @@ -1,3 +1,32 @@ -# TALXIS.DevKit.Build.Dataverse.Tasks +# TALXIS.DevKit.Build.Dataverse.PdPackage -See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file +MSBuild targets for Dataverse PDPackage projects. + +## MSBuild properties +### PDPackage +- `PdPackageMsBuildVersion` (default `1.50.1`): version of `Microsoft.PowerApps.MSBuild.PDPackage` imported by the package. +- `GeneratePdPackageOnBuild` (default `true`): runs `GeneratePdPackage` after build/publish. + +### ILRepack +- `DataversePackageRunILRepack` (default `true`): runs ILRepack after build. +- `SkipPackageILRepack`: set to `true` to skip ILRepack. +- `ILRepackVersion` (default `2.0.18`): ILRepack package version. +- `ILRepackExe` (default `$(NuGetPackageRoot)ilrepack\\tools\ILRepack.exe`): path to ILRepack.exe. +- `ReferencedAssembliesDir` (default `$(TargetDir)`): directory scanned for assemblies to merge. +- `DataversePackageILRepackKeyFile`: strong-name key file passed to ILRepack `/keyfile`. + +### Cmt packages +- `CmtPackageSearchRoot` (default project directory): root folder scanned for Cmt packages. +- `CmtPackageOutputDir` (default `\CmtPackages` or `\CmtPackages`): output folder for zipped Cmt packages. +- `IncludedCmtPackages`: semicolon-separated package names to include (case-insensitive). +- `ExcludedCmtPackages`: semicolon-separated package names to exclude (case-insensitive). + +### Cmt metadata merge +- `CmtPackageName`: name injected into merged metadata. +- `CmtMetadataOutputDir` (default `$(IntermediateOutputPath)\CmtMetadata\`): temp folder for merged metadata. +- `CmtMetadataZipName` (default `$(CmtPackageName)` or `MainCmtPackage`): name of the merged metadata zip. +- `CmtMetadataLcid`: LCID used when appending metadata to ImportConfig. +- `CmtMetadataUserMapFileName`: optional user map file name used in ImportConfig. +- `CmtImportConfigPath`: path to ImportConfig.xml used for metadata injection. +- `AutoGeneratePdImportConfig`: when `true`, uses the generated ImportConfig instead of copying a project file. +- `PdAssetsTargetFolder`: target folder under publish assets for the merged metadata zip. diff --git a/src/Dataverse/Pcf/README.md b/src/Dataverse/Pcf/README.md index e8af5e6..f3ae650 100644 --- a/src/Dataverse/Pcf/README.md +++ b/src/Dataverse/Pcf/README.md @@ -1,3 +1,8 @@ # TALXIS.DevKit.Build.Dataverse.Pcf -See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file +MSBuild targets for Dataverse PCF projects. + +## MSBuild properties +- `Version` (required): base version; used for Git versioning and applied to assembly/package versions. +- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). +- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. diff --git a/src/Dataverse/Plugin/README.md b/src/Dataverse/Plugin/README.md index bae364d..b791c2e 100644 --- a/src/Dataverse/Plugin/README.md +++ b/src/Dataverse/Plugin/README.md @@ -1,3 +1,12 @@ # TALXIS.DevKit.Build.Dataverse.Plugin -See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file +MSBuild targets for Dataverse plugin projects. + +## MSBuild properties +- `ProjectType` (default `Plugin`): marks the project as a plugin for reference discovery. +- `Version` (required): base version; major/minor are used for Git versioning and applied to assembly/package versions. +- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). +- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. +- `PluginTargetFramework` (default `$(TargetFramework)` when set, otherwise `net462`): target framework used to locate the compiled plugin DLL. +- `PluginPublishFolderName` (default `publish`): publish folder name under `bin\\\`. +- `PluginAssemblyId`: explicit GUID for the plugin assembly metadata; if empty, a new GUID is generated. diff --git a/src/Dataverse/ScriptLibrary/README.md b/src/Dataverse/ScriptLibrary/README.md index f1608d7..e346052 100644 --- a/src/Dataverse/ScriptLibrary/README.md +++ b/src/Dataverse/ScriptLibrary/README.md @@ -1,3 +1,11 @@ -# TALXIS.DevKit.Build.Dataverse.Tasks +# TALXIS.DevKit.Build.Dataverse.ScriptLibrary -See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file +MSBuild targets for Dataverse ScriptLibrary projects. + +## MSBuild properties +- `ProjectType` (default `ScriptLibrary`): marks the project for reference discovery. +- `RunNodeBuild` (default `false`): runs `npm install` and `npm run build` in `TypeScriptDir`. +- `TypeScriptDir` (default `$(MSBuildProjectDirectory)\TS`): folder containing the TypeScript project. +- `ScriptLibraryMainFile` (default `$(MSBuildProjectDirectory)\TS\build\main.js`): main script file used by consuming targets. +- `LangVersion` (default `latest`): C# language version for the project. +- `GenerateAssemblyInfo` (default `false`): disables auto-generated assembly info. diff --git a/src/Dataverse/Sdk/README.md b/src/Dataverse/Sdk/README.md index 5a1ce0a..23f52cf 100644 --- a/src/Dataverse/Sdk/README.md +++ b/src/Dataverse/Sdk/README.md @@ -1,2 +1,9 @@ # TALXIS.DevKit.Build.Sdk +SDK that wires package references based on `ProjectType`. + +## MSBuild properties +- `ProjectType`: selects the package to reference (for example `Solution`, `Plugin`, `Pcf`, `ScriptLibrary`, `PdPackage`, `Tasks`). +- `TALXISDevKitDataversePackageBase` (default `TALXIS.DevKit.Build.Dataverse`): base package name used with `ProjectType`. +- `TALXISDevKitDataversePackageVersion` (default `0.0.0.1`): version used in the package reference. +- `TALXISDevKitDataversePackageName`: explicit package name; overrides the base+ProjectType combination. diff --git a/src/Dataverse/Solution/README.md b/src/Dataverse/Solution/README.md index 87a3314..0c97c43 100644 --- a/src/Dataverse/Solution/README.md +++ b/src/Dataverse/Solution/README.md @@ -1,3 +1,20 @@ # TALXIS.DevKit.Build.Dataverse.Solution -See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file +MSBuild targets for Dataverse solution projects. + +## MSBuild properties +- `ProjectType` (default `Solution`): marks the project as a solution for reference discovery. +- `Version` (required): base version; used for Git versioning and applied to solution.xml and related metadata. +- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). +- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. +- `Managed`: value written to the `` element in solution.xml. +- `PublisherName`: value written to the publisher name fields in solution.xml. +- `PublisherPrefix`: value written to solution.xml and used as the web resource name prefix. +- `SolutionRootPath` (default `.`): relative path to the solution source root. +- `SolutionPackagerWorkingDirectory` (default `$(IntermediateOutputPath)`): working folder for solution packager operations. +- `SolutionPackagerMetadataWorkingDirectory` (default `$(SolutionPackagerWorkingDirectory)Metadata`): metadata folder used for version updates. +- `SolutionPackagerLocalizationWorkingDirectory`: optional localization working folder (cleaned by CleanupWorkingDirectory). +- `SolutionPackageLogFilePath` (default `$(IntermediateOutputPath)SolutionPackager.log`): SolutionPackager log path. +- `SolutionPackageZipFilePath` (default `$(OutputPath)$(MSBuildProjectName).zip`): output zip path for pack tasks. +- `WebResourcesDir`: destination folder for script library web resources (default `$(MSBuildProjectDirectory)\$(SolutionRootPath)\WebResources\`). +- `PcfForceUpdate`: forwarded to PAC `ProcessCdsProjectReferencesOutputs` to force PCF updates. diff --git a/src/Dataverse/Tasks/README.md b/src/Dataverse/Tasks/README.md index d88af65..32b475f 100644 --- a/src/Dataverse/Tasks/README.md +++ b/src/Dataverse/Tasks/README.md @@ -1,3 +1,17 @@ -# TALXIS.DevKit.Build.Dataverse.Tasks +# TALXIS.DevKit.Build.Dataverse.Tasks -See [here](https://github.com/TALXIS/tools-devkit-build) for more information. \ No newline at end of file +MSBuild tasks and targets shared by Dataverse packages. + +## MSBuild properties +### Versioning + +### Solution packager paths +- `SolutionRootPath` (default `.`): relative path to the solution source root. +- `SolutionPackagerWorkingDirectory` (default `$(IntermediateOutputPath)`): working folder for pack/unpack. +- `SolutionPackagerMetadataWorkingDirectory` (default `$(SolutionPackagerWorkingDirectory)Metadata`): metadata folder used by version update targets. +- `SolutionPackagerLocalizationWorkingDirectory`: optional localization working folder (cleaned by CleanupWorkingDirectory). +- `SolutionPackageLogFilePath` (default `$(IntermediateOutputPath)SolutionPackager.log`): SolutionPackager log path. +- `SolutionPackageZipFilePath` (default `$(OutputPath)$(MSBuildProjectName).zip`): output zip path used by PackDataverseSolution. + +### PCF versioning +- `PcfOutputPath`: output directory containing `ControlManifest.xml` (used by ApplyPcfVersionNumber). From 08ac84369db9b1fd3a53f7f1d317c24183569654 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 16 Jan 2026 23:02:35 +0100 Subject: [PATCH 19/52] bugs fixed --- .claude/settings.local.json | 8 + ...S.DevKit.Build.Dataverse.PdPackage.targets | 1 + ...ALXIS.DevKit.Build.Dataverse.Plugin.nuspec | 4 +- ...TALXIS.DevKit.Build.Dataverse.Plugin.props | 10 ++ ...LXIS.DevKit.Build.Dataverse.Plugin.targets | 2 + ...vKit.Build.Dataverse.ScriptLibrary.targets | 9 +- .../Tasks/Tasks/ApplyVersionNumber.cs | 46 ++++- .../Tasks/Tasks/GenerateGitVersion.cs | 159 +++++++++--------- tmpclaude-0451-cwd | 1 + tmpclaude-06c9-cwd | 1 + tmpclaude-0d56-cwd | 1 + tmpclaude-1607-cwd | 1 + tmpclaude-178d-cwd | 1 + tmpclaude-1954-cwd | 1 + tmpclaude-2a41-cwd | 1 + tmpclaude-2f29-cwd | 1 + tmpclaude-3302-cwd | 1 + tmpclaude-44b0-cwd | 1 + tmpclaude-44e3-cwd | 1 + tmpclaude-4526-cwd | 1 + tmpclaude-45bf-cwd | 1 + tmpclaude-4717-cwd | 1 + tmpclaude-588a-cwd | 1 + tmpclaude-5c8b-cwd | 1 + tmpclaude-5f2e-cwd | 1 + tmpclaude-5f5a-cwd | 1 + tmpclaude-675a-cwd | 1 + tmpclaude-688a-cwd | 1 + tmpclaude-6ad4-cwd | 1 + tmpclaude-6cd1-cwd | 1 + tmpclaude-70cf-cwd | 1 + tmpclaude-735c-cwd | 1 + tmpclaude-74cf-cwd | 1 + tmpclaude-7870-cwd | 1 + tmpclaude-798f-cwd | 1 + tmpclaude-838d-cwd | 1 + tmpclaude-9224-cwd | 1 + tmpclaude-9450-cwd | 1 + tmpclaude-967a-cwd | 1 + tmpclaude-a996-cwd | 1 + tmpclaude-b2d2-cwd | 1 + tmpclaude-b575-cwd | 1 + tmpclaude-b653-cwd | 1 + tmpclaude-c00e-cwd | 1 + tmpclaude-c421-cwd | 1 + tmpclaude-d9e4-cwd | 1 + tmpclaude-dc4d-cwd | 1 + tmpclaude-dcd3-cwd | 1 + tmpclaude-dd8c-cwd | 1 + tmpclaude-ddf7-cwd | 1 + tmpclaude-e031-cwd | 1 + tmpclaude-e41b-cwd | 1 + tmpclaude-e5fb-cwd | 1 + 53 files changed, 200 insertions(+), 84 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 tmpclaude-0451-cwd create mode 100644 tmpclaude-06c9-cwd create mode 100644 tmpclaude-0d56-cwd create mode 100644 tmpclaude-1607-cwd create mode 100644 tmpclaude-178d-cwd create mode 100644 tmpclaude-1954-cwd create mode 100644 tmpclaude-2a41-cwd create mode 100644 tmpclaude-2f29-cwd create mode 100644 tmpclaude-3302-cwd create mode 100644 tmpclaude-44b0-cwd create mode 100644 tmpclaude-44e3-cwd create mode 100644 tmpclaude-4526-cwd create mode 100644 tmpclaude-45bf-cwd create mode 100644 tmpclaude-4717-cwd create mode 100644 tmpclaude-588a-cwd create mode 100644 tmpclaude-5c8b-cwd create mode 100644 tmpclaude-5f2e-cwd create mode 100644 tmpclaude-5f5a-cwd create mode 100644 tmpclaude-675a-cwd create mode 100644 tmpclaude-688a-cwd create mode 100644 tmpclaude-6ad4-cwd create mode 100644 tmpclaude-6cd1-cwd create mode 100644 tmpclaude-70cf-cwd create mode 100644 tmpclaude-735c-cwd create mode 100644 tmpclaude-74cf-cwd create mode 100644 tmpclaude-7870-cwd create mode 100644 tmpclaude-798f-cwd create mode 100644 tmpclaude-838d-cwd create mode 100644 tmpclaude-9224-cwd create mode 100644 tmpclaude-9450-cwd create mode 100644 tmpclaude-967a-cwd create mode 100644 tmpclaude-a996-cwd create mode 100644 tmpclaude-b2d2-cwd create mode 100644 tmpclaude-b575-cwd create mode 100644 tmpclaude-b653-cwd create mode 100644 tmpclaude-c00e-cwd create mode 100644 tmpclaude-c421-cwd create mode 100644 tmpclaude-d9e4-cwd create mode 100644 tmpclaude-dc4d-cwd create mode 100644 tmpclaude-dcd3-cwd create mode 100644 tmpclaude-dd8c-cwd create mode 100644 tmpclaude-ddf7-cwd create mode 100644 tmpclaude-e031-cwd create mode 100644 tmpclaude-e41b-cwd create mode 100644 tmpclaude-e5fb-cwd diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b0ee8d8 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(xargs -I {} sh -c 'echo \"\"=== {} ===\"\" && cat {}')", + "Bash(xargs -I {} sh -c 'echo \"\"=== $\\(basename {}\\) ===\"\" && head -30 {}')" + ] + } +} diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index fc375b9..9bbb4d8 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -6,6 +6,7 @@ 2025 NETWORG - + + + diff --git a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props index 2029a55..6f64f4c 100644 --- a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props +++ b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.props @@ -3,5 +3,15 @@ Plugin + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps + {4C25E9B5-9FA6-436c-8E19-B395D2A65FAF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + + + diff --git a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets index 2b155b3..74e7ce9 100644 --- a/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets +++ b/src/Dataverse/Plugin/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Plugin.targets @@ -1,6 +1,8 @@ + + diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 8116df0..4b6d737 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -2,9 +2,10 @@ - false - $(MSBuildProjectDirectory)\TS + + true + false - + PreserveNewest @@ -44,7 +45,7 @@ DependsOnTargets="Build" Returns="@(_ScriptLibraryOutputs)"> - <_ScriptLibraryOutputs Include="$(MSBuildProjectDirectory)\TS\build\main.js" /> + <_ScriptLibraryOutputs Include="$(ScriptLibraryMainFile)" /> diff --git a/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs b/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs index 56f03f7..9f05cc7 100644 --- a/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs +++ b/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs @@ -27,7 +27,49 @@ public class ApplyVersionNumber : Task public override bool Execute() { - + UpdateVersionInSolutionXmlFile(SolutionXml.ItemSpec, Version); + if (PluginAssembliesFolder != null && Directory.Exists(PluginAssembliesFolder.ItemSpec)) + { + var pluginAssemblies = Directory.EnumerateFiles(PluginAssembliesFolder.ItemSpec, "*.dll.data.xml", SearchOption.AllDirectories); + foreach (var pluginAssemblyXmlPath in pluginAssemblies) + { + var pluginAssemblyDocument = XDocument.Load(pluginAssemblyXmlPath); + var fullNameAttributeValue = pluginAssemblyDocument.Root.Attribute("FullName")?.Value; + var assemblyName = fullNameAttributeValue?.Split(',')[0].Trim(); + var assembly = Assembly.LoadFrom(pluginAssemblyXmlPath.Replace(".data.xml", "")); + _assemblies.Add(assembly); + + Log.LogMessage(MessageImportance.High, $" > Discovered {assembly.FullName} at {pluginAssemblyXmlPath}"); + + var solutionXml = XDocument.Load(SolutionXml.ItemSpec); + // Find all RootComponents of type PluginAssembly (91) and update schemaName to match assembly name - match based on startsWith assemblyName + var rootComponents = solutionXml.Descendants("RootComponent") + .Where(rc => rc.Attribute("type")?.Value == "91" && rc.Attribute("schemaName")?.Value.StartsWith(assemblyName, StringComparison.OrdinalIgnoreCase) == true); + foreach (var rootComponent in rootComponents) + { + rootComponent.Attribute("schemaName").SetValue(assembly.FullName); + } + File.WriteAllText(SolutionXml.ItemSpec, solutionXml.ToString()); + } + } + if (WorkflowsFolder != null && Directory.Exists(WorkflowsFolder.ItemSpec)) + { + var workflows = Directory.EnumerateFiles(WorkflowsFolder.ItemSpec, "*.xml", SearchOption.AllDirectories); + foreach (var workflowXmlPath in workflows) + { + Log.LogMessage(MessageImportance.High, $"Processing {workflowXmlPath}"); + UpdateVersionInWorkflowFiles(workflowXmlPath); + } + } + if(SdkMessageProcessingStepsFolder != null && Directory.Exists(SdkMessageProcessingStepsFolder.ItemSpec)) + { + var sdkMessageProcessingSteps = Directory.EnumerateFiles(SdkMessageProcessingStepsFolder.ItemSpec, "*.xml", SearchOption.AllDirectories); + foreach (var sdkMessageProcessingStepXmlPath in sdkMessageProcessingSteps) + { + Log.LogMessage(MessageImportance.High, $"Processing {sdkMessageProcessingStepXmlPath}"); + UpdateVersionInSdkMessageProcessingStepFiles(sdkMessageProcessingStepXmlPath); + } + } return true; } @@ -119,4 +161,4 @@ private string ExtractVersionFromFQDN(string fullName) var match = Regex.Match(fullName, @"Version=([\d.]*),"); return match.Success ? match.Groups[1].Value : null; } -} +} \ No newline at end of file diff --git a/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs b/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs index 6052781..eee0929 100644 --- a/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs +++ b/src/Dataverse/Tasks/Tasks/GenerateGitVersion.cs @@ -39,90 +39,94 @@ public override bool Execute() LocalBranchBuildVersionNumber = "0.0.0.1"; } - // Prepare for running git commands - var gitInfo = CreateGitProcessInfo(ProjectPath); - if (!IsGitRepository(gitInfo)) + // Ensure repository is connected to Git before running commands + if (!TryFindGitRoot(ProjectPath, out var gitRoot)) { - Log.LogWarning($"Git repository not found for ProjectPath '{ProjectPath}'. Falling back to LocalBranchBuildVersionNumber."); + Log.LogMessage(MessageImportance.High, "Git repository not found; skipping automatic Git versioning."); VersionOutput = LocalBranchBuildVersionNumber; return true; } - var currentBranch = GetCurrentBranch(gitInfo); - if (string.IsNullOrWhiteSpace(ApplyToBranches)) - { - Log.LogWarning("ApplyToBranches is empty. Falling back to LocalBranchBuildVersionNumber."); - VersionOutput = LocalBranchBuildVersionNumber; - return true; - } + // Prepare for running git commands + var gitInfo = CreateGitProcessInfo(gitRoot); - _branches = ApplyToBranches.Split(';').Select(BranchVersioning.Parse); - if (_branches == null || !_branches.Any()) - { - Log.LogWarning($"No valid branches found in ApplyToBranches '{ApplyToBranches}'."); - VersionOutput = LocalBranchBuildVersionNumber; - return true; - } - var branch = _branches.FirstOrDefault(b => - string.Equals(b.BranchName, currentBranch, StringComparison.OrdinalIgnoreCase) || - // Basic wildcard support, e.g. feature/* - (b.BranchName.EndsWith("*") && currentBranch.StartsWith(b.BranchName.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)) - ); - if (branch == null) - { - Log.LogWarning($"The current branch '{currentBranch}' is not enabled for automatic Git versioning."); - VersionOutput = LocalBranchBuildVersionNumber; - return true; - } - else + try { - Log.LogMessage($"The current branch '{currentBranch}' is enabled for automatic Git versioning."); - - var projects = new List + var currentBranch = GetCurrentBranch(gitInfo); + _branches = ApplyToBranches.Split(';').Select(BranchVersioning.Parse); + if (_branches == null || !_branches.Any()) { - ProjectPath - }; - RetrieveAllProjectReferences(ProjectPath, projects); - Log.LogMessage(MessageImportance.High, $"Got number of projects: {projects.Count}"); + Log.LogWarning($"No valid branches found in ApplyToBranches '{ApplyToBranches}'."); + VersionOutput = LocalBranchBuildVersionNumber; + return true; + } + var branch = _branches.FirstOrDefault(b => + string.Equals(b.BranchName, currentBranch, StringComparison.OrdinalIgnoreCase) || + // Basic wildcard support, e.g. feature/* + (b.BranchName.EndsWith("*") && currentBranch.StartsWith(b.BranchName.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)) + ); + if (branch == null) + { + Log.LogWarning($"The current branch '{currentBranch}' is not enabled for automatic Git versioning."); + VersionOutput = LocalBranchBuildVersionNumber; + return true; + } + else + { + Log.LogMessage($"The current branch '{currentBranch}' is enabled for automatic Git versioning."); - var totalComitCount = 0; - var latestCommitDate = new DateTime(1900, 1, 1); + var projects = new List + { + ProjectPath + }; + RetrieveAllProjectReferences(ProjectPath, projects); + Log.LogMessage(MessageImportance.High, $"Got number of projects: {projects.Count}"); - foreach (var project in projects) - { - Log.LogMessage(MessageImportance.High, $"Project: {project}"); - var (commitCount, lastCommitDate) = GetNumberOfCommits(project); - Log.LogMessage(MessageImportance.High, $"{project}: Last commit: {lastCommitDate:yyyy-MM-dd}, count: {commitCount}"); - if (latestCommitDate < lastCommitDate) + var totalComitCount = 0; + var latestCommitDate = new DateTime(1900, 1, 1); + + foreach (var project in projects) { - totalComitCount = commitCount; - latestCommitDate = lastCommitDate; + Log.LogMessage(MessageImportance.High, $"Project: {project}"); + var (commitCount, lastCommitDate) = GetNumberOfCommits(project); + Log.LogMessage(MessageImportance.High, $"{project}: Last commit: {lastCommitDate:yyyy-MM-dd}, count: {commitCount}"); + if (latestCommitDate < lastCommitDate) + { + totalComitCount = commitCount; + latestCommitDate = lastCommitDate; + } + else if (latestCommitDate == lastCommitDate) + { + totalComitCount += commitCount; + } } - else if (latestCommitDate == lastCommitDate) + Log.LogMessage(MessageImportance.High, $"Commit count for the month: {totalComitCount}"); + if (totalComitCount > 999) { - totalComitCount += commitCount; + throw new Exception($"Too many commits ({totalComitCount} > 999), cannot generate version number. Please reach out to the author."); } - } - Log.LogMessage(MessageImportance.High, $"Commit count for the month: {totalComitCount}"); - if (totalComitCount > 999) - { - throw new Exception($"Too many commits ({totalComitCount} > 999), cannot generate version number. Please reach out to the author."); - } - // Convert the latest commit date to build number - // DateTime lastCommitDateTime = DateTime.ParseExact(latestCommitDate, "yyyy-MM-dd HH:mm:ss K", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - var build = ushort.Parse(latestCommitDate.ToString("yyMM")); - if (branch.Prefix.HasValue) - { - build = ushort.Parse($"{branch.Prefix}{latestCommitDate:yyMM}"); - } + // Convert the latest commit date to build number + // DateTime lastCommitDateTime = DateTime.ParseExact(latestCommitDate, "yyyy-MM-dd HH:mm:ss K", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + var build = ushort.Parse(latestCommitDate.ToString("yyMM")); + if (branch.Prefix.HasValue) + { + build = ushort.Parse($"{branch.Prefix}{latestCommitDate:yyMM}"); + } - // Get the revision number as the last commit day and commit count for the month (reduce risk of deploying lower version after refactoring) - var revision = ushort.Parse($"{latestCommitDate:dd}{totalComitCount:000}"); + // Get the revision number as the last commit day and commit count for the month (reduce risk of deploying lower version after refactoring) + var revision = ushort.Parse($"{latestCommitDate:dd}{totalComitCount:000}"); - // Combine the version parts into final version number - VersionOutput = $"{VersionMajor}.{VersionMinor}.{build}.{revision}"; + // Combine the version parts into final version number + VersionOutput = $"{VersionMajor}.{VersionMinor}.{build}.{revision}"; + return true; + } + } + catch (Exception ex) + { + Log.LogMessage(MessageImportance.High, $"Git versioning skipped: {ex.Message}"); + VersionOutput = LocalBranchBuildVersionNumber; return true; } } @@ -227,19 +231,20 @@ private string ExecuteGitCommand(ProcessStartInfo gitInfo, string command) } } } - - private bool IsGitRepository(ProcessStartInfo gitInfo) + private bool TryFindGitRoot(string path, out string gitRoot) { - try + var directory = new DirectoryInfo(path); + while (directory != null) { - string output = ExecuteGitCommand(gitInfo, "rev-parse --is-inside-work-tree"); - return output.Trim().Equals("true", StringComparison.OrdinalIgnoreCase); - } - catch (Exception ex) - { - Log.LogMessage(MessageImportance.Low, $"Git repository check failed: {ex.Message}"); - return false; + if (Directory.Exists(Path.Combine(directory.FullName, ".git"))) + { + gitRoot = directory.FullName; + return true; + } + directory = directory.Parent; } + gitRoot = null; + return false; } private void RetrieveAllProjectReferences(string projectPath, List projects) { @@ -313,4 +318,4 @@ public static BranchVersioning Parse(string branchDefinition) return new BranchVersioning { BranchName = branchName, Prefix = prefix }; } } -} +} \ No newline at end of file diff --git a/tmpclaude-0451-cwd b/tmpclaude-0451-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-0451-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-06c9-cwd b/tmpclaude-06c9-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-06c9-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-0d56-cwd b/tmpclaude-0d56-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-0d56-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-1607-cwd b/tmpclaude-1607-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-1607-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-178d-cwd b/tmpclaude-178d-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-178d-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-1954-cwd b/tmpclaude-1954-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-1954-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-2a41-cwd b/tmpclaude-2a41-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-2a41-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-2f29-cwd b/tmpclaude-2f29-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-2f29-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-3302-cwd b/tmpclaude-3302-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-3302-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-44b0-cwd b/tmpclaude-44b0-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-44b0-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-44e3-cwd b/tmpclaude-44e3-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-44e3-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-4526-cwd b/tmpclaude-4526-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-4526-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-45bf-cwd b/tmpclaude-45bf-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-45bf-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-4717-cwd b/tmpclaude-4717-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-4717-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-588a-cwd b/tmpclaude-588a-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-588a-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-5c8b-cwd b/tmpclaude-5c8b-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-5c8b-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-5f2e-cwd b/tmpclaude-5f2e-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-5f2e-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-5f5a-cwd b/tmpclaude-5f5a-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-5f5a-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-675a-cwd b/tmpclaude-675a-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-675a-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-688a-cwd b/tmpclaude-688a-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-688a-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-6ad4-cwd b/tmpclaude-6ad4-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-6ad4-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-6cd1-cwd b/tmpclaude-6cd1-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-6cd1-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-70cf-cwd b/tmpclaude-70cf-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-70cf-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-735c-cwd b/tmpclaude-735c-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-735c-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-74cf-cwd b/tmpclaude-74cf-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-74cf-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-7870-cwd b/tmpclaude-7870-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-7870-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-798f-cwd b/tmpclaude-798f-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-798f-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-838d-cwd b/tmpclaude-838d-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-838d-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-9224-cwd b/tmpclaude-9224-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-9224-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-9450-cwd b/tmpclaude-9450-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-9450-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-967a-cwd b/tmpclaude-967a-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-967a-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-a996-cwd b/tmpclaude-a996-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-a996-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-b2d2-cwd b/tmpclaude-b2d2-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-b2d2-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-b575-cwd b/tmpclaude-b575-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-b575-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-b653-cwd b/tmpclaude-b653-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-b653-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-c00e-cwd b/tmpclaude-c00e-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-c00e-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-c421-cwd b/tmpclaude-c421-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-c421-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-d9e4-cwd b/tmpclaude-d9e4-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-d9e4-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-dc4d-cwd b/tmpclaude-dc4d-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-dc4d-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-dcd3-cwd b/tmpclaude-dcd3-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-dcd3-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-dd8c-cwd b/tmpclaude-dd8c-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-dd8c-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-ddf7-cwd b/tmpclaude-ddf7-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-ddf7-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-e031-cwd b/tmpclaude-e031-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-e031-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-e41b-cwd b/tmpclaude-e41b-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-e41b-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-e5fb-cwd b/tmpclaude-e5fb-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-e5fb-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build From b8bb5ad8282dc27c912c89254829033cff4b7bd2 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Sat, 17 Jan 2026 01:13:00 +0100 Subject: [PATCH 20/52] Plugin bug fixed --- .claude/settings.local.json | 7 ++- ...vKit.Build.Dataverse.ScriptLibrary.targets | 10 ++++ ...ild.Dataverse.Solution.OverridePAC.targets | 16 +++++++ .../Tasks/EnsurePluginAssemblyDataXml.cs | 46 +++++++++---------- tmpclaude-3b91-cwd | 1 + tmpclaude-4ddc-cwd | 1 + tmpclaude-6143-cwd | 1 + tmpclaude-7a15-cwd | 1 + tmpclaude-9fff-cwd | 1 + tmpclaude-c510-cwd | 1 + tmpclaude-cf16-cwd | 1 + 11 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 tmpclaude-3b91-cwd create mode 100644 tmpclaude-4ddc-cwd create mode 100644 tmpclaude-6143-cwd create mode 100644 tmpclaude-7a15-cwd create mode 100644 tmpclaude-9fff-cwd create mode 100644 tmpclaude-c510-cwd create mode 100644 tmpclaude-cf16-cwd diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b0ee8d8..42c4404 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,12 @@ "permissions": { "allow": [ "Bash(xargs -I {} sh -c 'echo \"\"=== {} ===\"\" && cat {}')", - "Bash(xargs -I {} sh -c 'echo \"\"=== $\\(basename {}\\) ===\"\" && head -30 {}')" + "Bash(xargs -I {} sh -c 'echo \"\"=== $\\(basename {}\\) ===\"\" && head -30 {}')", + "Bash(powershell.exe -ExecutionPolicy Bypass -File \"C:\\\\Users\\\\azeke\\\\souce\\\\repos\\\\talxis-projects\\\\WorkProjects\\\\tools-devkit-build\\\\pack-set.ps1\")", + "Bash(dotnet build:*)", + "Bash(rd /s /q \"C:\\\\Users\\\\azeke\\\\souce\\\\repos\\\\talxis-projects\\\\TestProjects\\\\TestProject\\\\src\\\\Solutions.Logic\\\\obj\")", + "Bash(cmd.exe /c:*)", + "Bash(powershell.exe:*)" ] } } diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 4b6d737..5c6dbf2 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -57,4 +57,14 @@ + + + <_ScriptLibraryMainFile Include="$(ScriptLibraryMainFile)" /> + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets index 712e64e..a15d78c 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets @@ -61,4 +61,20 @@ PcfForceUpdateFlag="$(PcfForceUpdate)" /> + + + + <_IntermediateDirsToRemove Include="$(SolutionPackagerMetadataWorkingDirectory)" Condition="Exists('$(SolutionPackagerMetadataWorkingDirectory)')" /> + <_IntermediateDirsToRemove Include="$(SolutionPackagerLocalizationWorkingDirectory)" Condition="Exists('$(SolutionPackagerLocalizationWorkingDirectory)')" /> + + + + + + + + diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index ba9914e..4e1eac0 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -143,7 +143,8 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize if (CopyPluginDll) { string destDllPath = Path.Combine(xmlDir, info.AssemblyName + ".dll"); - File.Copy(info.DllPath, destDllPath, true); + // Copy from source DLL path to avoid file locking issues + File.Copy(info.SourceDllPath, destDllPath, true); } UpsertRootComponentIntoSolutionXml( @@ -243,21 +244,9 @@ private string PrepareWorkingPluginDll(string assemblyName, string normalizedGui if (!File.Exists(sourceDllPath)) throw new FileNotFoundException("Build not found", sourceDllPath); - string metadataDir = Path.Combine( - PluginRootPath, - "obj", - Configuration, - TargetFramework, - "Metadata", - "PluginAssemblies", - assemblyName + "-" + normalizedGuid.ToUpperInvariant()); - - Directory.CreateDirectory(metadataDir); - - string preferredDllPath = Path.Combine(metadataDir, assemblyName + ".dll"); - - // If the destination is in use (e.g., another parallel task loaded it), fall back to a unique path under the same metadata directory. - return CopyPluginDllWithFallback(sourceDllPath, preferredDllPath); + // No longer copying to metadata directory to avoid file locking issues + // Since we load assemblies using Assembly.Load(byte[]), we don't need a separate copy + return sourceDllPath; } private HashSet BuildProbeDirectories(string dllPath, string projectDirectory) @@ -294,7 +283,12 @@ private static ResolveEventHandler CreateAssemblyResolveHandler(HashSet var candidate = Path.Combine(dir, name + ".dll"); if (File.Exists(candidate)) { - try { return Assembly.LoadFrom(candidate); } + try + { + // Load from byte array to avoid file locking + var bytes = File.ReadAllBytes(candidate); + return Assembly.Load(bytes); + } catch { /* ignore */ } } } @@ -517,7 +511,12 @@ private static string BuildAssemblyQualifiedTypeName(string className, string as private static void TryLoadAssemblyNoThrow(string path) { - try { Assembly.LoadFrom(path); } + try + { + // Load from byte array to avoid file locking + var bytes = File.ReadAllBytes(path); + Assembly.Load(bytes); + } catch { /* ignore */ } } @@ -556,19 +555,16 @@ private Assembly LoadPluginAssembly(string dllPath, string assemblyName, HashSet #else try { - return Assembly.LoadFrom(dllPath); + // Load from byte array to avoid file locking + var bytes = File.ReadAllBytes(dllPath); + return Assembly.Load(bytes); } catch (FileLoadException) { var loaded = FindLoadedAssembly(assemblyName); if (loaded != null) return loaded; - - string uniqueDir = Path.Combine(Path.GetDirectoryName(dllPath) ?? Path.GetTempPath(), "run-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(uniqueDir); - string altPath = Path.Combine(uniqueDir, Path.GetFileName(dllPath)); - File.Copy(dllPath, altPath, true); - return Assembly.LoadFrom(altPath); + throw; } #endif } diff --git a/tmpclaude-3b91-cwd b/tmpclaude-3b91-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-3b91-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-4ddc-cwd b/tmpclaude-4ddc-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-4ddc-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-6143-cwd b/tmpclaude-6143-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-6143-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-7a15-cwd b/tmpclaude-7a15-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-7a15-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-9fff-cwd b/tmpclaude-9fff-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-9fff-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-c510-cwd b/tmpclaude-c510-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-c510-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-cf16-cwd b/tmpclaude-cf16-cwd new file mode 100644 index 0000000..5f83f94 --- /dev/null +++ b/tmpclaude-cf16-cwd @@ -0,0 +1 @@ +/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build From f182b67d9b7430bc1d0f41b1e6e5e6f62acda53d Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 19 Jan 2026 17:43:45 +0100 Subject: [PATCH 21/52] remove unnesesarly files --- .claude/settings.local.json | 13 ------------- tmpclaude-0451-cwd | 1 - tmpclaude-06c9-cwd | 1 - tmpclaude-0d56-cwd | 1 - tmpclaude-1607-cwd | 1 - tmpclaude-178d-cwd | 1 - tmpclaude-1954-cwd | 1 - tmpclaude-2a41-cwd | 1 - tmpclaude-2f29-cwd | 1 - tmpclaude-3302-cwd | 1 - tmpclaude-3b91-cwd | 1 - tmpclaude-44b0-cwd | 1 - tmpclaude-44e3-cwd | 1 - tmpclaude-4526-cwd | 1 - tmpclaude-45bf-cwd | 1 - tmpclaude-4717-cwd | 1 - tmpclaude-4ddc-cwd | 1 - tmpclaude-588a-cwd | 1 - tmpclaude-5c8b-cwd | 1 - tmpclaude-5f2e-cwd | 1 - tmpclaude-5f5a-cwd | 1 - tmpclaude-6143-cwd | 1 - tmpclaude-675a-cwd | 1 - tmpclaude-688a-cwd | 1 - tmpclaude-6ad4-cwd | 1 - tmpclaude-6cd1-cwd | 1 - tmpclaude-70cf-cwd | 1 - tmpclaude-735c-cwd | 1 - tmpclaude-74cf-cwd | 1 - tmpclaude-7870-cwd | 1 - tmpclaude-798f-cwd | 1 - tmpclaude-7a15-cwd | 1 - tmpclaude-838d-cwd | 1 - tmpclaude-9224-cwd | 1 - tmpclaude-9450-cwd | 1 - tmpclaude-967a-cwd | 1 - tmpclaude-9fff-cwd | 1 - tmpclaude-a996-cwd | 1 - tmpclaude-b2d2-cwd | 1 - tmpclaude-b575-cwd | 1 - tmpclaude-b653-cwd | 1 - tmpclaude-c00e-cwd | 1 - tmpclaude-c421-cwd | 1 - tmpclaude-c510-cwd | 1 - tmpclaude-cf16-cwd | 1 - tmpclaude-d9e4-cwd | 1 - tmpclaude-dc4d-cwd | 1 - tmpclaude-dcd3-cwd | 1 - tmpclaude-dd8c-cwd | 1 - tmpclaude-ddf7-cwd | 1 - tmpclaude-e031-cwd | 1 - tmpclaude-e41b-cwd | 1 - tmpclaude-e5fb-cwd | 1 - 53 files changed, 65 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 tmpclaude-0451-cwd delete mode 100644 tmpclaude-06c9-cwd delete mode 100644 tmpclaude-0d56-cwd delete mode 100644 tmpclaude-1607-cwd delete mode 100644 tmpclaude-178d-cwd delete mode 100644 tmpclaude-1954-cwd delete mode 100644 tmpclaude-2a41-cwd delete mode 100644 tmpclaude-2f29-cwd delete mode 100644 tmpclaude-3302-cwd delete mode 100644 tmpclaude-3b91-cwd delete mode 100644 tmpclaude-44b0-cwd delete mode 100644 tmpclaude-44e3-cwd delete mode 100644 tmpclaude-4526-cwd delete mode 100644 tmpclaude-45bf-cwd delete mode 100644 tmpclaude-4717-cwd delete mode 100644 tmpclaude-4ddc-cwd delete mode 100644 tmpclaude-588a-cwd delete mode 100644 tmpclaude-5c8b-cwd delete mode 100644 tmpclaude-5f2e-cwd delete mode 100644 tmpclaude-5f5a-cwd delete mode 100644 tmpclaude-6143-cwd delete mode 100644 tmpclaude-675a-cwd delete mode 100644 tmpclaude-688a-cwd delete mode 100644 tmpclaude-6ad4-cwd delete mode 100644 tmpclaude-6cd1-cwd delete mode 100644 tmpclaude-70cf-cwd delete mode 100644 tmpclaude-735c-cwd delete mode 100644 tmpclaude-74cf-cwd delete mode 100644 tmpclaude-7870-cwd delete mode 100644 tmpclaude-798f-cwd delete mode 100644 tmpclaude-7a15-cwd delete mode 100644 tmpclaude-838d-cwd delete mode 100644 tmpclaude-9224-cwd delete mode 100644 tmpclaude-9450-cwd delete mode 100644 tmpclaude-967a-cwd delete mode 100644 tmpclaude-9fff-cwd delete mode 100644 tmpclaude-a996-cwd delete mode 100644 tmpclaude-b2d2-cwd delete mode 100644 tmpclaude-b575-cwd delete mode 100644 tmpclaude-b653-cwd delete mode 100644 tmpclaude-c00e-cwd delete mode 100644 tmpclaude-c421-cwd delete mode 100644 tmpclaude-c510-cwd delete mode 100644 tmpclaude-cf16-cwd delete mode 100644 tmpclaude-d9e4-cwd delete mode 100644 tmpclaude-dc4d-cwd delete mode 100644 tmpclaude-dcd3-cwd delete mode 100644 tmpclaude-dd8c-cwd delete mode 100644 tmpclaude-ddf7-cwd delete mode 100644 tmpclaude-e031-cwd delete mode 100644 tmpclaude-e41b-cwd delete mode 100644 tmpclaude-e5fb-cwd diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 42c4404..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(xargs -I {} sh -c 'echo \"\"=== {} ===\"\" && cat {}')", - "Bash(xargs -I {} sh -c 'echo \"\"=== $\\(basename {}\\) ===\"\" && head -30 {}')", - "Bash(powershell.exe -ExecutionPolicy Bypass -File \"C:\\\\Users\\\\azeke\\\\souce\\\\repos\\\\talxis-projects\\\\WorkProjects\\\\tools-devkit-build\\\\pack-set.ps1\")", - "Bash(dotnet build:*)", - "Bash(rd /s /q \"C:\\\\Users\\\\azeke\\\\souce\\\\repos\\\\talxis-projects\\\\TestProjects\\\\TestProject\\\\src\\\\Solutions.Logic\\\\obj\")", - "Bash(cmd.exe /c:*)", - "Bash(powershell.exe:*)" - ] - } -} diff --git a/tmpclaude-0451-cwd b/tmpclaude-0451-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-0451-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-06c9-cwd b/tmpclaude-06c9-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-06c9-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-0d56-cwd b/tmpclaude-0d56-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-0d56-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-1607-cwd b/tmpclaude-1607-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-1607-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-178d-cwd b/tmpclaude-178d-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-178d-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-1954-cwd b/tmpclaude-1954-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-1954-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-2a41-cwd b/tmpclaude-2a41-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-2a41-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-2f29-cwd b/tmpclaude-2f29-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-2f29-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-3302-cwd b/tmpclaude-3302-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-3302-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-3b91-cwd b/tmpclaude-3b91-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-3b91-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-44b0-cwd b/tmpclaude-44b0-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-44b0-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-44e3-cwd b/tmpclaude-44e3-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-44e3-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-4526-cwd b/tmpclaude-4526-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-4526-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-45bf-cwd b/tmpclaude-45bf-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-45bf-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-4717-cwd b/tmpclaude-4717-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-4717-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-4ddc-cwd b/tmpclaude-4ddc-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-4ddc-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-588a-cwd b/tmpclaude-588a-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-588a-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-5c8b-cwd b/tmpclaude-5c8b-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-5c8b-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-5f2e-cwd b/tmpclaude-5f2e-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-5f2e-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-5f5a-cwd b/tmpclaude-5f5a-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-5f5a-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-6143-cwd b/tmpclaude-6143-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-6143-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-675a-cwd b/tmpclaude-675a-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-675a-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-688a-cwd b/tmpclaude-688a-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-688a-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-6ad4-cwd b/tmpclaude-6ad4-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-6ad4-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-6cd1-cwd b/tmpclaude-6cd1-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-6cd1-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-70cf-cwd b/tmpclaude-70cf-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-70cf-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-735c-cwd b/tmpclaude-735c-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-735c-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-74cf-cwd b/tmpclaude-74cf-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-74cf-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-7870-cwd b/tmpclaude-7870-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-7870-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-798f-cwd b/tmpclaude-798f-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-798f-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-7a15-cwd b/tmpclaude-7a15-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-7a15-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-838d-cwd b/tmpclaude-838d-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-838d-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-9224-cwd b/tmpclaude-9224-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-9224-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-9450-cwd b/tmpclaude-9450-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-9450-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-967a-cwd b/tmpclaude-967a-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-967a-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-9fff-cwd b/tmpclaude-9fff-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-9fff-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-a996-cwd b/tmpclaude-a996-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-a996-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-b2d2-cwd b/tmpclaude-b2d2-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-b2d2-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-b575-cwd b/tmpclaude-b575-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-b575-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-b653-cwd b/tmpclaude-b653-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-b653-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-c00e-cwd b/tmpclaude-c00e-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-c00e-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-c421-cwd b/tmpclaude-c421-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-c421-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-c510-cwd b/tmpclaude-c510-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-c510-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-cf16-cwd b/tmpclaude-cf16-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-cf16-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-d9e4-cwd b/tmpclaude-d9e4-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-d9e4-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-dc4d-cwd b/tmpclaude-dc4d-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-dc4d-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-dcd3-cwd b/tmpclaude-dcd3-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-dcd3-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-dd8c-cwd b/tmpclaude-dd8c-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-dd8c-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-ddf7-cwd b/tmpclaude-ddf7-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-ddf7-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-e031-cwd b/tmpclaude-e031-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-e031-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-e41b-cwd b/tmpclaude-e41b-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-e41b-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build diff --git a/tmpclaude-e5fb-cwd b/tmpclaude-e5fb-cwd deleted file mode 100644 index 5f83f94..0000000 --- a/tmpclaude-e5fb-cwd +++ /dev/null @@ -1 +0,0 @@ -/c/Users/azeke/souce/repos/talxis-projects/WorkProjects/tools-devkit-build From 8e158f419d59da32608006b15e1e65b7393e10df Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 20 Jan 2026 11:29:37 +0100 Subject: [PATCH 22/52] data.xml generating changed --- .../Tasks/Tasks/ApplyVersionNumber.cs | 60 +++++++++++++++++- .../Tasks/EnsurePluginAssemblyDataXml.cs | 62 +------------------ 2 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs b/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs index 9f05cc7..40736ca 100644 --- a/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs +++ b/src/Dataverse/Tasks/Tasks/ApplyVersionNumber.cs @@ -19,6 +19,7 @@ public class ApplyVersionNumber : Task [Required] public string WorkingDirectoryPath { get; set; } public ITaskItem PluginAssembliesFolder { get; set; } + public ITaskItem[] PluginAssembliesFolders { get; set; } public ITaskItem SdkMessageProcessingStepsFolder { get; set; } public ITaskItem WorkflowsFolder { get; set; } public ITaskItem ControlsFolder { get; set; } @@ -28,15 +29,23 @@ public class ApplyVersionNumber : Task public override bool Execute() { UpdateVersionInSolutionXmlFile(SolutionXml.ItemSpec, Version); - if (PluginAssembliesFolder != null && Directory.Exists(PluginAssembliesFolder.ItemSpec)) + var pluginAssemblyFolders = GetPluginAssemblyFolders().ToList(); + foreach (var pluginAssembliesFolder in pluginAssemblyFolders) { - var pluginAssemblies = Directory.EnumerateFiles(PluginAssembliesFolder.ItemSpec, "*.dll.data.xml", SearchOption.AllDirectories); + var pluginAssemblies = Directory.EnumerateFiles(pluginAssembliesFolder, "*.dll.data.xml", SearchOption.AllDirectories); foreach (var pluginAssemblyXmlPath in pluginAssemblies) { var pluginAssemblyDocument = XDocument.Load(pluginAssemblyXmlPath); var fullNameAttributeValue = pluginAssemblyDocument.Root.Attribute("FullName")?.Value; var assemblyName = fullNameAttributeValue?.Split(',')[0].Trim(); - var assembly = Assembly.LoadFrom(pluginAssemblyXmlPath.Replace(".data.xml", "")); + var assemblyPath = ResolveAssemblyPath(pluginAssemblyXmlPath, pluginAssemblyFolders); + if (assemblyPath == null) + { + Log.LogMessage(MessageImportance.High, $" > Skipping plugin assembly: file not found for {pluginAssemblyXmlPath}"); + continue; + } + + var assembly = Assembly.LoadFrom(assemblyPath); _assemblies.Add(assembly); Log.LogMessage(MessageImportance.High, $" > Discovered {assembly.FullName} at {pluginAssemblyXmlPath}"); @@ -161,4 +170,49 @@ private string ExtractVersionFromFQDN(string fullName) var match = Regex.Match(fullName, @"Version=([\d.]*),"); return match.Success ? match.Groups[1].Value : null; } + + private string ResolveAssemblyPath(string pluginAssemblyXmlPath, IEnumerable searchRoots) + { + var directPath = pluginAssemblyXmlPath.Replace(".data.xml", ""); + if (File.Exists(directPath)) + { + return directPath; + } + + var assemblyFileName = Path.GetFileName(directPath); + foreach (var root in searchRoots) + { + var match = Directory.EnumerateFiles(root, assemblyFileName, SearchOption.AllDirectories).FirstOrDefault(); + if (match != null) + { + return match; + } + } + + return null; + } + + private IEnumerable GetPluginAssemblyFolders() + { + var folders = new List(); + + if (PluginAssembliesFolders != null) + { + folders.AddRange(PluginAssembliesFolders.Select(x => x?.ItemSpec).Where(x => !string.IsNullOrWhiteSpace(x))); + } + + if (PluginAssembliesFolder != null && !string.IsNullOrWhiteSpace(PluginAssembliesFolder.ItemSpec)) + { + folders.Add(PluginAssembliesFolder.ItemSpec); + } + + var distinctFolders = folders.Where(Directory.Exists).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + foreach (var missing in folders.Except(distinctFolders, StringComparer.OrdinalIgnoreCase)) + { + Log.LogMessage(MessageImportance.Low, $"Skipping non-existent plugin assemblies folder: {missing}"); + } + + return distinctFolders; + } } \ No newline at end of file diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 4e1eac0..aa2b58d 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -26,7 +26,6 @@ public sealed class EnsurePluginAssemblyDataXml : Task public string TargetFramework { get; set; } = "net462"; public string PublishFolderName { get; set; } = "publish"; public string PluginDllPath { get; set; } = ""; - public bool CopyPluginDll { get; set; } = true; public override bool Execute() { @@ -88,8 +87,7 @@ private PluginProjectInfo BuildProjectInfo(string repoRoot, string normalizedGui string assemblyName = meta.Item1; string fileVersion = meta.Item2; - string sourceDllPath = ResolvePluginDllPath(assemblyName); - string dllPath = PrepareWorkingPluginDll(assemblyName, normalizedGuid); + string dllPath = ResolvePluginDllPath(assemblyName); string xmlPath = BuildPluginDataXmlPath(repoRoot, assemblyName, normalizedGuid); return new PluginProjectInfo @@ -100,8 +98,7 @@ private PluginProjectInfo BuildProjectInfo(string repoRoot, string normalizedGui AssemblyName = assemblyName, FileVersion = fileVersion, XmlPath = xmlPath, - DllPath = dllPath, - SourceDllPath = sourceDllPath + DllPath = dllPath }; } @@ -140,13 +137,6 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize pluginDoc.Save(info.XmlPath); - if (CopyPluginDll) - { - string destDllPath = Path.Combine(xmlDir, info.AssemblyName + ".dll"); - // Copy from source DLL path to avoid file locking issues - File.Copy(info.SourceDllPath, destDllPath, true); - } - UpsertRootComponentIntoSolutionXml( info.RepositoryRoot, normalizedGuid, @@ -237,18 +227,6 @@ private string ResolvePluginDllPath(string assemblyName) return BuildPluginDllPath(assemblyName); } - private string PrepareWorkingPluginDll(string assemblyName, string normalizedGuid) - { - string sourceDllPath = ResolvePluginDllPath(assemblyName); - - if (!File.Exists(sourceDllPath)) - throw new FileNotFoundException("Build not found", sourceDllPath); - - // No longer copying to metadata directory to avoid file locking issues - // Since we load assemblies using Assembly.Load(byte[]), we don't need a separate copy - return sourceDllPath; - } - private HashSet BuildProbeDirectories(string dllPath, string projectDirectory) { string dllDir = Path.GetDirectoryName(dllPath); @@ -580,41 +558,6 @@ private static Assembly FindLoadedAssembly(string assemblyName) }); } - private string CopyPluginDllWithFallback(string sourceDllPath, string preferredPath) - { - try - { - CopyFileWithRetry(sourceDllPath, preferredPath, overwrite: true); - return preferredPath; - } - catch (IOException) - { - string parentDir = Path.GetDirectoryName(preferredPath) ?? Path.GetTempPath(); - string altDir = Path.Combine(parentDir, "run-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(altDir); - - string altPath = Path.Combine(altDir, Path.GetFileName(preferredPath)); - CopyFileWithRetry(sourceDllPath, altPath, overwrite: true); - return altPath; - } - } - - private static void CopyFileWithRetry(string source, string destination, bool overwrite, int maxAttempts = 3, int delayMs = 200) - { - for (int attempt = 1; ; attempt++) - { - try - { - File.Copy(source, destination, overwrite); - return; - } - catch (IOException) when (attempt < maxAttempts) - { - Thread.Sleep(delayMs); - } - } - } - private static string NormalizeGuid(string guidText) { if (string.IsNullOrWhiteSpace(guidText)) @@ -745,6 +688,5 @@ private sealed class PluginProjectInfo public string FileVersion { get; set; } = ""; public string XmlPath { get; set; } = ""; public string DllPath { get; set; } = ""; - public string SourceDllPath { get; set; } = ""; } } From 7d535ded2eba2f82306f2ab34e4d8c730dfa7a8b Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 20 Jan 2026 14:41:39 +0100 Subject: [PATCH 23/52] No plugin dll copy --- .claude/settings.local.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..0f1662d --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet pack:*)", + "Bash(dotnet clean:*)", + "Bash(find:*)", + "Bash(grep:*)", + "Bash(git rm:*)", + "Bash(dotnet build:*)", + "Bash(Select-String -Pattern \"\\\\.resources|MSB3577\" -Context 3,3)", + "Bash(tasklist:*)", + "Bash(dotnet restore:*)" + ] + } +} From bdee86e3036a6c5397adef23fe999d75c1badae1 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 20 Jan 2026 15:48:05 +0100 Subject: [PATCH 24/52] plugin dll copy --- ...XIS.DevKit.Build.Dataverse.Solution.Plugin.targets | 4 ++++ .../Tasks/Tasks/EnsurePluginAssemblyDataXml.cs | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index 980eb7e..cc051ba 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -2,6 +2,10 @@ + + BuildPluginLibraries;$(BuildDependsOn) + + diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index aa2b58d..3a18f35 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -136,6 +136,7 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize ); pluginDoc.Save(info.XmlPath); + CopyPluginAssembly(info); UpsertRootComponentIntoSolutionXml( info.RepositoryRoot, @@ -679,6 +680,16 @@ private static XmlElement CreatePluginTypeElement(XmlDocument doc) return doc.CreateElement("PluginType"); } + private static void CopyPluginAssembly(PluginProjectInfo info) + { + string destDir = Path.GetDirectoryName(info.XmlPath); + if (string.IsNullOrEmpty(destDir)) + throw new Exception("PluginAssembly data directory not resolved"); + + string destPath = Path.Combine(destDir, info.AssemblyName + ".dll"); + File.Copy(info.DllPath, destPath, true); + } + private sealed class PluginProjectInfo { public string RepositoryRoot { get; set; } = ""; From 2457d6d8394e04461c8997b44f23eb6126358fc6 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 20 Jan 2026 22:16:48 +0100 Subject: [PATCH 25/52] plugin dll copy --- .../tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets | 5 +++-- .../TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index 9bbb4d8..409c8da 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -27,10 +27,11 @@ + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index cc051ba..f49c219 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -71,7 +71,7 @@ TargetFramework="%(_PluginAssemblyInfo.TargetFramework)" PublishFolderName="%(_PluginAssemblyInfo.PublishFolderName)" PluginDllPath="%(_PluginAssemblyInfo.PluginDllPath)" - Condition="'@(_PluginAssemblyInfo)'!=''" /> + Condition="'@(_PluginAssemblyInfo)'!='' and '@(_PluginLibraryProjects)'!=''" /> From b4e25db119fa76c842449253f6250f399848455b Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Sun, 25 Jan 2026 16:03:52 +0100 Subject: [PATCH 26/52] plugin dll copy to metadata. plugin assemply any folder --- ...TALXIS.DevKit.Build.Dataverse.Plugin.props | 1 - ...LXIS.DevKit.Build.Dataverse.Solution.props | 1 - ...ild.Dataverse.Solution.OverridePAC.targets | 20 +------------------ ...it.Build.Dataverse.Solution.Plugin.targets | 9 --------- ...LXIS.DevKit.Build.Dataverse.Solution.props | 6 ++++-- 5 files changed, 5 insertions(+), 32 deletions(-) diff --git a/src/Dataverse/Plugin/msbuild/build/TALXIS.DevKit.Build.Dataverse.Plugin.props b/src/Dataverse/Plugin/msbuild/build/TALXIS.DevKit.Build.Dataverse.Plugin.props index 29482aa..a10194c 100644 --- a/src/Dataverse/Plugin/msbuild/build/TALXIS.DevKit.Build.Dataverse.Plugin.props +++ b/src/Dataverse/Plugin/msbuild/build/TALXIS.DevKit.Build.Dataverse.Plugin.props @@ -1,5 +1,4 @@ - \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props index 29482aa..a10194c 100644 --- a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props +++ b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props @@ -1,5 +1,4 @@ - \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets index a15d78c..298e856 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets @@ -23,7 +23,6 @@ <_ScriptLibraryProjectsList>;@(_ScriptLibraryProjects->'%(Identity)'); - <_PluginProjectsList>;@(_PluginProjects->'%(Identity)'); @@ -33,8 +32,6 @@ <_CdsRefs Remove="@(_CdsRefs)" Condition="$([System.String]::Copy('$(_ScriptLibraryProjectsList)').Contains(';%(FullPath);'))" /> - <_CdsRefs Remove="@(_CdsRefs)" - Condition="$([System.String]::Copy('$(_PluginProjectsList)').Contains(';%(FullPath);'))" /> - - - - <_IntermediateDirsToRemove Include="$(SolutionPackagerMetadataWorkingDirectory)" Condition="Exists('$(SolutionPackagerMetadataWorkingDirectory)')" /> - <_IntermediateDirsToRemove Include="$(SolutionPackagerLocalizationWorkingDirectory)" Condition="Exists('$(SolutionPackagerLocalizationWorkingDirectory)')" /> - - - - - - - - + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index f49c219..2423e29 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -63,15 +63,6 @@ - - diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props index 763fffb..d8f6fef 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props @@ -1,4 +1,6 @@ - - \ No newline at end of file + + Solution + + From 6b8da45d80e8ff80bab53ddabd24ef43afbce71c Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Sun, 25 Jan 2026 17:55:51 +0100 Subject: [PATCH 27/52] data.xml version generating --- ...it.Build.Dataverse.Solution.Plugin.targets | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index 2423e29..ce1c484 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -30,7 +30,7 @@ @@ -63,6 +63,27 @@ + + + + + + + + From 9c9535fa448a526a4591472c96fad9b6f06b1442 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Sun, 25 Jan 2026 18:33:56 +0100 Subject: [PATCH 28/52] metadata set up --- .../Tasks/EnsurePluginAssemblyDataXml.cs | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 3a18f35..dd2f624 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -132,7 +132,8 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize normalizedGuid, classList, info.CsprojFileName, - info.XmlPath + info.XmlPath, + info.RepositoryRoot ); pluginDoc.Save(info.XmlPath); @@ -170,33 +171,47 @@ private static string GetProjectDirectory(string csprojPath) return projectDirectory; } + private static string FindExistingDataXml(string repoRoot, string assemblyName) + { + string pluginAssembliesRoot = Path.Combine(repoRoot, "PluginAssemblies"); + + if (!Directory.Exists(pluginAssembliesRoot)) + return null; + + string dataXmlFileName = assemblyName + ".dll.data.xml"; + + var files = Directory.GetFiles(pluginAssembliesRoot, dataXmlFileName, SearchOption.AllDirectories); + + return files.FirstOrDefault(); + } + private static string BuildPluginDataXmlPath(string repoRoot, string assemblyName, string normalizedGuid) { + string existingXmlPath = FindExistingDataXml(repoRoot, assemblyName); + + if (existingXmlPath != null) + { + return existingXmlPath; + } + return Path.Combine( repoRoot, "PluginAssemblies", - assemblyName + "-" + normalizedGuid.ToUpperInvariant(), assemblyName + ".dll.data.xml" ); } private string FindPluginAssemblyId(string repoRoot, string assemblyName) { - string pluginAssembliesRoot = Path.Combine(repoRoot, "PluginAssemblies"); - - if (!Directory.Exists(pluginAssembliesRoot)) return ""; - - var matchDirs = Directory.GetDirectories(pluginAssembliesRoot, "*" + assemblyName + "*"); + string existingXmlPath = FindExistingDataXml(repoRoot, assemblyName); - if (matchDirs.Length == 0) return ""; - - var xmlPath = matchDirs.FirstOrDefault() == null ? null : Directory.GetFiles(matchDirs.FirstOrDefault(), "*.xml").FirstOrDefault(); - - if (xmlPath == null) return ""; + if (existingXmlPath == null) + return ""; - var doc = XDocument.Load(xmlPath); + var doc = XDocument.Load(existingXmlPath); var root = doc.Root; - if (root == null) return ""; + if (root == null) + return ""; var idAttr = root.Attribute("PluginAssemblyId"); return idAttr == null ? "" : idAttr.Value; @@ -328,7 +343,8 @@ private static XmlDocument CreatePluginAssemblyDocument( string normalizedGuid, IEnumerable classList, string csprojFileName, - string existingXmlPath) + string existingXmlPath, + string repoRoot) { var pluginDoc = new XmlDocument(); var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); @@ -350,7 +366,7 @@ private static XmlDocument CreatePluginAssemblyDocument( root.AppendChild(sourceType); XmlElement fileName = pluginDoc.CreateElement("FileName"); - fileName.InnerText = "/PluginAssemblies/" + assemblyName + "-" + normalizedGuid.ToUpperInvariant() + "/" + assemblyName + ".dll"; + fileName.InnerText = BuildRelativeDllPath(existingXmlPath, repoRoot, assemblyName); root.AppendChild(fileName); XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); @@ -478,6 +494,28 @@ private static string NormalizeGuidBraces(string s) } + private static string BuildRelativeDllPath(string xmlPath, string repoRoot, string assemblyName) + { + string xmlDir = Path.GetDirectoryName(xmlPath); + if (string.IsNullOrEmpty(xmlDir)) + return "/PluginAssemblies/" + assemblyName + ".dll"; + + string pluginAssembliesRoot = Path.Combine(repoRoot, "PluginAssemblies"); + string relativePath; + + if (xmlDir.Equals(pluginAssembliesRoot, StringComparison.OrdinalIgnoreCase)) + { + relativePath = "/PluginAssemblies/" + assemblyName + ".dll"; + } + else + { + string subFolder = xmlDir.Substring(pluginAssembliesRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + relativePath = "/PluginAssemblies/" + subFolder.Replace(Path.DirectorySeparatorChar, '/') + "/" + assemblyName + ".dll"; + } + + return relativePath; + } + private static string BuildAssemblyFullName(string assemblyName, string fileVersion, string publicKeyToken) { return assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken; From 7e6e2433028f7c2eb59970368aae24cce4e6acac Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Sun, 25 Jan 2026 19:29:48 +0100 Subject: [PATCH 29/52] Custom Temp folder --- .../Tasks/EnsurePluginAssemblyDataXml.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index dd2f624..9068010 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -107,7 +107,9 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize if (!File.Exists(info.DllPath)) throw new FileNotFoundException("Build not found", info.DllPath); - HashSet probeDirs = BuildProbeDirectories(info.DllPath, info.ProjectDirectory); + string tempDllPath = CopyDllToTempFolder(info); + + HashSet probeDirs = BuildProbeDirectories(tempDllPath, info.ProjectDirectory); ResolveEventHandler handler = CreateAssemblyResolveHandler(probeDirs); AppDomain.CurrentDomain.AssemblyResolve += handler; @@ -116,7 +118,7 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize { TryAddSdkAssemblyProbe(probeDirs); - Assembly pluginAssembly = LoadPluginAssembly(info.DllPath, info.AssemblyName, probeDirs); + Assembly pluginAssembly = LoadPluginAssembly(tempDllPath, info.AssemblyName, probeDirs); string publicKeyToken = GetPublicKeyToken(pluginAssembly); List classList = GetPluginClassNames(pluginAssembly); @@ -137,7 +139,6 @@ private void GeneratePluginAssemblyData(PluginProjectInfo info, string normalize ); pluginDoc.Save(info.XmlPath); - CopyPluginAssembly(info); UpsertRootComponentIntoSolutionXml( info.RepositoryRoot, @@ -718,6 +719,24 @@ private static XmlElement CreatePluginTypeElement(XmlDocument doc) return doc.CreateElement("PluginType"); } + private string CopyDllToTempFolder(PluginProjectInfo info) + { + string tempDir = Path.Combine( + info.RepositoryRoot, + "obj", + Configuration, + TargetFramework, + "Temp" + ); + + Directory.CreateDirectory(tempDir); + + string tempDllPath = Path.Combine(tempDir, info.AssemblyName + ".dll"); + File.Copy(info.DllPath, tempDllPath, true); + + return tempDllPath; + } + private static void CopyPluginAssembly(PluginProjectInfo info) { string destDir = Path.GetDirectoryName(info.XmlPath); From 5abcd2940e412d68266912ecbde0f75b822cf022 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Sun, 25 Jan 2026 19:30:42 +0100 Subject: [PATCH 30/52] Custom Temp folder --- .claude/settings.local.json | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 0f1662d..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(dotnet pack:*)", - "Bash(dotnet clean:*)", - "Bash(find:*)", - "Bash(grep:*)", - "Bash(git rm:*)", - "Bash(dotnet build:*)", - "Bash(Select-String -Pattern \"\\\\.resources|MSB3577\" -Context 3,3)", - "Bash(tasklist:*)", - "Bash(dotnet restore:*)" - ] - } -} From 383d01fc844dc9c2d3d3f8fd03188cfc5f5fce5d Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 26 Jan 2026 10:19:52 +0100 Subject: [PATCH 31/52] publisher prefix condition --- .claude/settings.local.json | 8 ++ ...Dataverse.Solution.ScriptLibraries.targets | 17 +++-- .../Tasks/Tasks/ResolveWebResourceName.cs | 73 +++++++++++++++++++ ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 1 + 4 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 src/Dataverse/Tasks/Tasks/ResolveWebResourceName.cs diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ad3f3a7 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "mcp__Desktop_Commander__list_directory", + "Bash(git checkout:*)" + ] + } +} diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index 5d8130f..89455fa 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -58,14 +58,17 @@ + + + + - <_ScriptFiles Include="@(_ScriptLibraryOutputs)" /> - <_ScriptFilesToCopy Include="@(_ScriptFiles)"> - - $(WebResourcesDir)$(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')) - $(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')) - $([System.IO.Path]::GetFileName('%(Identity)')) - $(WebResourcesDir)$(PublisherPrefix)_$([System.IO.Path]::GetFileName('%(Identity)')).data.xml + <_ScriptFilesToCopy Include="@(_ResolvedScriptFiles)"> + $(WebResourcesDir)%(ResolvedName) + %(ResolvedName) + %(DisplayName) + $(WebResourcesDir)%(ResolvedName).data.xml diff --git a/src/Dataverse/Tasks/Tasks/ResolveWebResourceName.cs b/src/Dataverse/Tasks/Tasks/ResolveWebResourceName.cs new file mode 100644 index 0000000..dfc1782 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/ResolveWebResourceName.cs @@ -0,0 +1,73 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System; +using System.Collections.Generic; + +public class ResolveWebResourceName : Task +{ + [Required] + public ITaskItem[] Files { get; set; } = Array.Empty(); + + [Required] + public string PublisherPrefix { get; set; } = ""; + + [Output] + public ITaskItem[] ResolvedFiles { get; set; } = Array.Empty(); + + public override bool Execute() + { + try + { + string prefix = PublisherPrefix.Trim().ToLowerInvariant(); + var results = new List(); + + foreach (var file in Files) + { + string filePath = file.ItemSpec; + string fileName = System.IO.Path.GetFileName(filePath); + + string resolvedName; + string displayName; + + int underscoreIndex = fileName.IndexOf('_'); + + if (underscoreIndex > 0) + { + string existingPrefix = fileName.Substring(0, underscoreIndex).ToLowerInvariant(); + + if (existingPrefix.Equals(prefix, StringComparison.OrdinalIgnoreCase)) + { + resolvedName = fileName; + displayName = fileName.Substring(underscoreIndex + 1); + } + else + { + resolvedName = prefix + "_" + fileName; + displayName = fileName; + } + } + else + { + resolvedName = prefix + "_" + fileName; + displayName = fileName; + } + + var resultItem = new TaskItem(filePath); + resultItem.SetMetadata("ResolvedName", resolvedName); + resultItem.SetMetadata("DisplayName", displayName); + results.Add(resultItem); + + Log.LogMessage(MessageImportance.Normal, + $"ResolveWebResourceName: {fileName} -> {resolvedName} (DisplayName: {displayName})"); + } + + ResolvedFiles = results.ToArray(); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true); + return false; + } + } +} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index 1de8a98..e0661e6 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -31,4 +31,5 @@ + From ff989fd64f893a8fbedad544f6236518ba99bac3 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 26 Jan 2026 14:35:28 +0100 Subject: [PATCH 32/52] script library build update --- .../TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props | 3 ++- .../TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets | 8 -------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props index b357e3b..19359f0 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props @@ -1,8 +1,9 @@ ScriptLibrary + main - $(MSBuildProjectDirectory)\TS\build\main.js + $(MSBuildProjectDirectory)\TS\build\$(ScriptLibraryName).js latest false diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 5c6dbf2..272d1b9 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -49,14 +49,6 @@ - - - <_ProjectOutputPath Include="$(TargetPath)" /> - - - From 01dec1143ccd128fb66757937afcfadc6ff704b5 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 26 Jan 2026 20:30:54 +0100 Subject: [PATCH 33/52] workfow --- src/Dataverse/WorkflowActivity/README.md | 12 +++++ ...it.Build.Dataverse.WorkflowActivity.csproj | 25 +++++++++++ ...it.Build.Dataverse.WorkflowActivity.nuspec | 28 ++++++++++++ ...Kit.Build.Dataverse.WorkflowActivity.props | 5 +++ ...t.Build.Dataverse.WorkflowActivity.targets | 5 +++ ...Kit.Build.Dataverse.WorkflowActivity.props | 15 +++++++ ...t.Build.Dataverse.WorkflowActivity.targets | 45 +++++++++++++++++++ 7 files changed, 135 insertions(+) create mode 100644 src/Dataverse/WorkflowActivity/README.md create mode 100644 src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.csproj create mode 100644 src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec create mode 100644 src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props create mode 100644 src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets create mode 100644 src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props create mode 100644 src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets diff --git a/src/Dataverse/WorkflowActivity/README.md b/src/Dataverse/WorkflowActivity/README.md new file mode 100644 index 0000000..391ea0b --- /dev/null +++ b/src/Dataverse/WorkflowActivity/README.md @@ -0,0 +1,12 @@ +# TALXIS.DevKit.Build.Dataverse.WorkflowActivity + +MSBuild targets for Dataverse workflowActivity projects. + +## MSBuild properties +- `ProjectType` (default `WorkflowActivity`): marks the project as a workflowActivity for reference discovery. +- `Version` (required): base version; major/minor are used for Git versioning and applied to assembly/package versions. +- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). +- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. +- `WorkflowActivityTargetFramework` (default `$(TargetFramework)` when set, otherwise `net462`): target framework used to locate the compiled workflowActivity DLL. +- `WorkflowActivityPublishFolderName` (default `publish`): publish folder name under `bin\\\`. +- `WorkflowActivityAssemblyId`: explicit GUID for the workflowActivity assembly metadata; if empty, a new GUID is generated. diff --git a/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.csproj b/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.csproj new file mode 100644 index 0000000..27eece5 --- /dev/null +++ b/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.csproj @@ -0,0 +1,25 @@ + + + TALXIS.DevKit.Build.Dataverse.WorkflowActivity + + + net472;net6.0 + + true + true + 0.0.0.1 + true + true + NU1604 + + $(MSBuildProjectName).nuspec + $(OutputPath) + Version=$(Version);MicrosoftPowerAppsTargetsVersion=$(MicrosoftPowerAppsTargetsVersion) + + + + + + + + \ No newline at end of file diff --git a/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec b/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec new file mode 100644 index 0000000..b4d837f --- /dev/null +++ b/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec @@ -0,0 +1,28 @@ + + + + TALXIS.DevKit.Build.Dataverse.WorkflowActivity + $Version$ + TALXIS + true + MIT + https://licenses.nuget.org/MIT + README.md + https://github.com/TALXIS/tools-devkit-build + Dataverse MSBuild WorkflowActivity + https://github.com/TALXIS/tools-devkit-build/releases + 2025 NETWORG + + + + + + + + + + + + + + diff --git a/src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props b/src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props new file mode 100644 index 0000000..8a0424d --- /dev/null +++ b/src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets b/src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets new file mode 100644 index 0000000..29482aa --- /dev/null +++ b/src/Dataverse/WorkflowActivity/msbuild/build/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props b/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props new file mode 100644 index 0000000..72d8cf9 --- /dev/null +++ b/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props @@ -0,0 +1,15 @@ + + + + + WorkflowActivity + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps + {2AA76AF3-4D9E-4AF0-B243-EB9BCDFB143B};{32f31d43-81cc-4c15-9de6-3fc5453562b6};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + + + diff --git a/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets b/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets new file mode 100644 index 0000000..54aa101 --- /dev/null +++ b/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.targets @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + <_ProjectType Include="$(MSBuildProjectFullPath)"> + $(ProjectType) + + + + + + + <_WorkflowActivityTargetFramework Condition="'$(TargetFramework)'!=''">$(TargetFramework) + <_WorkflowActivityTargetFramework Condition="'$(_WorkflowActivityTargetFramework)'=='' and '$(WorkflowActivityTargetFramework)'!=''">$(WorkflowActivityTargetFramework) + <_WorkflowActivityTargetFramework Condition="'$(_WorkflowActivityTargetFramework)'==''">net462 + + <_WorkflowActivityPublishFolderName Condition="'$(WorkflowActivityPublishFolderName)'!=''">$(WorkflowActivityPublishFolderName) + <_WorkflowActivityPublishFolderName Condition="'$(_WorkflowActivityPublishFolderName)'==''">publish + + <_WorkflowActivityAssemblyName Condition="'$(AssemblyName)'!=''">$(AssemblyName) + <_WorkflowActivityAssemblyName Condition="'$(_WorkflowActivityAssemblyName)'==''">$(MSBuildProjectName) + + + + <_WorkflowActivityAssemblyInfo Include="$(MSBuildProjectFullPath)"> + $(MSBuildProjectDirectory) + $(WorkflowActivityAssemblyId) + $(_WorkflowActivityTargetFramework) + $(_WorkflowActivityPublishFolderName) + $(_WorkflowActivityAssemblyName) + + + + From adb4c1d5261ebc475b296624ecca3bf7933aa587 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 26 Jan 2026 21:35:36 +0100 Subject: [PATCH 34/52] workfow --- .claude/settings.local.json | 5 +- ...ataverse.Solution.WorkflowActivity.targets | 89 ++ ...IS.DevKit.Build.Dataverse.Solution.targets | 4 + .../EnsureWorkflowActivityAssemblyDataXml.cs | 879 ++++++++++++++++++ ...ALXIS.DevKit.Build.Dataverse.Tasks.targets | 1 + 5 files changed, 977 insertions(+), 1 deletion(-) create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets create mode 100644 src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ad3f3a7..a2ab8dd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,10 @@ "permissions": { "allow": [ "mcp__Desktop_Commander__list_directory", - "Bash(git checkout:*)" + "Bash(git checkout:*)", + "Bash(dotnet build:*)", + "Bash(powershell -ExecutionPolicy Bypass -File pack-set.ps1:*)", + "Bash(Select-Object -Last 30)" ] } } diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets new file mode 100644 index 0000000..d9c0115 --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets @@ -0,0 +1,89 @@ + + + + + + BuildWorkflowActivityLibraries;$(BuildDependsOn) + + + + + + + + + + <_WorkflowActivityLibraryProjects Remove="@(_WorkflowActivityLibraryProjects)" /> + <_WorkflowActivityLibraryProjects Include="@(_ProjectTypeFromReferences)" + Condition="'%(ProjectType)'=='WorkflowActivity'" /> + + + + + + + + + + + + + + + <_WorkflowActivityAssemblyInfo Remove="@(_WorkflowActivityAssemblyInfo)" + Condition="'%(_WorkflowActivityAssemblyInfo.WorkflowActivityRootPath)'=='' or '%(_WorkflowActivityAssemblyInfo.AssemblyName)'==''" /> + <_WorkflowActivityAssemblyInfo Update="@(_WorkflowActivityAssemblyInfo)" + Condition="'%(_WorkflowActivityAssemblyInfo.WorkflowActivityAssemblyId)'==''"> + $([System.Guid]::NewGuid().ToString('D')) + + <_WorkflowActivityAssemblyInfo Update="@(_WorkflowActivityAssemblyInfo)"> + $([System.IO.Path]::Combine('%(_WorkflowActivityAssemblyInfo.WorkflowActivityRootPath)','bin','$(Configuration)','%(_WorkflowActivityAssemblyInfo.TargetFramework)','%(_WorkflowActivityAssemblyInfo.PublishFolderName)','%(_WorkflowActivityAssemblyInfo.AssemblyName).dll')) + + + + + + + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index 6f5345e..2ef584a 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -44,6 +44,10 @@ + + + diff --git a/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs new file mode 100644 index 0000000..0070b86 --- /dev/null +++ b/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs @@ -0,0 +1,879 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using System.Collections.Generic; +using System.Threading; +#if NET6_0_OR_GREATER +using System.Runtime.Loader; +#endif + +public sealed class EnsureWorkflowActivityAssemblyDataXml : Task +{ + [Required] + public string WorkflowActivityRootPath { get; set; } = ""; + + [Required] + public string WorkflowActivityAssemblyId { get; set; } = ""; + + public string RepositoryRoot { get; set; } = ""; + + public string Configuration { get; set; } = "Debug"; + public string TargetFramework { get; set; } = "net462"; + public string PublishFolderName { get; set; } = "publish"; + public string WorkflowActivityDllPath { get; set; } = ""; + public string DefaultWorkflowActivityGroupName { get; set; } = ""; + + public override bool Execute() + { + try + { + ValidateRootPath(); + string repoRoot = GetRepositoryRoot(); + + string csprojPath = FindProjectFile(WorkflowActivityRootPath); + string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); + Tuple meta = ReadProjectMetadata(csprojPath, csprojFileName); + string assemblyName = meta.Item1; + + string existingId = FindWorkflowActivityAssemblyId(repoRoot, assemblyName); + string effectiveId = !string.IsNullOrWhiteSpace(existingId) ? existingId : WorkflowActivityAssemblyId; + if (string.IsNullOrWhiteSpace(effectiveId)) + effectiveId = Guid.NewGuid().ToString("D"); + + string normalizedGuid = NormalizeGuid(effectiveId); + WorkflowActivityAssemblyId = normalizedGuid; + + WorkflowActivityProjectInfo info = BuildProjectInfo(repoRoot, normalizedGuid); + + GenerateWorkflowActivityAssemblyData(info, normalizedGuid); + + Log.LogMessage(MessageImportance.High, "WorkflowActivityAssembly data xml generated: " + info.XmlPath); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true, true, null); + return false; + } + } + + private void ValidateRootPath() + { + if (string.IsNullOrWhiteSpace(WorkflowActivityRootPath)) + throw new ArgumentException("WorkflowActivityRootPath is empty"); + + if (!Directory.Exists(WorkflowActivityRootPath)) + throw new DirectoryNotFoundException("WorkflowActivityRootPath not found: " + WorkflowActivityRootPath); + } + + private string GetRepositoryRoot() + { + return !string.IsNullOrWhiteSpace(RepositoryRoot) + ? RepositoryRoot + : Directory.GetCurrentDirectory(); + } + + private WorkflowActivityProjectInfo BuildProjectInfo(string repoRoot, string normalizedGuid) + { + string csprojPath = FindProjectFile(WorkflowActivityRootPath); + string projectDirectory = GetProjectDirectory(csprojPath); + string csprojFileName = Path.GetFileNameWithoutExtension(csprojPath); + + Tuple meta = ReadProjectMetadata(csprojPath, csprojFileName); + string assemblyName = meta.Item1; + string fileVersion = meta.Item2; + + string dllPath = ResolveWorkflowActivityDllPath(assemblyName); + string xmlPath = BuildWorkflowActivityDataXmlPath(repoRoot, assemblyName, normalizedGuid); + + return new WorkflowActivityProjectInfo + { + RepositoryRoot = repoRoot, + ProjectDirectory = projectDirectory, + CsprojFileName = csprojFileName, + AssemblyName = assemblyName, + FileVersion = fileVersion, + XmlPath = xmlPath, + DllPath = dllPath + }; + } + + private void GenerateWorkflowActivityAssemblyData(WorkflowActivityProjectInfo info, string normalizedGuid) + { + if (!File.Exists(info.DllPath)) + throw new FileNotFoundException("Build not found", info.DllPath); + + string tempDllPath = CopyDllToTempFolder(info); + + HashSet probeDirs = BuildProbeDirectories(tempDllPath, info.ProjectDirectory); + ResolveEventHandler handler = CreateAssemblyResolveHandler(probeDirs); + + AppDomain.CurrentDomain.AssemblyResolve += handler; + + try + { + TryAddSdkAssemblyProbe(probeDirs); + + Assembly workflowActivityAssembly = LoadWorkflowActivityAssembly(tempDllPath, info.AssemblyName, probeDirs); + string publicKeyToken = GetPublicKeyToken(workflowActivityAssembly); + + List classList = GetWorkflowActivityClassInfos(workflowActivityAssembly, info.FileVersion); + if (!classList.Any()) + throw new Exception("WorkflowActivities not found in assembly " + info.AssemblyName); + + string xmlDir = EnsureDirectoryForFile(info.XmlPath); + + XmlDocument workflowActivityDoc = CreateWorkflowActivityAssemblyDocument( + info.AssemblyName, + info.FileVersion, + publicKeyToken, + normalizedGuid, + classList, + info.CsprojFileName, + info.XmlPath, + info.RepositoryRoot + ); + + workflowActivityDoc.Save(info.XmlPath); + + UpsertRootComponentIntoSolutionXml( + info.RepositoryRoot, + normalizedGuid, + info.AssemblyName, + info.FileVersion, + publicKeyToken + ); + } + finally + { + AppDomain.CurrentDomain.AssemblyResolve -= handler; + } + } + + private static string FindProjectFile(string rootPath) + { + string csprojPath = Directory.GetFiles(rootPath, "*.csproj").FirstOrDefault(); + if (csprojPath == null) + throw new Exception("csproj not found"); + + return csprojPath; + } + + private static string GetProjectDirectory(string csprojPath) + { + string projectDirectory = Path.GetDirectoryName(csprojPath); + if (string.IsNullOrEmpty(projectDirectory)) + throw new Exception("ProjectDirectory not resolved"); + + return projectDirectory; + } + + private static string FindExistingDataXml(string repoRoot, string assemblyName) + { + string pluginAssembliesRoot = Path.Combine(repoRoot, "PluginAssemblies"); + + if (!Directory.Exists(pluginAssembliesRoot)) + return null; + + string dataXmlFileName = assemblyName + ".dll.data.xml"; + + var files = Directory.GetFiles(pluginAssembliesRoot, dataXmlFileName, SearchOption.AllDirectories); + + return files.FirstOrDefault(); + } + + private static string BuildWorkflowActivityDataXmlPath(string repoRoot, string assemblyName, string normalizedGuid) + { + string existingXmlPath = FindExistingDataXml(repoRoot, assemblyName); + + if (existingXmlPath != null) + { + return existingXmlPath; + } + + return Path.Combine( + repoRoot, + "PluginAssemblies", + assemblyName + ".dll.data.xml" + ); + } + + private string FindWorkflowActivityAssemblyId(string repoRoot, string assemblyName) + { + string existingXmlPath = FindExistingDataXml(repoRoot, assemblyName); + + if (existingXmlPath == null) + return ""; + + var doc = XDocument.Load(existingXmlPath); + var root = doc.Root; + if (root == null) + return ""; + + var idAttr = root.Attribute("PluginAssemblyId"); + return idAttr == null ? "" : idAttr.Value; + } + + private string BuildWorkflowActivityDllPath(string assemblyName) + { + return Path.Combine( + WorkflowActivityRootPath, + "bin", + Configuration, + TargetFramework, + PublishFolderName, + assemblyName + ".dll" + ); + } + + private string ResolveWorkflowActivityDllPath(string assemblyName) + { + if (!string.IsNullOrWhiteSpace(WorkflowActivityDllPath)) + { + var candidate = WorkflowActivityDllPath; + if (!Path.IsPathRooted(candidate)) + candidate = Path.Combine(WorkflowActivityRootPath, candidate); + + return Path.GetFullPath(candidate); + } + + return BuildWorkflowActivityDllPath(assemblyName); + } + + private HashSet BuildProbeDirectories(string dllPath, string projectDirectory) + { + string dllDir = Path.GetDirectoryName(dllPath); + if (string.IsNullOrEmpty(dllDir)) + throw new Exception("dll directory not resolved"); + + var probeDirs = new HashSet(StringComparer.OrdinalIgnoreCase); + probeDirs.Add(dllDir); + probeDirs.Add(Path.Combine(WorkflowActivityRootPath, "bin", Configuration, TargetFramework)); + probeDirs.Add(projectDirectory); + + return probeDirs; + } + + private static ResolveEventHandler CreateAssemblyResolveHandler(HashSet probeDirs) + { + return (sender, args) => + { + string name = null; + try + { + var an = new AssemblyName(args.Name); + name = an.Name; + } + catch { /* ignore */ } + + if (string.IsNullOrWhiteSpace(name)) + return null; + + foreach (var dir in probeDirs) + { + var candidate = Path.Combine(dir, name + ".dll"); + if (File.Exists(candidate)) + { + try + { + var bytes = File.ReadAllBytes(candidate); + return Assembly.Load(bytes); + } + catch { /* ignore */ } + } + } + return null; + }; + } + + private void TryAddSdkAssemblyProbe(HashSet probeDirs) + { + string sdkPath = Path.Combine(WorkflowActivityRootPath, "bin", Configuration, TargetFramework, "Microsoft.Xrm.Sdk.dll"); + + if (!File.Exists(sdkPath)) + return; + + TryLoadAssemblyNoThrow(sdkPath); + + string sdkDir = Path.GetDirectoryName(sdkPath); + + if (!string.IsNullOrEmpty(sdkDir)) + probeDirs.Add(sdkDir); + + // Add .NET Framework reference assemblies for System.Activities + TryAddFrameworkAssemblyProbe(probeDirs); + } + + private void TryAddFrameworkAssemblyProbe(HashSet probeDirs) + { + // Try to find System.Activities in standard .NET Framework locations + string[] possiblePaths = new[] + { + @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319", + @"C:\Windows\Microsoft.NET\Framework\v4.0.30319", + @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2", + @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8", + @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2", + }; + + foreach (var path in possiblePaths) + { + if (Directory.Exists(path)) + { + probeDirs.Add(path); + string systemActivitiesPath = Path.Combine(path, "System.Activities.dll"); + if (File.Exists(systemActivitiesPath)) + { + TryLoadAssemblyNoThrow(systemActivitiesPath); + } + } + } + } + + private static string GetPublicKeyToken(Assembly assembly) + { + byte[] token = assembly.GetName().GetPublicKeyToken(); + + if (token == null || token.Length == 0) + throw new Exception("Build not signed"); + + return BitConverter.ToString(token).Replace("-", "").ToLowerInvariant(); + } + + private List GetWorkflowActivityClassInfos(Assembly assembly, string fileVersion) + { + var result = new List(); + + foreach (var type in GetTypesSafe(assembly)) + { + if (!type.IsClass || !type.IsPublic || type.IsAbstract) + continue; + + if (!InheritsFromByName(type, "System.Activities.CodeActivity")) + continue; + + string fullName = type.FullName; + if (string.IsNullOrWhiteSpace(fullName)) + continue; + + string groupName = GetWorkflowActivityGroupName(type, fileVersion); + string displayName = GetWorkflowActivityDisplayName(type); + + result.Add(new WorkflowActivityTypeInfo + { + FullName = fullName, + GroupName = groupName, + DisplayName = displayName + }); + } + + return result; + } + + private string GetWorkflowActivityGroupName(Type type, string fileVersion) + { + // Try to get from CrmPluginRegistrationAttribute (Group parameter) + foreach (var attr in type.GetCustomAttributesData()) + { + if (attr.AttributeType.Name == "CrmPluginRegistrationAttribute") + { + // Look for Group named argument + foreach (var namedArg in attr.NamedArguments) + { + if (namedArg.MemberName == "Group" && namedArg.TypedValue.Value is string groupValue) + { + if (!string.IsNullOrWhiteSpace(groupValue)) + return groupValue + " (" + fileVersion + ")"; + } + } + } + } + + // Fallback to DefaultWorkflowActivityGroupName or assembly name + string baseName = !string.IsNullOrWhiteSpace(DefaultWorkflowActivityGroupName) + ? DefaultWorkflowActivityGroupName + : type.Assembly.GetName().Name; + + return baseName + " (" + fileVersion + ")"; + } + + private static string GetWorkflowActivityDisplayName(Type type) + { + // Try to get from CrmPluginRegistrationAttribute (Name parameter) + foreach (var attr in type.GetCustomAttributesData()) + { + if (attr.AttributeType.Name == "CrmPluginRegistrationAttribute") + { + // First constructor argument is usually the Name + if (attr.ConstructorArguments.Count > 0) + { + var nameValue = attr.ConstructorArguments[0].Value as string; + if (!string.IsNullOrWhiteSpace(nameValue)) + return nameValue; + } + } + } + + // Fallback to class name + return type.Name; + } + + private static bool InheritsFromByName(Type t, string baseClassName) + { + try + { + Type current = t.BaseType; + while (current != null) + { + // Check by FullName + if (string.Equals(current.FullName, baseClassName, StringComparison.Ordinal)) + return true; + + // Also check by Name only (in case namespace differs) + string simpleClassName = baseClassName; + int lastDot = baseClassName.LastIndexOf('.'); + if (lastDot >= 0) + simpleClassName = baseClassName.Substring(lastDot + 1); + + if (string.Equals(current.Name, simpleClassName, StringComparison.Ordinal)) + return true; + + current = current.BaseType; + } + } + catch + { + // If we can't inspect the type hierarchy, return false + } + return false; + } + + private static string EnsureDirectoryForFile(string filePath) + { + string dir = Path.GetDirectoryName(filePath); + if (string.IsNullOrEmpty(dir)) + throw new Exception("xml directory not resolved"); + + Directory.CreateDirectory(dir); + + return dir; + } + + private static XmlDocument CreateWorkflowActivityAssemblyDocument( + string assemblyName, + string fileVersion, + string publicKeyToken, + string normalizedGuid, + IEnumerable classList, + string csprojFileName, + string existingXmlPath, + string repoRoot) + { + var doc = new XmlDocument(); + var xmlDecl = doc.CreateXmlDeclaration("1.0", "utf-8", null); + doc.AppendChild(xmlDecl); + + XmlElement root = doc.CreateElement("PluginAssembly"); + root.SetAttribute("FullName", BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken)); + root.SetAttribute("PluginAssemblyId", normalizedGuid); + root.SetAttribute("CustomizationLevel", "1"); + root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + doc.AppendChild(root); + + XmlElement isolationMode = doc.CreateElement("IsolationMode"); + isolationMode.InnerText = "2"; + root.AppendChild(isolationMode); + + XmlElement sourceType = doc.CreateElement("SourceType"); + sourceType.InnerText = "0"; + root.AppendChild(sourceType); + + XmlElement introducedVersion = doc.CreateElement("IntroducedVersion"); + introducedVersion.InnerText = "1.0"; + root.AppendChild(introducedVersion); + + XmlElement fileName = doc.CreateElement("FileName"); + fileName.InnerText = BuildRelativeDllPath(existingXmlPath, repoRoot, assemblyName); + root.AppendChild(fileName); + + XmlElement pluginTypes = doc.CreateElement("PluginTypes"); + root.AppendChild(pluginTypes); + + var existingPluginTypes = LoadExistingPluginTypeMap(existingXmlPath, doc); + + var seen = new HashSet(StringComparer.Ordinal); + foreach (var classInfo in classList) + { + string className = classInfo.FullName; + + if (!seen.Add(className)) + continue; + + XmlElement pluginType; + + if (existingPluginTypes.TryGetValue(className, out var existingPluginType)) + { + pluginType = existingPluginType; + } + else + { + pluginType = CreatePluginTypeElement(doc); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", classInfo.DisplayName); + } + + pluginType.SetAttribute( + "AssemblyQualifiedName", + BuildAssemblyQualifiedTypeName(className, assemblyName, fileVersion, publicKeyToken) + ); + + // Ensure Name attribute is set + if (string.IsNullOrWhiteSpace(pluginType.GetAttribute("Name"))) + pluginType.SetAttribute("Name", classInfo.DisplayName); + + // Ensure FriendlyName element exists + var friendlyName = pluginType.SelectSingleNode("FriendlyName") as XmlElement; + if (friendlyName == null) + { + friendlyName = doc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); + } + + // Update or create WorkflowActivityGroupName element + var workflowGroupName = pluginType.SelectSingleNode("WorkflowActivityGroupName") as XmlElement; + if (workflowGroupName == null) + { + workflowGroupName = doc.CreateElement("WorkflowActivityGroupName"); + pluginType.AppendChild(workflowGroupName); + } + workflowGroupName.InnerText = classInfo.GroupName; + + if (string.IsNullOrWhiteSpace(pluginType.GetAttribute("PluginTypeId"))) + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + + pluginTypes.AppendChild(pluginType); + } + + return doc; + } + + private static void UpsertRootComponentIntoSolutionXml( + string repoRoot, + string normalizedGuid, + string assemblyName, + string fileVersion, + string publicKeyToken) + { + var solutionPath = Path.Combine(repoRoot, "Other", "Solution.xml"); + if (!File.Exists(solutionPath)) + throw new FileNotFoundException("Solution.xml not found", solutionPath); + + var doc = new XmlDocument(); + + doc.Load(solutionPath); + + XmlElement rootComponents = doc.SelectSingleNode("//RootComponents") as XmlElement; + if (rootComponents == null) + { + if (doc.DocumentElement == null) + throw new Exception("Solution.xml has no document element"); + + rootComponents = doc.CreateElement("RootComponents"); + doc.DocumentElement.AppendChild(rootComponents); + } + + var desiredIdBraced = "{" + normalizedGuid + "}"; + + XmlElement existing = null; + foreach (XmlNode n in rootComponents.ChildNodes) + { + var el = n as XmlElement; + if (el == null) continue; + if (!string.Equals(el.Name, "RootComponent", StringComparison.Ordinal)) continue; + + var typeAttr = el.GetAttribute("type"); + if (!string.Equals(typeAttr, "91", StringComparison.Ordinal)) continue; + + var idAttr = el.GetAttribute("id"); + if (IsSameGuidBraced(idAttr, desiredIdBraced)) + { + existing = el; + break; + } + } + + XmlElement rc = existing ?? doc.CreateElement("RootComponent"); + rc.SetAttribute("type", "91"); + rc.SetAttribute("id", desiredIdBraced); + rc.SetAttribute("schemaName", BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken)); + rc.SetAttribute("behavior", "0"); + + if (existing == null) + rootComponents.AppendChild(rc); + + doc.Save(solutionPath); + } + + private static bool IsSameGuidBraced(string a, string b) + { + string na = NormalizeGuidBraces(a); + string nb = NormalizeGuidBraces(b); + + return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase); + } + + private static string NormalizeGuidBraces(string s) + { + if (string.IsNullOrWhiteSpace(s)) return ""; + + return s.Trim().Trim('{', '}'); + } + + private static string BuildRelativeDllPath(string xmlPath, string repoRoot, string assemblyName) + { + string xmlDir = Path.GetDirectoryName(xmlPath); + if (string.IsNullOrEmpty(xmlDir)) + return "/PluginAssemblies/" + assemblyName + ".dll"; + + string pluginAssembliesRoot = Path.Combine(repoRoot, "PluginAssemblies"); + string relativePath; + + if (xmlDir.Equals(pluginAssembliesRoot, StringComparison.OrdinalIgnoreCase)) + { + relativePath = "/PluginAssemblies/" + assemblyName + ".dll"; + } + else + { + string subFolder = xmlDir.Substring(pluginAssembliesRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + relativePath = "/PluginAssemblies/" + subFolder.Replace(Path.DirectorySeparatorChar, '/') + "/" + assemblyName + ".dll"; + } + + return relativePath; + } + + private static string BuildAssemblyFullName(string assemblyName, string fileVersion, string publicKeyToken) + { + return assemblyName + ", Version=" + fileVersion + ", Culture=neutral, PublicKeyToken=" + publicKeyToken; + } + + private static string BuildAssemblyQualifiedTypeName(string className, string assemblyName, string fileVersion, string publicKeyToken) + { + return className + ", " + BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken); + } + + private static void TryLoadAssemblyNoThrow(string path) + { + try + { + var bytes = File.ReadAllBytes(path); + Assembly.Load(bytes); + } + catch { /* ignore */ } + } + + private Assembly LoadWorkflowActivityAssembly(string dllPath, string assemblyName, HashSet probeDirs) + { + var alreadyLoaded = FindLoadedAssembly(assemblyName); + if (alreadyLoaded != null) + return alreadyLoaded; + +#if NET6_0_OR_GREATER + try + { + var alc = new AssemblyLoadContext("WorkflowActivityAssembly-" + Guid.NewGuid().ToString("N"), isCollectible: true); + alc.Resolving += (context, name) => + { + foreach (var dir in probeDirs) + { + var candidate = Path.Combine(dir, name.Name + ".dll"); + if (File.Exists(candidate)) + return context.LoadFromAssemblyPath(candidate); + } + return null; + }; + + var bytes = File.ReadAllBytes(dllPath); + var asm = alc.LoadFromStream(new MemoryStream(bytes)); + return asm; + } + catch (FileLoadException) + { + var loaded = FindLoadedAssembly(assemblyName); + if (loaded != null) + return loaded; + throw; + } +#else + try + { + var bytes = File.ReadAllBytes(dllPath); + return Assembly.Load(bytes); + } + catch (FileLoadException) + { + var loaded = FindLoadedAssembly(assemblyName); + if (loaded != null) + return loaded; + throw; + } +#endif + } + + private static Assembly FindLoadedAssembly(string assemblyName) + { + return AppDomain.CurrentDomain + .GetAssemblies() + .FirstOrDefault(a => + { + var name = a.GetName(); + return name != null && string.Equals(name.Name, assemblyName, StringComparison.OrdinalIgnoreCase); + }); + } + + private static string NormalizeGuid(string guidText) + { + if (string.IsNullOrWhiteSpace(guidText)) + throw new ArgumentException("WorkflowActivityAssemblyId is empty"); + + var trimmed = guidText.Trim().Trim('{', '}'); + + Guid g; + if (!Guid.TryParse(trimmed, out g)) + throw new ArgumentException("WorkflowActivityAssemblyId is not a valid GUID: " + guidText); + + return g.ToString("D"); + } + + private static Tuple ReadProjectMetadata(string csprojPath, string fallbackAssemblyName) + { + var xdoc = XDocument.Load(csprojPath); + + string assemblyName = xdoc.Descendants() + .FirstOrDefault(e => e.Name.LocalName == "AssemblyName") + ?.Value; + + string fileVersion = xdoc.Descendants() + .FirstOrDefault(e => e.Name.LocalName == "FileVersion") + ?.Value; + + assemblyName = (assemblyName ?? "").Trim(); + fileVersion = (fileVersion ?? "").Trim(); + + if (string.IsNullOrWhiteSpace(assemblyName)) + assemblyName = fallbackAssemblyName; + + if (string.IsNullOrWhiteSpace(fileVersion)) + fileVersion = "1.0.0.0"; + + return Tuple.Create(assemblyName, fileVersion); + } + + private static IEnumerable GetTypesSafe(Assembly asm) + { + try + { + return asm.GetTypes(); + } + catch (ReflectionTypeLoadException rtle) + { + return rtle.Types.Where(t => t != null).Cast(); + } + } + + private static Dictionary LoadExistingPluginTypeMap(string xmlPath, XmlDocument targetDoc) + { + var result = new Dictionary(StringComparer.Ordinal); + + if (!File.Exists(xmlPath)) + return result; + + var existingDoc = new XmlDocument(); + existingDoc.Load(xmlPath); + + var pluginTypesNode = existingDoc.SelectSingleNode("//PluginAssembly/PluginTypes") as XmlElement; + if (pluginTypesNode == null) + return result; + + foreach (var node in pluginTypesNode.ChildNodes) + { + var el = node as XmlElement; + if (el == null) + continue; + + if (!string.Equals(el.Name, "PluginType", StringComparison.Ordinal)) + continue; + + string className = GetPluginTypeClassName(el); + if (string.IsNullOrWhiteSpace(className)) + continue; + + if (result.ContainsKey(className)) + continue; + + var imported = (XmlElement)targetDoc.ImportNode(el, true); + result[className] = imported; + } + + return result; + } + + private static string GetPluginTypeClassName(XmlElement pluginTypeElement) + { + string aqn = pluginTypeElement.GetAttribute("AssemblyQualifiedName"); + if (string.IsNullOrWhiteSpace(aqn)) + return ""; + + int commaIndex = aqn.IndexOf(','); + if (commaIndex < 0) + return aqn.Trim(); + + return aqn.Substring(0, commaIndex).Trim(); + } + + private static XmlElement CreatePluginTypeElement(XmlDocument doc) + { + return doc.CreateElement("PluginType"); + } + + private string CopyDllToTempFolder(WorkflowActivityProjectInfo info) + { + string tempDir = Path.Combine( + info.RepositoryRoot, + "obj", + Configuration, + TargetFramework, + "Temp" + ); + + Directory.CreateDirectory(tempDir); + + string tempDllPath = Path.Combine(tempDir, info.AssemblyName + ".dll"); + File.Copy(info.DllPath, tempDllPath, true); + + return tempDllPath; + } + + private sealed class WorkflowActivityProjectInfo + { + public string RepositoryRoot { get; set; } = ""; + public string ProjectDirectory { get; set; } = ""; + public string CsprojFileName { get; set; } = ""; + public string AssemblyName { get; set; } = ""; + public string FileVersion { get; set; } = ""; + public string XmlPath { get; set; } = ""; + public string DllPath { get; set; } = ""; + } + + private sealed class WorkflowActivityTypeInfo + { + public string FullName { get; set; } = ""; + public string GroupName { get; set; } = ""; + public string DisplayName { get; set; } = ""; + } +} diff --git a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets index e0661e6..b4df509 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Tasks.targets @@ -26,6 +26,7 @@ + From 7efefd1c8641537dad3284a285149acccdd2f7d0 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 26 Jan 2026 23:37:19 +0100 Subject: [PATCH 35/52] WorkflowActivity targets --- .claude/settings.local.json | 11 ---- .gitignore | 4 +- pack-dataverse-latest-to-local.ps1 | 41 ++++++++++++++ pack-dataverse-selected.ps1 | 21 ++++++++ .../EnsureWorkflowActivityAssemblyDataXml.cs | 54 +++++++++++++++---- ...Kit.Build.Dataverse.WorkflowActivity.props | 1 - 6 files changed, 107 insertions(+), 25 deletions(-) delete mode 100644 .claude/settings.local.json create mode 100644 pack-dataverse-latest-to-local.ps1 create mode 100644 pack-dataverse-selected.ps1 diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index a2ab8dd..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "permissions": { - "allow": [ - "mcp__Desktop_Commander__list_directory", - "Bash(git checkout:*)", - "Bash(dotnet build:*)", - "Bash(powershell -ExecutionPolicy Bypass -File pack-set.ps1:*)", - "Bash(Select-Object -Last 30)" - ] - } -} diff --git a/.gitignore b/.gitignore index 997d0c9..dd320be 100644 --- a/.gitignore +++ b/.gitignore @@ -398,6 +398,4 @@ FodyWeavers.xsd *.sln.iml -pack-dataverse-latest-to-local.ps1 -pack-dataverse-selected.ps1 -Pack-Dataverse.ps1 + diff --git a/pack-dataverse-latest-to-local.ps1 b/pack-dataverse-latest-to-local.ps1 new file mode 100644 index 0000000..88e6437 --- /dev/null +++ b/pack-dataverse-latest-to-local.ps1 @@ -0,0 +1,41 @@ +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +$sourceRoot = Join-Path $repoRoot "src\\Dataverse" +$destination = "C:\\NuGetLocal" + +if (-not (Test-Path -Path $destination)) { + New-Item -Path $destination -ItemType Directory -Force | Out-Null +} + +$packageCandidates = Get-ChildItem -Path $sourceRoot -Directory | ForEach-Object { + $releaseDir = Join-Path $_.FullName "bin\\Release" + if (Test-Path -Path $releaseDir) { + Get-ChildItem -Path $releaseDir -Filter *.nupkg -File -ErrorAction SilentlyContinue + } +} + +$latestPackages = $packageCandidates | Group-Object -Property Name | ForEach-Object { + $_.Group | Sort-Object LastWriteTime -Descending | Select-Object -First 1 +} + +if (-not $latestPackages) { + Write-Host "No .nupkg files found under $sourceRoot." + exit 0 +} + +foreach ($pkg in $latestPackages) { + Copy-Item -Path $pkg.FullName -Destination $destination -Force +} + +Write-Host "Copied $($latestPackages.Count) package(s) to $destination." + +# Stop all .NET Host processes before clearing NuGet cache +$dotnetProcesses = Get-Process -Name "dotnet" -ErrorAction SilentlyContinue +if ($dotnetProcesses) { + Write-Host "Stopping $($dotnetProcesses.Count) .NET Host process(es)..." + $dotnetProcesses | Stop-Process -Force + Start-Sleep -Seconds 1 +} + +dotnet nuget locals all --clear diff --git a/pack-dataverse-selected.ps1 b/pack-dataverse-selected.ps1 new file mode 100644 index 0000000..0ed6fca --- /dev/null +++ b/pack-dataverse-selected.ps1 @@ -0,0 +1,21 @@ +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path + +$projects = @( + "src\\Dataverse\\PDPackage\\TALXIS.DevKit.Build.Dataverse.PdPackage.csproj", + "src\\Dataverse\\Plugin\\TALXIS.DevKit.Build.Dataverse.Plugin.csproj", + "src\\Dataverse\\Solution\\TALXIS.DevKit.Build.Dataverse.Solution.csproj", + "src\\Dataverse\\ScriptLibrary\\TALXIS.DevKit.Build.Dataverse.ScriptLibrary.csproj", + "src\\Dataverse\\Tasks\\TALXIS.DevKit.Build.Dataverse.Tasks.csproj", + "src\\Dataverse\\WorkflowActivity\\TALXIS.DevKit.Build.Dataverse.WorkflowActivity.csproj" +) + +foreach ($project in $projects) { + $projectPath = Join-Path $repoRoot $project + if (-not (Test-Path -Path $projectPath)) { + throw "Project not found: $projectPath" + } + + dotnet pack $projectPath -c Release +} diff --git a/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs index 0070b86..171be29 100644 --- a/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs @@ -312,6 +312,7 @@ private void TryAddSdkAssemblyProbe(HashSet probeDirs) private void TryAddFrameworkAssemblyProbe(HashSet probeDirs) { // Try to find System.Activities in standard .NET Framework locations + // Order matters - try GAC first, then reference assemblies string[] possiblePaths = new[] { @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319", @@ -326,15 +327,38 @@ private void TryAddFrameworkAssemblyProbe(HashSet probeDirs) if (Directory.Exists(path)) { probeDirs.Add(path); + // Force load System.Activities before loading the workflow assembly string systemActivitiesPath = Path.Combine(path, "System.Activities.dll"); if (File.Exists(systemActivitiesPath)) { - TryLoadAssemblyNoThrow(systemActivitiesPath); + ForceLoadAssembly(systemActivitiesPath); + break; // Only load from first found location } } } } + private void ForceLoadAssembly(string path) + { + try + { + // First check if already loaded + var name = AssemblyName.GetAssemblyName(path); + var existing = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => string.Equals(a.GetName().Name, name.Name, StringComparison.OrdinalIgnoreCase)); + + if (existing != null) + return; + + // Load from GAC/framework path - this ensures proper binding + Assembly.LoadFrom(path); + } + catch (Exception ex) + { + Log.LogMessage(MessageImportance.Low, "Failed to load " + path + ": " + ex.Message); + } + } + private static string GetPublicKeyToken(Assembly assembly) { byte[] token = assembly.GetName().GetPublicKeyToken(); @@ -679,9 +703,8 @@ private static void TryLoadAssemblyNoThrow(string path) private Assembly LoadWorkflowActivityAssembly(string dllPath, string assemblyName, HashSet probeDirs) { - var alreadyLoaded = FindLoadedAssembly(assemblyName); - if (alreadyLoaded != null) - return alreadyLoaded; + // Always load fresh copy - don't reuse cached assemblies that may have been loaded + // without proper dependencies (causes intermittent ReflectionTypeLoadException) #if NET6_0_OR_GREATER try @@ -704,6 +727,7 @@ private Assembly LoadWorkflowActivityAssembly(string dllPath, string assemblyNam } catch (FileLoadException) { + // Fallback to already loaded if fresh load fails var loaded = FindLoadedAssembly(assemblyName); if (loaded != null) return loaded; @@ -712,15 +736,25 @@ private Assembly LoadWorkflowActivityAssembly(string dllPath, string assemblyNam #else try { - var bytes = File.ReadAllBytes(dllPath); - return Assembly.Load(bytes); + // For .NET Framework, use Assembly.LoadFile to load into a separate context + // This avoids reusing cached assemblies that may be incomplete + return Assembly.LoadFile(dllPath); } catch (FileLoadException) { - var loaded = FindLoadedAssembly(assemblyName); - if (loaded != null) - return loaded; - throw; + // Fallback to byte array loading + try + { + var bytes = File.ReadAllBytes(dllPath); + return Assembly.Load(bytes); + } + catch + { + var loaded = FindLoadedAssembly(assemblyName); + if (loaded != null) + return loaded; + throw; + } } #endif } diff --git a/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props b/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props index 72d8cf9..7cce676 100644 --- a/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props +++ b/src/Dataverse/WorkflowActivity/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.props @@ -10,6 +10,5 @@ - From 6934b6bb9bcb7d87a68e8baba91975291e1c500c Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Wed, 28 Jan 2026 14:55:08 +0100 Subject: [PATCH 36/52] false --- .../tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets | 2 +- ...XIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets | 2 +- .../msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.props | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets index ce1c484..89e7f1a 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Plugin.targets @@ -71,7 +71,7 @@ TargetFramework="%(_PluginAssemblyInfo.TargetFramework)" PublishFolderName="%(_PluginAssemblyInfo.PublishFolderName)" PluginDllPath="%(_PluginAssemblyInfo.PluginDllPath)" - Condition="'@(_PluginAssemblyInfo)'!='' and '@(_PluginLibraryProjects)'!=''" /> + Condition="'@(_PluginAssemblyInfo)'!='' and '@(_PluginLibraryProjects)'!='' and '$(GeneratePluginAssembly)'=='true'" /> + Condition="'@(_WorkflowActivityAssemblyInfo)'!='' and '@(_WorkflowActivityLibraryProjects)'!='' and '$(GeneratePluginAssembly)'=='true'" /> Solution + true From 81c6b9970b65fd92a743c0c3f5f6ed13935b7b3d Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Thu, 29 Jan 2026 13:14:44 +0100 Subject: [PATCH 37/52] warkflow targetts finished --- .claude/settings.local.json | 8 +++++++ ...LXIS.DevKit.Build.Dataverse.Solution.props | 4 ++++ ...ild.Dataverse.Solution.OverridePAC.targets | 22 ++++++++++++++++- ...ataverse.Solution.WorkflowActivity.targets | 24 ++++++++++++++++--- 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b53ffd4 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(pwsh:*)", + "Bash(dotnet build:*)" + ] + } +} diff --git a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props index a10194c..4a623cf 100644 --- a/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props +++ b/src/Dataverse/Solution/msbuild/build/TALXIS.DevKit.Build.Dataverse.Solution.props @@ -1,4 +1,8 @@ + + Solution + true + \ No newline at end of file diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets index 298e856..51eff2c 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.OverridePAC.targets @@ -19,10 +19,14 @@ <_PluginProjects Remove="@(_PluginProjects)" /> <_PluginProjects Include="@(_ProjectTypeFromReferences)" Condition="'%(ProjectType)'=='Plugin'" /> + <_WorkflowActivityProjects Remove="@(_WorkflowActivityProjects)" /> + <_WorkflowActivityProjects Include="@(_ProjectTypeFromReferences)" + Condition="'%(ProjectType)'=='WorkflowActivity'" /> <_ScriptLibraryProjectsList>;@(_ScriptLibraryProjects->'%(Identity)'); + <_WorkflowActivityProjectsList>;@(_WorkflowActivityProjects->'%(Identity)'); @@ -32,6 +36,8 @@ <_CdsRefs Remove="@(_CdsRefs)" Condition="$([System.String]::Copy('$(_ScriptLibraryProjectsList)').Contains(';%(FullPath);'))" /> + <_CdsRefs Remove="@(_CdsRefs)" + Condition="$([System.String]::Copy('$(_WorkflowActivityProjectsList)').Contains(';%(FullPath);'))" /> + + + + + + + Targets="GetProjectOutputPath" + Condition="'@(_CdsRefs)'!=''"> + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets index d964319..34cb2c3 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.WorkflowActivity.targets @@ -74,16 +74,34 @@ Condition="'@(_WorkflowActivityAssemblyInfo)'!='' and '@(_WorkflowActivityLibraryProjects)'!='' and '$(GeneratePluginAssembly)'=='true'" /> + + + + <_WorkflowActivityDllSource>$([System.IO.Path]::Combine('%(_WorkflowActivityAssemblyInfo.WorkflowActivityRootPath)','bin','$(Configuration)','%(_WorkflowActivityAssemblyInfo.TargetFramework)','%(_WorkflowActivityAssemblyInfo.AssemblyName).dll')) + + + + + + + + Targets="GetProjectOutputPath" + SkipNonexistentTargets="true"> + ItemName="_WorkflowActivityProjectOutputPaths" /> + SourceFileItems="@(_WorkflowActivityProjectOutputPaths)" /> From 8dbfab25138caf1994c2b58c3acbce12eeb6f1ff Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Fri, 30 Jan 2026 00:42:25 +0100 Subject: [PATCH 38/52] Plugin assemblys enshure Tasks update --- .../Tasks/EnsurePluginAssemblyDataXml.cs | 155 +++++++++++++----- .../EnsureWorkflowActivityAssemblyDataXml.cs | 146 ++++++++++++----- 2 files changed, 217 insertions(+), 84 deletions(-) diff --git a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs index 9068010..0a5dab0 100644 --- a/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsurePluginAssemblyDataXml.cs @@ -346,6 +346,94 @@ private static XmlDocument CreatePluginAssemblyDocument( string csprojFileName, string existingXmlPath, string repoRoot) + { + string pluginBaseName = string.IsNullOrEmpty(csprojFileName) ? "" : csprojFileName + ".PluginBase"; + + if (File.Exists(existingXmlPath)) + { + return UpdateExistingPluginAssemblyDocument( + existingXmlPath, classList, pluginBaseName, + assemblyName, fileVersion, publicKeyToken); + } + + return CreateNewPluginAssemblyDocument( + assemblyName, fileVersion, publicKeyToken, normalizedGuid, + classList, pluginBaseName, existingXmlPath, repoRoot); + } + + private static XmlDocument UpdateExistingPluginAssemblyDocument( + string existingXmlPath, + IEnumerable classList, + string pluginBaseName, + string assemblyName, + string fileVersion, + string publicKeyToken) + { + var pluginDoc = new XmlDocument(); + pluginDoc.Load(existingXmlPath); + + var pluginTypesNode = pluginDoc.SelectSingleNode("//PluginAssembly/PluginTypes") as XmlElement; + if (pluginTypesNode == null) + { + var root = pluginDoc.DocumentElement; + if (root == null) + throw new Exception("Existing XML has no document element"); + + pluginTypesNode = pluginDoc.CreateElement("PluginTypes"); + root.AppendChild(pluginTypesNode); + } + + var existingClassNames = new HashSet(StringComparer.Ordinal); + foreach (XmlNode node in pluginTypesNode.ChildNodes) + { + var el = node as XmlElement; + if (el == null || !string.Equals(el.Name, "PluginType", StringComparison.Ordinal)) + continue; + + string className = GetPluginTypeClassName(el); + if (!string.IsNullOrWhiteSpace(className)) + existingClassNames.Add(className); + } + + var seen = new HashSet(StringComparer.Ordinal); + foreach (var className in classList) + { + if (className == pluginBaseName) + continue; + + if (!seen.Add(className)) + continue; + + if (existingClassNames.Contains(className)) + continue; + + XmlElement pluginType = CreatePluginTypeElement(pluginDoc); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", className); + pluginType.SetAttribute( + "AssemblyQualifiedName", + BuildAssemblyQualifiedTypeName(className, assemblyName, fileVersion, publicKeyToken) + ); + + var friendlyName = pluginDoc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); + + pluginTypesNode.AppendChild(pluginType); + } + + return pluginDoc; + } + + private static XmlDocument CreateNewPluginAssemblyDocument( + string assemblyName, + string fileVersion, + string publicKeyToken, + string normalizedGuid, + IEnumerable classList, + string pluginBaseName, + string xmlPath, + string repoRoot) { var pluginDoc = new XmlDocument(); var xmlDecl = pluginDoc.CreateXmlDeclaration("1.0", "utf-8", null); @@ -367,16 +455,12 @@ private static XmlDocument CreatePluginAssemblyDocument( root.AppendChild(sourceType); XmlElement fileName = pluginDoc.CreateElement("FileName"); - fileName.InnerText = BuildRelativeDllPath(existingXmlPath, repoRoot, assemblyName); + fileName.InnerText = BuildRelativeDllPath(xmlPath, repoRoot, assemblyName); root.AppendChild(fileName); XmlElement pluginTypes = pluginDoc.CreateElement("PluginTypes"); root.AppendChild(pluginTypes); - var existingPluginTypes = LoadExistingPluginTypeMap(existingXmlPath, pluginDoc); - - string pluginBaseName = string.IsNullOrEmpty(csprojFileName) ? "" : csprojFileName + ".PluginBase"; - var seen = new HashSet(StringComparer.Ordinal); foreach (var className in classList) { @@ -386,35 +470,17 @@ private static XmlDocument CreatePluginAssemblyDocument( if (!seen.Add(className)) continue; - XmlElement pluginType; - - if (existingPluginTypes.TryGetValue(className, out var existingPluginType)) - { - pluginType = existingPluginType; - } - else - { - pluginType = CreatePluginTypeElement(pluginDoc); - pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); - pluginType.SetAttribute("Name", className); - } - + XmlElement pluginType = CreatePluginTypeElement(pluginDoc); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", className); pluginType.SetAttribute( "AssemblyQualifiedName", BuildAssemblyQualifiedTypeName(className, assemblyName, fileVersion, publicKeyToken) ); - pluginType.SetAttribute("Name", className); - - var friendlyName = pluginType.SelectSingleNode("FriendlyName") as XmlElement; - if (friendlyName == null) - { - friendlyName = pluginDoc.CreateElement("FriendlyName"); - friendlyName.InnerText = Guid.NewGuid().ToString("D"); - pluginType.AppendChild(friendlyName); - } - if (string.IsNullOrWhiteSpace(pluginType.GetAttribute("PluginTypeId"))) - pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + var friendlyName = pluginDoc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); pluginTypes.AppendChild(pluginType); } @@ -467,14 +533,18 @@ private static void UpsertRootComponentIntoSolutionXml( } } - XmlElement rc = existing ?? doc.CreateElement("RootComponent"); + if (existing != null) + { + // Assembly already registered in Solution.xml — do not modify + return; + } + + XmlElement rc = doc.CreateElement("RootComponent"); rc.SetAttribute("type", "91"); rc.SetAttribute("id", desiredIdBraced); rc.SetAttribute("schemaName", BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken)); rc.SetAttribute("behavior", "0"); - - if (existing == null) - rootComponents.AppendChild(rc); + rootComponents.AppendChild(rc); doc.Save(solutionPath); } @@ -699,19 +769,20 @@ private static Dictionary LoadExistingPluginTypeMap(string x private static string GetPluginTypeClassName(XmlElement pluginTypeElement) { + string aqn = pluginTypeElement.GetAttribute("AssemblyQualifiedName"); + if (!string.IsNullOrWhiteSpace(aqn)) + { + int commaIndex = aqn.IndexOf(','); + if (commaIndex < 0) + return aqn.Trim(); + return aqn.Substring(0, commaIndex).Trim(); + } + string nameAttr = pluginTypeElement.GetAttribute("Name"); if (!string.IsNullOrWhiteSpace(nameAttr)) return nameAttr.Trim(); - string aqn = pluginTypeElement.GetAttribute("AssemblyQualifiedName"); - if (string.IsNullOrWhiteSpace(aqn)) - return ""; - - int commaIndex = aqn.IndexOf(','); - if (commaIndex < 0) - return aqn.Trim(); - - return aqn.Substring(0, commaIndex).Trim(); + return ""; } private static XmlElement CreatePluginTypeElement(XmlDocument doc) diff --git a/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs b/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs index 171be29..d26b398 100644 --- a/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs +++ b/src/Dataverse/Tasks/Tasks/EnsureWorkflowActivityAssemblyDataXml.cs @@ -497,6 +497,93 @@ private static XmlDocument CreateWorkflowActivityAssemblyDocument( string csprojFileName, string existingXmlPath, string repoRoot) + { + if (File.Exists(existingXmlPath)) + { + return UpdateExistingWorkflowActivityDocument( + existingXmlPath, classList, + assemblyName, fileVersion, publicKeyToken); + } + + return CreateNewWorkflowActivityDocument( + assemblyName, fileVersion, publicKeyToken, normalizedGuid, + classList, existingXmlPath, repoRoot); + } + + private static XmlDocument UpdateExistingWorkflowActivityDocument( + string existingXmlPath, + IEnumerable classList, + string assemblyName, + string fileVersion, + string publicKeyToken) + { + var doc = new XmlDocument(); + doc.Load(existingXmlPath); + + var pluginTypesNode = doc.SelectSingleNode("//PluginAssembly/PluginTypes") as XmlElement; + if (pluginTypesNode == null) + { + var root = doc.DocumentElement; + if (root == null) + throw new Exception("Existing XML has no document element"); + + pluginTypesNode = doc.CreateElement("PluginTypes"); + root.AppendChild(pluginTypesNode); + } + + var existingClassNames = new HashSet(StringComparer.Ordinal); + foreach (XmlNode node in pluginTypesNode.ChildNodes) + { + var el = node as XmlElement; + if (el == null || !string.Equals(el.Name, "PluginType", StringComparison.Ordinal)) + continue; + + string className = GetPluginTypeClassName(el); + if (!string.IsNullOrWhiteSpace(className)) + existingClassNames.Add(className); + } + + var seen = new HashSet(StringComparer.Ordinal); + foreach (var classInfo in classList) + { + string className = classInfo.FullName; + + if (!seen.Add(className)) + continue; + + if (existingClassNames.Contains(className)) + continue; + + XmlElement pluginType = CreatePluginTypeElement(doc); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", classInfo.DisplayName); + pluginType.SetAttribute( + "AssemblyQualifiedName", + BuildAssemblyQualifiedTypeName(className, assemblyName, fileVersion, publicKeyToken) + ); + + var friendlyName = doc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); + + var workflowGroupName = doc.CreateElement("WorkflowActivityGroupName"); + workflowGroupName.InnerText = classInfo.GroupName; + pluginType.AppendChild(workflowGroupName); + + pluginTypesNode.AppendChild(pluginType); + } + + return doc; + } + + private static XmlDocument CreateNewWorkflowActivityDocument( + string assemblyName, + string fileVersion, + string publicKeyToken, + string normalizedGuid, + IEnumerable classList, + string xmlPath, + string repoRoot) { var doc = new XmlDocument(); var xmlDecl = doc.CreateXmlDeclaration("1.0", "utf-8", null); @@ -522,14 +609,12 @@ private static XmlDocument CreateWorkflowActivityAssemblyDocument( root.AppendChild(introducedVersion); XmlElement fileName = doc.CreateElement("FileName"); - fileName.InnerText = BuildRelativeDllPath(existingXmlPath, repoRoot, assemblyName); + fileName.InnerText = BuildRelativeDllPath(xmlPath, repoRoot, assemblyName); root.AppendChild(fileName); XmlElement pluginTypes = doc.CreateElement("PluginTypes"); root.AppendChild(pluginTypes); - var existingPluginTypes = LoadExistingPluginTypeMap(existingXmlPath, doc); - var seen = new HashSet(StringComparer.Ordinal); foreach (var classInfo in classList) { @@ -538,48 +623,21 @@ private static XmlDocument CreateWorkflowActivityAssemblyDocument( if (!seen.Add(className)) continue; - XmlElement pluginType; - - if (existingPluginTypes.TryGetValue(className, out var existingPluginType)) - { - pluginType = existingPluginType; - } - else - { - pluginType = CreatePluginTypeElement(doc); - pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); - pluginType.SetAttribute("Name", classInfo.DisplayName); - } - + XmlElement pluginType = CreatePluginTypeElement(doc); + pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.SetAttribute("Name", classInfo.DisplayName); pluginType.SetAttribute( "AssemblyQualifiedName", BuildAssemblyQualifiedTypeName(className, assemblyName, fileVersion, publicKeyToken) ); - // Ensure Name attribute is set - if (string.IsNullOrWhiteSpace(pluginType.GetAttribute("Name"))) - pluginType.SetAttribute("Name", classInfo.DisplayName); - - // Ensure FriendlyName element exists - var friendlyName = pluginType.SelectSingleNode("FriendlyName") as XmlElement; - if (friendlyName == null) - { - friendlyName = doc.CreateElement("FriendlyName"); - friendlyName.InnerText = Guid.NewGuid().ToString("D"); - pluginType.AppendChild(friendlyName); - } + var friendlyName = doc.CreateElement("FriendlyName"); + friendlyName.InnerText = Guid.NewGuid().ToString("D"); + pluginType.AppendChild(friendlyName); - // Update or create WorkflowActivityGroupName element - var workflowGroupName = pluginType.SelectSingleNode("WorkflowActivityGroupName") as XmlElement; - if (workflowGroupName == null) - { - workflowGroupName = doc.CreateElement("WorkflowActivityGroupName"); - pluginType.AppendChild(workflowGroupName); - } + var workflowGroupName = doc.CreateElement("WorkflowActivityGroupName"); workflowGroupName.InnerText = classInfo.GroupName; - - if (string.IsNullOrWhiteSpace(pluginType.GetAttribute("PluginTypeId"))) - pluginType.SetAttribute("PluginTypeId", Guid.NewGuid().ToString("D")); + pluginType.AppendChild(workflowGroupName); pluginTypes.AppendChild(pluginType); } @@ -632,14 +690,18 @@ private static void UpsertRootComponentIntoSolutionXml( } } - XmlElement rc = existing ?? doc.CreateElement("RootComponent"); + if (existing != null) + { + // Assembly already registered in Solution.xml — do not modify + return; + } + + XmlElement rc = doc.CreateElement("RootComponent"); rc.SetAttribute("type", "91"); rc.SetAttribute("id", desiredIdBraced); rc.SetAttribute("schemaName", BuildAssemblyFullName(assemblyName, fileVersion, publicKeyToken)); rc.SetAttribute("behavior", "0"); - - if (existing == null) - rootComponents.AppendChild(rc); + rootComponents.AppendChild(rc); doc.Save(solutionPath); } From 1343d886d3286e8fde02013a18930abdd7c6f224 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 2 Feb 2026 12:58:37 +0100 Subject: [PATCH 39/52] .resx support added --- .../tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index 2ef584a..d599815 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -5,6 +5,13 @@ Solution + + + %(Filename) + %(ManifestResourceName) + + + <_ProjectType Include="$(MSBuildProjectFullPath)"> From 246726853ca656d65e0084675c0e17e5d7059761 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 2 Feb 2026 15:50:21 +0100 Subject: [PATCH 40/52] Solutions to nuget packages --- ....DevKit.Build.Dataverse.CmtPackage.targets | 5 ++-- ...ild.Dataverse.Solution.Pack.consumer.props | 9 +++++++ ...vKit.Build.Dataverse.Solution.Pack.targets | 24 +++++++++++++++++++ ...IS.DevKit.Build.Dataverse.Solution.targets | 4 ++++ .../Targets/GenerateVersionNumber.targets | 3 +++ 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.consumer.props create mode 100644 src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets index 34fa33d..c7b14b3 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.targets @@ -84,7 +84,7 @@ AfterTargets="Build" DependsOnTargets="TalxisDiscoverCmtPackages"> - + <_CmtPackageZips Include="@(_CmtPackageDirs)"> @@ -97,6 +97,7 @@ + Overwrite="true" + Condition="'@(_CmtPackageZips)'!=''" /> diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.consumer.props b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.consumer.props new file mode 100644 index 0000000..4d0025c --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.consumer.props @@ -0,0 +1,9 @@ + + + + + Solution + PdSolution + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets new file mode 100644 index 0000000..9c191c5 --- /dev/null +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets @@ -0,0 +1,24 @@ + + + + + true + false + true + true + + + + + + + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets index d599815..f65a907 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.targets @@ -59,4 +59,8 @@ + + + diff --git a/src/Dataverse/Tasks/msbuild/tasks/Targets/GenerateVersionNumber.targets b/src/Dataverse/Tasks/msbuild/tasks/Targets/GenerateVersionNumber.targets index 53e9e59..a53a7f1 100644 --- a/src/Dataverse/Tasks/msbuild/tasks/Targets/GenerateVersionNumber.targets +++ b/src/Dataverse/Tasks/msbuild/tasks/Targets/GenerateVersionNumber.targets @@ -1,4 +1,7 @@ + + 0.0.20000.0 + From 2831322d9d8121d8815d90fe8a398d012858dcb7 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 2 Feb 2026 19:44:04 +0100 Subject: [PATCH 41/52] PDPackages to nuget packages --- ...Kit.Build.Dataverse.PdPackage.Pack.targets | 21 +++++++++++++++++++ ...S.DevKit.Build.Dataverse.PdPackage.targets | 4 ++++ 2 files changed, 25 insertions(+) create mode 100644 src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets new file mode 100644 index 0000000..570ed32 --- /dev/null +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets @@ -0,0 +1,21 @@ + + + + + true + false + true + true + + + + + + + + + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index 409c8da..30a2cf6 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -48,4 +48,8 @@ Condition="'$(GeneratePdPackageOnBuild)'=='true'"> + + + From b0010a5239023cc83123b2961c3c96ba737a28ad Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 2 Feb 2026 21:02:48 +0100 Subject: [PATCH 42/52] ScriptLibrary update --- src/Dataverse/ScriptLibrary/README.md | 2 +- ...DevKit.Build.Dataverse.ScriptLibrary.props | 4 +-- ...vKit.Build.Dataverse.ScriptLibrary.targets | 11 ++++---- ...Dataverse.Solution.ScriptLibraries.targets | 28 +++++++++++++------ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/Dataverse/ScriptLibrary/README.md b/src/Dataverse/ScriptLibrary/README.md index e346052..366d0ea 100644 --- a/src/Dataverse/ScriptLibrary/README.md +++ b/src/Dataverse/ScriptLibrary/README.md @@ -6,6 +6,6 @@ MSBuild targets for Dataverse ScriptLibrary projects. - `ProjectType` (default `ScriptLibrary`): marks the project for reference discovery. - `RunNodeBuild` (default `false`): runs `npm install` and `npm run build` in `TypeScriptDir`. - `TypeScriptDir` (default `$(MSBuildProjectDirectory)\TS`): folder containing the TypeScript project. -- `ScriptLibraryMainFile` (default `$(MSBuildProjectDirectory)\TS\build\main.js`): main script file used by consuming targets. +- `ScriptLibraryMainFile` : main script file used by consuming targets. - `LangVersion` (default `latest`): C# language version for the project. - `GenerateAssemblyInfo` (default `false`): disables auto-generated assembly info. diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props index 19359f0..68fa309 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.props @@ -2,9 +2,7 @@ ScriptLibrary main - - $(MSBuildProjectDirectory)\TS\build\$(ScriptLibraryName).js - + $(MSBuildProjectDirectory)\TS\build\$(ScriptLibraryName).js latest false diff --git a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets index 272d1b9..221c292 100644 --- a/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets +++ b/src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets @@ -45,18 +45,17 @@ DependsOnTargets="Build" Returns="@(_ScriptLibraryOutputs)"> - <_ScriptLibraryOutputs Include="$(ScriptLibraryMainFile)" /> + <_ScriptLibraryOutputs Include="$(TargetDir)$(ScriptLibraryName).js" /> + AfterTargets="Build"> - <_ScriptLibraryMainFile Include="$(ScriptLibraryMainFile)" /> + <_ScriptLibraryMainFile Include="$(TypeScriptDir)\**\$(ScriptLibraryName).js" /> - - + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets index 89455fa..b670f05 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.ScriptLibraries.targets @@ -72,14 +72,6 @@ - - - - <_ScriptFilesMissingDataXml Include="@(_ScriptFilesToCopy)" Condition="!Exists('%(DataXmlFile)')"> @@ -103,4 +95,24 @@ /> + + + + <_MetadataWebResourcesDir>$(SolutionPackagerMetadataWorkingDirectory)\WebResources\ + + + + + + + + + From 86b88f86a9e37f10d8cb9ad6db3a951180d0d35e Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 2 Feb 2026 22:06:49 +0100 Subject: [PATCH 43/52] Script library build update --- .claude/settings.local.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index b53ffd4..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(pwsh:*)", - "Bash(dotnet build:*)" - ] - } -} From 0cb6b418385dd3031bd2c8e01a87b87c53c8f5b6 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Wed, 4 Feb 2026 11:32:27 +0100 Subject: [PATCH 44/52] pacjaging update --- pack-dataverse-selected.ps1 | 2 +- ...vKit.Build.Dataverse.PdPackage.Pack.targets | 14 ++++++++++++++ ...evKit.Build.Dataverse.Solution.Pack.targets | 14 ++++++++++++++ .../TALXIS.DevKit.Build.Dataverse.Tasks.csproj | 18 +++++++++++++----- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/pack-dataverse-selected.ps1 b/pack-dataverse-selected.ps1 index 0ed6fca..b6456c4 100644 --- a/pack-dataverse-selected.ps1 +++ b/pack-dataverse-selected.ps1 @@ -17,5 +17,5 @@ foreach ($project in $projects) { throw "Project not found: $projectPath" } - dotnet pack $projectPath -c Release + dotnet build $projectPath -c Release && dotnet pack $projectPath -c Release } diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets index 570ed32..7dd4756 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets @@ -2,10 +2,17 @@ + true false true true + pp-pdpackage + true + + true + false + false + + + + diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets index 9c191c5..671d171 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets @@ -2,10 +2,17 @@ + true false true true + pp-solution + true + + true + false + false + + + + diff --git a/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj b/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj index 838a3c9..18903f6 100644 --- a/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj +++ b/src/Dataverse/Tasks/TALXIS.DevKit.Build.Dataverse.Tasks.csproj @@ -21,8 +21,6 @@ false true true - - bin/$(Configuration) @@ -54,9 +52,19 @@ - - - + + + + $(TargetsForTfmSpecificContentInPackage);_AddTasksDllToPackage + + + + + + tasks/$(TargetFramework) + + + From 37cd2864751e18d8c8cd2ec69f778a748b3b7085 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 9 Feb 2026 12:44:43 +0100 Subject: [PATCH 45/52] Readme update --- pack-dataverse-latest-to-local.ps1 | 2 +- src/Dataverse/PDPackage/README.md | 118 ++++++++++++++++++----- src/Dataverse/Pcf/README.md | 47 ++++++++- src/Dataverse/Plugin/README.md | 61 ++++++++++-- src/Dataverse/ScriptLibrary/README.md | 70 ++++++++++++-- src/Dataverse/Sdk/README.md | 49 ++++++++-- src/Dataverse/Solution/README.md | 113 ++++++++++++++++++---- src/Dataverse/Tasks/README.md | 77 +++++++++++++-- src/Dataverse/WorkflowActivity/README.md | 61 ++++++++++-- 9 files changed, 504 insertions(+), 94 deletions(-) diff --git a/pack-dataverse-latest-to-local.ps1 b/pack-dataverse-latest-to-local.ps1 index 88e6437..75c86f2 100644 --- a/pack-dataverse-latest-to-local.ps1 +++ b/pack-dataverse-latest-to-local.ps1 @@ -2,7 +2,7 @@ $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $sourceRoot = Join-Path $repoRoot "src\\Dataverse" -$destination = "C:\\NuGetLocal" +$destination = "C:\Dev\NuGet\LocalPackages" if (-not (Test-Path -Path $destination)) { New-Item -Path $destination -ItemType Directory -Force | Out-Null diff --git a/src/Dataverse/PDPackage/README.md b/src/Dataverse/PDPackage/README.md index d796afc..2b04224 100644 --- a/src/Dataverse/PDPackage/README.md +++ b/src/Dataverse/PDPackage/README.md @@ -1,32 +1,98 @@ # TALXIS.DevKit.Build.Dataverse.PdPackage -MSBuild targets for Dataverse PDPackage projects. +MSBuild integration for Power Platform Package Deployer (PD) packages. Wraps `Microsoft.PowerApps.MSBuild.PDPackage`, adds ILRepack-based assembly merging for the deployment package DLL, and provides Configuration Migration Tool (CMT) package discovery, metadata merging, and zipping. + +## Installation + +```xml + +``` + +Or use the SDK approach: + +```xml + + + PdPackage + + +``` + +## How It Works + +### Microsoft PDPackage import + +Props and targets from `Microsoft.PowerApps.MSBuild.PDPackage` are imported automatically. The version is controlled by `PdPackageMsBuildVersion`. + +### Project reference filtering + +`_DetectPdProjectReferenceTypes` probes all `ProjectReference` items for `GetProjectType`. Solution-type references have `ReferenceOutputAssembly` set to `false` so their DLLs are not included in the package output. + +### ILRepack + +`DataverseILRepack` (runs after `Build`) merges all non-Microsoft DLLs (excluding reference assemblies and `Newtonsoft.Json`) into the main output assembly using ILRepack.exe. Can be disabled with `DataversePackageRunILRepack=false` or `SkipPackageILRepack=true`. + +### CMT package discovery + +`TalxisDiscoverCmtPackages` scans for folders containing `[Content_Types].xml` with sibling `data.xml` and `data_schema.xml`. Supports include/exclude filtering via `IncludedCmtPackages`/`ExcludedCmtPackages`. + +### CMT package zipping + +`TalxisZipCmtPackages` (runs after `Build`) zips each discovered CMT package directory into `CmtPackageOutputDir`. + +### CMT metadata merging + +`TalxisPrepareCmtPackageMetadata` merges `data.xml` and `data_schema.xml` from all CMT packages into a single combined package, generates `[Content_Types].xml`, zips it, and appends a reference to `ImportConfig.xml`. + +### NuGet packing + +`dotnet pack` produces a `.nupkg` with the `.pdpkg.zip`. + +## MSBuild Properties -## MSBuild properties ### PDPackage -- `PdPackageMsBuildVersion` (default `1.50.1`): version of `Microsoft.PowerApps.MSBuild.PDPackage` imported by the package. -- `GeneratePdPackageOnBuild` (default `true`): runs `GeneratePdPackage` after build/publish. + +| Property | Default | Description | +|----------|---------|-------------| +| `PdPackageMsBuildVersion` | `1.50.1` | Version of `Microsoft.PowerApps.MSBuild.PDPackage` imported by the package. | +| `GeneratePdPackageOnBuild` | `true` | Runs `GeneratePdPackage` after build/publish. | ### ILRepack -- `DataversePackageRunILRepack` (default `true`): runs ILRepack after build. -- `SkipPackageILRepack`: set to `true` to skip ILRepack. -- `ILRepackVersion` (default `2.0.18`): ILRepack package version. -- `ILRepackExe` (default `$(NuGetPackageRoot)ilrepack\\tools\ILRepack.exe`): path to ILRepack.exe. -- `ReferencedAssembliesDir` (default `$(TargetDir)`): directory scanned for assemblies to merge. -- `DataversePackageILRepackKeyFile`: strong-name key file passed to ILRepack `/keyfile`. - -### Cmt packages -- `CmtPackageSearchRoot` (default project directory): root folder scanned for Cmt packages. -- `CmtPackageOutputDir` (default `\CmtPackages` or `\CmtPackages`): output folder for zipped Cmt packages. -- `IncludedCmtPackages`: semicolon-separated package names to include (case-insensitive). -- `ExcludedCmtPackages`: semicolon-separated package names to exclude (case-insensitive). - -### Cmt metadata merge -- `CmtPackageName`: name injected into merged metadata. -- `CmtMetadataOutputDir` (default `$(IntermediateOutputPath)\CmtMetadata\`): temp folder for merged metadata. -- `CmtMetadataZipName` (default `$(CmtPackageName)` or `MainCmtPackage`): name of the merged metadata zip. -- `CmtMetadataLcid`: LCID used when appending metadata to ImportConfig. -- `CmtMetadataUserMapFileName`: optional user map file name used in ImportConfig. -- `CmtImportConfigPath`: path to ImportConfig.xml used for metadata injection. -- `AutoGeneratePdImportConfig`: when `true`, uses the generated ImportConfig instead of copying a project file. -- `PdAssetsTargetFolder`: target folder under publish assets for the merged metadata zip. + +| Property | Default | Description | +|----------|---------|-------------| +| `DataversePackageRunILRepack` | `true` | Runs ILRepack after build. | +| `SkipPackageILRepack` | _(none)_ | Set to `true` to skip ILRepack. | +| `ILRepackVersion` | `2.0.18` | ILRepack NuGet package version. | +| `ILRepackExe` | `$(NuGetPackageRoot)ilrepack\$(ILRepackVersion)\tools\ILRepack.exe` | Path to ILRepack.exe. | +| `ReferencedAssembliesDir` | `$(TargetDir)` | Directory scanned for assemblies to merge. | +| `DataversePackageILRepackKeyFile` | _(none)_ | Strong-name key file passed to ILRepack `/keyfile`. | + +### CMT packages + +| Property | Default | Description | +|----------|---------|-------------| +| `CmtPackageSearchRoot` | Project directory | Root folder scanned for CMT packages. | +| `CmtPackageOutputDir` | `$(TargetDir)\CmtPackages` | Output folder for zipped CMT packages. | +| `IncludedCmtPackages` | _(none)_ | Semicolon-separated package names to include (case-insensitive). | +| `ExcludedCmtPackages` | _(none)_ | Semicolon-separated package names to exclude (case-insensitive). | + +### CMT metadata merge + +| Property | Default | Description | +|----------|---------|-------------| +| `CmtPackageName` | _(none)_ | Name injected into merged metadata. | +| `CmtMetadataOutputDir` | `$(IntermediateOutputPath)\CmtMetadata\$(CmtMetadataZipName)` | Temp folder for merged metadata. | +| `CmtMetadataZipName` | `$(CmtPackageName)` or `MainCmtPackage` | Name of the merged metadata zip. | +| `CmtMetadataLcid` | _(none)_ | LCID used when appending metadata to ImportConfig. | +| `CmtMetadataUserMapFileName` | _(none)_ | Optional user map file name used in ImportConfig. | +| `CmtImportConfigPath` | _(none)_ | Path to ImportConfig.xml used for metadata injection. | +| `AutoGeneratePdImportConfig` | _(none)_ | When `true`, uses the generated ImportConfig instead of copying a project file. | +| `PdAssetsTargetFolder` | _(none)_ | Target folder under publish assets for the merged metadata zip. | + +## Related Packages + +- **Depends on**: `Microsoft.PowerApps.MSBuild.PDPackage`, `ilrepack` +- **Typically references**: `TALXIS.DevKit.Build.Dataverse.Solution` projects + + diff --git a/src/Dataverse/Pcf/README.md b/src/Dataverse/Pcf/README.md index f3ae650..f38f146 100644 --- a/src/Dataverse/Pcf/README.md +++ b/src/Dataverse/Pcf/README.md @@ -1,8 +1,45 @@ # TALXIS.DevKit.Build.Dataverse.Pcf -MSBuild targets for Dataverse PCF projects. +MSBuild integration for Power Apps Component Framework (PCF) projects. Wraps `Microsoft.PowerApps.MSBuild.Pcf` and adds automatic Git-based version number generation that is applied to the PCF control before build. + +## Installation + +```xml + +``` + +Or use the SDK approach: + +```xml + + + Pcf + + +``` + +## How It Works + +The package imports `Microsoft.PowerApps.MSBuild.Pcf` targets as a NuGet dependency and layers versioning on top. + +The `TalxisBeforeBuild` target runs before `BeforeBuild` and executes two steps in sequence: + +1. **GenerateVersionNumber** (from `Tasks`) -- reads the `Version` property, inspects the current Git branch against `ApplyToBranches` rules, and produces a full four-part version number. +2. **ApplyPluginVersionNumber** -- writes the generated version to `AssemblyVersion`, `FileVersion`, `Version`, and `PackageVersion`. + +The Microsoft PCF targets version is controlled by `MicrosoftPowerAppsTargetsVersion` from `Directory.Build.props`. + +## MSBuild Properties + +| Property | Default | Description | +|----------|---------|-------------| +| `Version` | _(required)_ | Base version (`Major.Minor`); used by Git versioning to produce the full version. | +| `ApplyToBranches` | _(none)_ | Semicolon-separated branch rules (e.g. `master;hotfix;develop:1;pr:3;feature/*:2`). | +| `LocalBranchBuildVersionNumber` | `0.0.0.1` | Fallback version when the current branch does not match `ApplyToBranches`. | + +## Related Packages + +- **Depends on**: `TALXIS.DevKit.Build.Dataverse.Tasks`, `Microsoft.PowerApps.MSBuild.Pcf` +- **Consumed by**: `TALXIS.DevKit.Build.Dataverse.Solution` projects via `ProjectReference` + -## MSBuild properties -- `Version` (required): base version; used for Git versioning and applied to assembly/package versions. -- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). -- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. diff --git a/src/Dataverse/Plugin/README.md b/src/Dataverse/Plugin/README.md index b791c2e..d153e96 100644 --- a/src/Dataverse/Plugin/README.md +++ b/src/Dataverse/Plugin/README.md @@ -1,12 +1,53 @@ # TALXIS.DevKit.Build.Dataverse.Plugin -MSBuild targets for Dataverse plugin projects. - -## MSBuild properties -- `ProjectType` (default `Plugin`): marks the project as a plugin for reference discovery. -- `Version` (required): base version; major/minor are used for Git versioning and applied to assembly/package versions. -- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). -- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. -- `PluginTargetFramework` (default `$(TargetFramework)` when set, otherwise `net462`): target framework used to locate the compiled plugin DLL. -- `PluginPublishFolderName` (default `publish`): publish folder name under `bin\\\`. -- `PluginAssemblyId`: explicit GUID for the plugin assembly metadata; if empty, a new GUID is generated. +MSBuild integration for Dataverse plugin assembly projects. Configures Visual Studio project type GUIDs for CRM plugin development, brings in `Microsoft.CrmSdk.CoreAssemblies` and `Microsoft.PowerApps.MSBuild.Plugin`, applies automatic Git-based versioning, and exposes metadata targets that allow Solution projects to discover and integrate plugin assemblies during build. + +## Installation + +```xml + +``` + +Or use the SDK approach: + +```xml + + + Plugin + + +``` + +## How It Works + +The package sets `ProjectType` to `Plugin` and configures `ProjectTypeGuids` for CRM plugin recognition in Visual Studio. + +### Build-time targets + +- **TalxisBeforeBuild** (runs before `BeforeBuild`) -- executes `GenerateVersionNumber` followed by `ApplyPluginVersionNumber` to set `AssemblyVersion`, `FileVersion`, `Version`, and `PackageVersion` from Git. + +### Integration targets + +These targets are called by `TALXIS.DevKit.Build.Dataverse.Solution` when it discovers this project via `ProjectReference`: + +- **GetProjectType** -- returns `Plugin` so the Solution build knows how to handle this reference. +- **GetPluginAssemblyInfo** -- returns `PluginRootPath`, `PluginAssemblyId`, `TargetFramework`, `PublishFolderName`, and `AssemblyName` for automatic plugin assembly metadata generation in the solution. + +## MSBuild Properties + +| Property | Default | Description | +|----------|---------|-------------| +| `ProjectType` | `Plugin` | Marks the project as a plugin for reference discovery. | +| `Version` | _(required)_ | Base version; major/minor are used for Git versioning. | +| `ApplyToBranches` | _(none)_ | Semicolon-separated branch rules (e.g. `master;hotfix;develop:1;pr:3;feature/*:2`). | +| `LocalBranchBuildVersionNumber` | `0.0.0.1` | Fallback version when Git versioning is not applied. | +| `PluginTargetFramework` | `$(TargetFramework)` or `net462` | Target framework used to locate the compiled plugin DLL. | +| `PluginPublishFolderName` | `publish` | Publish folder name under `bin\\\`. | +| `PluginAssemblyId` | _(auto-generated)_ | Explicit GUID for the plugin assembly metadata; a new GUID is generated if empty. | + +## Related Packages + +- **Depends on**: `TALXIS.DevKit.Build.Dataverse.Tasks`, `Microsoft.PowerApps.MSBuild.Plugin`, `Microsoft.CrmSdk.CoreAssemblies` +- **Consumed by**: `TALXIS.DevKit.Build.Dataverse.Solution` projects via `ProjectReference` + + diff --git a/src/Dataverse/ScriptLibrary/README.md b/src/Dataverse/ScriptLibrary/README.md index 366d0ea..e6a3d7a 100644 --- a/src/Dataverse/ScriptLibrary/README.md +++ b/src/Dataverse/ScriptLibrary/README.md @@ -1,11 +1,63 @@ # TALXIS.DevKit.Build.Dataverse.ScriptLibrary -MSBuild targets for Dataverse ScriptLibrary projects. - -## MSBuild properties -- `ProjectType` (default `ScriptLibrary`): marks the project for reference discovery. -- `RunNodeBuild` (default `false`): runs `npm install` and `npm run build` in `TypeScriptDir`. -- `TypeScriptDir` (default `$(MSBuildProjectDirectory)\TS`): folder containing the TypeScript project. -- `ScriptLibraryMainFile` : main script file used by consuming targets. -- `LangVersion` (default `latest`): C# language version for the project. -- `GenerateAssemblyInfo` (default `false`): disables auto-generated assembly info. +MSBuild integration for Dataverse web resource (JavaScript/TypeScript) projects. Automatically runs `npm install` and `npm run build` when a TypeScript project is detected, copies the compiled JS output to the build output directory, and exposes metadata targets that allow Solution projects to discover and integrate script libraries as web resources. + +## Installation + +```xml + +``` + +Or use the SDK approach: + +```xml + + + ScriptLibrary + + +``` + +## Prerequisites + +When `RunNodeBuild` is `true` (auto-detected from the presence of `package.json` in `TypeScriptDir`): + +- **Node.js** must be available on `PATH` +- **npm** 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 `ScriptLibrary` and disables `GenerateAssemblyInfo` by default since this is not a traditional .NET assembly project. + +### Build-time targets + +1. **CheckScriptLibraryPrereqs** -- validates that `TypeScriptDir` exists, `package.json` is present, and `node`/`npm` are on `PATH`. +2. **BuildTypeScript** (runs before `Build`) -- executes `npm install` followed by `npm run build` in `TypeScriptDir`. +3. **CopyScriptLibraryMainToOutput** (runs after `Build`) -- copies the main JS file from `TypeScriptDir\build\` to the output directory. + +### Integration targets + +Called by `TALXIS.DevKit.Build.Dataverse.Solution` via `ProjectReference`: + +- **GetProjectType** -- returns `ScriptLibrary`. +- **GetScriptLibraryOutputs** -- exposes the compiled JS file path for the solution to copy into `WebResources/`. + +## MSBuild Properties + +| Property | Default | Description | +|----------|---------|-------------| +| `ProjectType` | `ScriptLibrary` | Marks the project for reference discovery by Solution projects. | +| `RunNodeBuild` | Auto-detected | Set to `true` to run `npm install` and `npm run build`. Defaults to `true` if `package.json` exists in `TypeScriptDir`. | +| `TypeScriptDir` | `$(MSBuildProjectDirectory)\TS` | Folder containing the TypeScript project (`package.json`, sources). | +| `ScriptLibraryMainFile` | _(none)_ | Main script file path used by consuming targets. | +| `LangVersion` | `latest` | C# language version for the project. | +| `GenerateAssemblyInfo` | `false` | Disables auto-generated assembly info. | + +## Related Packages + +- **Depends on**: `TALXIS.DevKit.Build.Dataverse.Tasks` +- **Consumed by**: `TALXIS.DevKit.Build.Dataverse.Solution` projects via `ProjectReference` + + diff --git a/src/Dataverse/Sdk/README.md b/src/Dataverse/Sdk/README.md index 23f52cf..7b05feb 100644 --- a/src/Dataverse/Sdk/README.md +++ b/src/Dataverse/Sdk/README.md @@ -1,9 +1,46 @@ # TALXIS.DevKit.Build.Sdk -SDK that wires package references based on `ProjectType`. +An MSBuild SDK package that simplifies project setup by automatically resolving and referencing the correct `TALXIS.DevKit.Build.Dataverse.*` package based on the `ProjectType` property. Instead of manually adding `PackageReference` entries, projects declare this SDK and set `ProjectType` to have everything wired automatically. -## MSBuild properties -- `ProjectType`: selects the package to reference (for example `Solution`, `Plugin`, `Pcf`, `ScriptLibrary`, `PdPackage`, `Tasks`). -- `TALXISDevKitDataversePackageBase` (default `TALXIS.DevKit.Build.Dataverse`): base package name used with `ProjectType`. -- `TALXISDevKitDataversePackageVersion` (default `0.0.0.1`): version used in the package reference. -- `TALXISDevKitDataversePackageName`: explicit package name; overrides the base+ProjectType combination. +## Installation + +This is an MSBuild SDK, used differently from a regular NuGet package. + +```xml + + + Solution + + +``` + +## How It Works + +- **Sdk.props** imports `Microsoft.NET.Sdk` props and defines default values for `TALXISDevKitDataversePackageBase` and `TALXISDevKitDataversePackageVersion`. +- **Sdk.targets** imports `Microsoft.NET.Sdk` targets, then constructs `TALXISDevKitDataversePackageName` from `$(TALXISDevKitDataversePackageBase).$(ProjectType)` when `ProjectType` is set. It adds a `PackageReference` for the resolved package with `PrivateAssets="All"`. + +### Supported ProjectType values + +`Solution`, `Plugin`, `Pcf`, `ScriptLibrary`, `PdPackage`, `WorkflowActivity` + +The `TALXISDevKitDataversePackageName` property can be set explicitly to override the auto-resolution for advanced scenarios. + +## MSBuild Properties + +| Property | Default | Description | +|----------|---------|-------------| +| `ProjectType` | _(none)_ | Selects the package to reference (e.g. `Solution`, `Plugin`, `Pcf`). | +| `TALXISDevKitDataversePackageBase` | `TALXIS.DevKit.Build.Dataverse` | Base package name combined with `ProjectType`. | +| `TALXISDevKitDataversePackageVersion` | `0.0.0.1` | Version used in the auto-generated package reference. | +| `TALXISDevKitDataversePackageName` | `$(Base).$(ProjectType)` | Explicit package name; overrides the base + ProjectType combination. | + +## Related Packages + +This is the entry point to the TALXIS.DevKit.Build ecosystem. Based on `ProjectType`, it references one of: + +- `TALXIS.DevKit.Build.Dataverse.Solution` +- `TALXIS.DevKit.Build.Dataverse.Plugin` +- `TALXIS.DevKit.Build.Dataverse.Pcf` +- `TALXIS.DevKit.Build.Dataverse.ScriptLibrary` +- `TALXIS.DevKit.Build.Dataverse.PdPackage` +- `TALXIS.DevKit.Build.Dataverse.WorkflowActivity` diff --git a/src/Dataverse/Solution/README.md b/src/Dataverse/Solution/README.md index 0c97c43..2bbb582 100644 --- a/src/Dataverse/Solution/README.md +++ b/src/Dataverse/Solution/README.md @@ -1,20 +1,97 @@ # TALXIS.DevKit.Build.Dataverse.Solution -MSBuild targets for Dataverse solution projects. - -## MSBuild properties -- `ProjectType` (default `Solution`): marks the project as a solution for reference discovery. -- `Version` (required): base version; used for Git versioning and applied to solution.xml and related metadata. -- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). -- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. -- `Managed`: value written to the `` element in solution.xml. -- `PublisherName`: value written to the publisher name fields in solution.xml. -- `PublisherPrefix`: value written to solution.xml and used as the web resource name prefix. -- `SolutionRootPath` (default `.`): relative path to the solution source root. -- `SolutionPackagerWorkingDirectory` (default `$(IntermediateOutputPath)`): working folder for solution packager operations. -- `SolutionPackagerMetadataWorkingDirectory` (default `$(SolutionPackagerWorkingDirectory)Metadata`): metadata folder used for version updates. -- `SolutionPackagerLocalizationWorkingDirectory`: optional localization working folder (cleaned by CleanupWorkingDirectory). -- `SolutionPackageLogFilePath` (default `$(IntermediateOutputPath)SolutionPackager.log`): SolutionPackager log path. -- `SolutionPackageZipFilePath` (default `$(OutputPath)$(MSBuildProjectName).zip`): output zip path for pack tasks. -- `WebResourcesDir`: destination folder for script library web resources (default `$(MSBuildProjectDirectory)\$(SolutionRootPath)\WebResources\`). -- `PcfForceUpdate`: forwarded to PAC `ProcessCdsProjectReferencesOutputs` to force PCF updates. +MSBuild integration for building complete Dataverse solutions. Orchestrates the entire solution build pipeline: discovers and builds referenced Plugin, WorkflowActivity, ScriptLibrary, and PCF projects; patches solution XML with version, publisher, and managed state; runs the PAC solution packager to produce a `.zip` file; and supports `dotnet pack` to generate a NuGet package containing the solution zip. + +## Installation + +```xml + +``` + +Or use the SDK approach: + +```xml + + + Solution + + +``` + +## How It Works + +The package sets `ProjectType` to `Solution` and imports `Microsoft.PowerApps.MSBuild.Solution` targets. The build pipeline executes in the following order: + +### 1. Component discovery + +`ProbePluginLibraries`, `ProbeScriptLibraries`, and `ProbeWorkflowActivityLibraries` call `GetProjectType` on all `ProjectReference` items to classify them by component type. + +### 2. Component builds + +`BuildPluginLibraries`, `BuildScriptLibraries`, and `BuildWorkflowActivityLibraries` compile each referenced component project before `CopyCdsSolutionContent`. + +### 3. Component metadata generation + +- **Plugin assemblies** -- `EnsurePluginAssemblyDataXml` generates `.data.xml` files under `PluginAssemblies/`. +- **Workflow activities** -- `EnsureWorkflowActivityAssemblyDataXml` generates `.data.xml` for workflow activity assemblies. +- **Script libraries** -- `CopyScriptLibrariesToWebResources` resolves web resource names with the publisher prefix, generates `.data.xml`, and registers root components in `Solution.xml`. + +### 4. Solution XML patching + +`PatchSolutionXml` writes `Version`, `Managed`, `PublisherName`, and `PublisherPrefix` into `Solution.xml`. + +### 5. PAC override and versioning + +`ProcessCdsProjectReferencesOutputs` replaces the Microsoft default to filter ScriptLibrary and WorkflowActivity references from PAC processing. Then `GenerateVersionNumber` and `ApplyVersionNumber` patch the version across all solution metadata. + +### 6. Solution packaging + +`PackDataverseSolution` invokes the PAC solution packager to produce the output `.zip`. + +### 7. NuGet packing + +`dotnet pack` produces a `.nupkg` with the solution `.zip` under `content/solution/`. + +## MSBuild Properties + +### General + +| Property | Default | Description | +|----------|---------|-------------| +| `ProjectType` | `Solution` | Marks the project as a solution for reference discovery. | +| `Version` | _(required)_ | Base version; used for Git versioning and applied to solution.xml and related metadata. | +| `ApplyToBranches` | _(none)_ | Semicolon-separated branch rules (e.g. `master;hotfix;develop:1;pr:3;feature/*:2`). | +| `LocalBranchBuildVersionNumber` | `0.0.0.1` | Fallback version when Git versioning is not applied. | + +### Solution metadata + +| Property | Default | Description | +|----------|---------|-------------| +| `Managed` | _(none)_ | Value written to the `` element in solution.xml. | +| `PublisherName` | _(none)_ | Value written to the publisher name fields in solution.xml. | +| `PublisherPrefix` | _(none)_ | Value written to solution.xml and used as the web resource name prefix. | + +### Paths + +| Property | Default | Description | +|----------|---------|-------------| +| `SolutionRootPath` | `.` | Relative path to the solution source root. | +| `SolutionPackagerWorkingDirectory` | `$(IntermediateOutputPath)` | Working folder for solution packager operations. | +| `SolutionPackagerMetadataWorkingDirectory` | `$(SolutionPackagerWorkingDirectory)Metadata` | Metadata folder used for version updates. | +| `SolutionPackagerLocalizationWorkingDirectory` | _(none)_ | Optional localization working folder (cleaned by `CleanupWorkingDirectory`). | +| `SolutionPackageLogFilePath` | `$(IntermediateOutputPath)SolutionPackager.log` | SolutionPackager log path. | +| `SolutionPackageZipFilePath` | `$(OutputPath)$(MSBuildProjectName).zip` | Output zip path for pack tasks. | + +### Web resources and PCF + +| Property | Default | Description | +|----------|---------|-------------| +| `WebResourcesDir` | `$(MSBuildProjectDirectory)\$(SolutionRootPath)\WebResources\` | Destination folder for script library web resources. | +| `PcfForceUpdate` | _(none)_ | Forwarded to PAC `ProcessCdsProjectReferencesOutputs` to force PCF updates. | + +## Related Packages + +- **Depends on**: `TALXIS.DevKit.Build.Dataverse.Tasks`, `Microsoft.PowerApps.MSBuild.Solution` +- **Discovers and builds**: `Plugin`, `WorkflowActivity`, `ScriptLibrary`, and `Pcf` projects via `ProjectReference` + + diff --git a/src/Dataverse/Tasks/README.md b/src/Dataverse/Tasks/README.md index 32b475f..5fbf0b4 100644 --- a/src/Dataverse/Tasks/README.md +++ b/src/Dataverse/Tasks/README.md @@ -1,17 +1,76 @@ # TALXIS.DevKit.Build.Dataverse.Tasks -MSBuild tasks and targets shared by Dataverse packages. +Core MSBuild tasks and targets library shared by all `TALXIS.DevKit.Build.Dataverse.*` packages. Provides custom C# MSBuild tasks for Git-based version generation, solution XML patching, solution packaging via PAC CLI, schema validation, CMT data merging, and web resource management. Most users do not reference this package directly -- it is pulled in as a dependency of the higher-level packages (`Plugin`, `Solution`, `Pcf`, etc.). + +## Installation + +```xml + +``` + +Typically this package is referenced transitively through one of the component packages. + +## How It Works + +The package ships compiled task assemblies for `net472` and `net6.0`. At build time, the correct assembly is selected based on `MSBuildRuntimeType` (Core vs Full Framework). + +### Registered MSBuild tasks + +| Category | Tasks | +|----------|-------| +| Versioning | `GenerateGitVersion`, `ApplyVersionNumber`, `ApplyPcfVersionNumber`, `ApplyPluginVersionNumberInSolution` | +| Solution packaging | `InvokeSolutionPackager`, `PatchSolutionXml`, `EnsureCustomizationsNode` | +| Component metadata | `EnsurePluginAssemblyDataXml`, `EnsureWorkflowActivityAssemblyDataXml`, `EnsureWebResourceDataXml`, `AddRootComponentToSolution` | +| Validation | `ValidateSolutionComponentSchema`, `ValidateXmlFiles`, `ValidateJsonFiles` | +| CMT data | `MergeCmtDataXml`, `MergeCmtDataSchemaXml`, `AppendCmtDataFileToImportConfig` | +| Utilities | `RetrieveProjectReferences`, `ResolveWebResourceName` | + +### Key targets + +- **GenerateVersionNumber** -- requires the `Version` property. Runs `GenerateGitVersion` using the major/minor from `Version`, the current Git branch, and `ApplyToBranches` rules to produce a full four-part version number. +- **ApplyVersionNumber** -- patches the generated version into solution metadata folders (`SolutionXml`, `PluginAssemblies`, `Workflows`, `Controls`, `SdkMessageProcessingSteps`). +- **ApplyPcfVersionNumber** -- updates the version in `ControlManifest.xml` for PCF controls. +- **PackDataverseSolution** -- invokes the PAC solution packager to produce a `.zip` from the working directory. +- **ValidateSolutionComponentSchema** -- validates solution XML files against bundled XSD schemas and JSON files against JSON schemas. +- **InitializeSolutionPackagerWorkingDirectory** -- copies solution source files into the intermediate working directory for packaging. +- **CleanupWorkingDirectory** -- removes temporary localization and working directories after build. + +## MSBuild Properties -## MSBuild properties ### Versioning +| Property | Default | Description | +|----------|---------|-------------| +| `Version` | _(required)_ | Base version (`Major.Minor`); used by `GenerateGitVersion` to produce the full version. | +| `ApplyToBranches` | _(none)_ | Semicolon-separated branch rules (e.g. `master;hotfix;develop:1;pr:3;feature/*:2`). | +| `LocalBranchBuildVersionNumber` | `0.0.20000.0` | Fallback version used when the current branch does not match `ApplyToBranches`. | + ### Solution packager paths -- `SolutionRootPath` (default `.`): relative path to the solution source root. -- `SolutionPackagerWorkingDirectory` (default `$(IntermediateOutputPath)`): working folder for pack/unpack. -- `SolutionPackagerMetadataWorkingDirectory` (default `$(SolutionPackagerWorkingDirectory)Metadata`): metadata folder used by version update targets. -- `SolutionPackagerLocalizationWorkingDirectory`: optional localization working folder (cleaned by CleanupWorkingDirectory). -- `SolutionPackageLogFilePath` (default `$(IntermediateOutputPath)SolutionPackager.log`): SolutionPackager log path. -- `SolutionPackageZipFilePath` (default `$(OutputPath)$(MSBuildProjectName).zip`): output zip path used by PackDataverseSolution. + +| Property | Default | Description | +|----------|---------|-------------| +| `SolutionRootPath` | `.` | Relative path to the solution source root. | +| `SolutionPackagerWorkingDirectory` | `$(IntermediateOutputPath)` | Working folder for pack/unpack operations. | +| `SolutionPackagerMetadataWorkingDirectory` | `$(SolutionPackagerWorkingDirectory)Metadata` | Metadata folder used by version update targets. | +| `SolutionPackagerLocalizationWorkingDirectory` | _(none)_ | Optional localization working folder (cleaned by `CleanupWorkingDirectory`). | +| `SolutionPackageLogFilePath` | `$(IntermediateOutputPath)SolutionPackager.log` | SolutionPackager log file path. | +| `SolutionPackageZipFilePath` | `$(OutputPath)$(MSBuildProjectName).zip` | Output path for the packed solution `.zip`. | ### PCF versioning -- `PcfOutputPath`: output directory containing `ControlManifest.xml` (used by ApplyPcfVersionNumber). + +| Property | Default | Description | +|----------|---------|-------------| +| `PcfOutputPath` | _(none)_ | Output directory containing `ControlManifest.xml` (used by `ApplyPcfVersionNumber`). | + +## Related Packages + +This is the foundational package in the ecosystem. The following packages depend on it: + +- `TALXIS.DevKit.Build.Dataverse.Pcf` +- `TALXIS.DevKit.Build.Dataverse.Plugin` +- `TALXIS.DevKit.Build.Dataverse.WorkflowActivity` +- `TALXIS.DevKit.Build.Dataverse.ScriptLibrary` +- `TALXIS.DevKit.Build.Dataverse.Solution` +- `TALXIS.DevKit.Build.Dataverse.PdPackage` + + diff --git a/src/Dataverse/WorkflowActivity/README.md b/src/Dataverse/WorkflowActivity/README.md index 391ea0b..d364cbf 100644 --- a/src/Dataverse/WorkflowActivity/README.md +++ b/src/Dataverse/WorkflowActivity/README.md @@ -1,12 +1,53 @@ # TALXIS.DevKit.Build.Dataverse.WorkflowActivity -MSBuild targets for Dataverse workflowActivity projects. - -## MSBuild properties -- `ProjectType` (default `WorkflowActivity`): marks the project as a workflowActivity for reference discovery. -- `Version` (required): base version; major/minor are used for Git versioning and applied to assembly/package versions. -- `ApplyToBranches`: semicolon-separated branch rules for Git versioning (example: `master;hotfix;develop:1;pr:3;feature/*:2`). -- `LocalBranchBuildVersionNumber` (default `0.0.0.1`): fallback version when Git versioning is not applied. -- `WorkflowActivityTargetFramework` (default `$(TargetFramework)` when set, otherwise `net462`): target framework used to locate the compiled workflowActivity DLL. -- `WorkflowActivityPublishFolderName` (default `publish`): publish folder name under `bin\\\`. -- `WorkflowActivityAssemblyId`: explicit GUID for the workflowActivity assembly metadata; if empty, a new GUID is generated. +MSBuild integration for Dynamics 365 custom workflow activity assembly projects. Mirrors the Plugin package pattern: configures Visual Studio project type GUIDs, applies automatic Git-based versioning, and exposes metadata targets that allow Solution projects to discover and integrate workflow activity assemblies during build. + +## Installation + +```xml + +``` + +Or use the SDK approach: + +```xml + + + WorkflowActivity + + +``` + +## How It Works + +The package sets `ProjectType` to `WorkflowActivity` and configures `ProjectTypeGuids` for workflow activity recognition in Visual Studio. + +### Build-time targets + +- **TalxisBeforeBuild** (runs before `BeforeBuild`) -- executes `GenerateVersionNumber` followed by `ApplyPluginVersionNumber` to set `AssemblyVersion`, `FileVersion`, `Version`, and `PackageVersion` from Git. + +### Integration targets + +These targets are called by `TALXIS.DevKit.Build.Dataverse.Solution` when it discovers this project via `ProjectReference`: + +- **GetProjectType** -- returns `WorkflowActivity` so the Solution build knows how to handle this reference. +- **GetWorkflowActivityAssemblyInfo** -- returns `WorkflowActivityRootPath`, `WorkflowActivityAssemblyId`, `TargetFramework`, `PublishFolderName`, and `AssemblyName` for automatic workflow activity assembly metadata generation in the solution. + +## MSBuild Properties + +| Property | Default | Description | +|----------|---------|-------------| +| `ProjectType` | `WorkflowActivity` | Marks the project as a workflow activity for reference discovery. | +| `Version` | _(required)_ | Base version; major/minor are used for Git versioning. | +| `ApplyToBranches` | _(none)_ | Semicolon-separated branch rules (e.g. `master;hotfix;develop:1;pr:3;feature/*:2`). | +| `LocalBranchBuildVersionNumber` | `0.0.0.1` | Fallback version when Git versioning is not applied. | +| `WorkflowActivityTargetFramework` | `$(TargetFramework)` or `net462` | Target framework used to locate the compiled workflow activity DLL. | +| `WorkflowActivityPublishFolderName` | `publish` | Publish folder name under `bin\\\`. | +| `WorkflowActivityAssemblyId` | _(auto-generated)_ | Explicit GUID for the workflow activity assembly metadata; a new GUID is generated if empty. | + +## Related Packages + +- **Depends on**: `TALXIS.DevKit.Build.Dataverse.Tasks`, `Microsoft.PowerApps.MSBuild.Plugin`, `Microsoft.CrmSdk.CoreAssemblies` +- **Consumed by**: `TALXIS.DevKit.Build.Dataverse.Solution` projects via `ProjectReference` + + From 4abcd60bd2cd6318f25352104cd024ed2d5fbcfb Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Mon, 9 Feb 2026 13:23:45 +0100 Subject: [PATCH 46/52] Pdpackage bug fixed --- pack-dataverse-latest-to-local.ps1 | 41 ------------------- pack-dataverse-selected.ps1 | 21 ---------- pack-set.ps1 | 2 - src/Dataverse/PDPackage/README.md | 7 ++-- ...XIS.DevKit.Build.Dataverse.PdPackage.props | 2 + ...S.DevKit.Build.Dataverse.PdPackage.targets | 5 +-- 6 files changed, 8 insertions(+), 70 deletions(-) delete mode 100644 pack-dataverse-latest-to-local.ps1 delete mode 100644 pack-dataverse-selected.ps1 delete mode 100644 pack-set.ps1 diff --git a/pack-dataverse-latest-to-local.ps1 b/pack-dataverse-latest-to-local.ps1 deleted file mode 100644 index 75c86f2..0000000 --- a/pack-dataverse-latest-to-local.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -$ErrorActionPreference = "Stop" - -$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path -$sourceRoot = Join-Path $repoRoot "src\\Dataverse" -$destination = "C:\Dev\NuGet\LocalPackages" - -if (-not (Test-Path -Path $destination)) { - New-Item -Path $destination -ItemType Directory -Force | Out-Null -} - -$packageCandidates = Get-ChildItem -Path $sourceRoot -Directory | ForEach-Object { - $releaseDir = Join-Path $_.FullName "bin\\Release" - if (Test-Path -Path $releaseDir) { - Get-ChildItem -Path $releaseDir -Filter *.nupkg -File -ErrorAction SilentlyContinue - } -} - -$latestPackages = $packageCandidates | Group-Object -Property Name | ForEach-Object { - $_.Group | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -} - -if (-not $latestPackages) { - Write-Host "No .nupkg files found under $sourceRoot." - exit 0 -} - -foreach ($pkg in $latestPackages) { - Copy-Item -Path $pkg.FullName -Destination $destination -Force -} - -Write-Host "Copied $($latestPackages.Count) package(s) to $destination." - -# Stop all .NET Host processes before clearing NuGet cache -$dotnetProcesses = Get-Process -Name "dotnet" -ErrorAction SilentlyContinue -if ($dotnetProcesses) { - Write-Host "Stopping $($dotnetProcesses.Count) .NET Host process(es)..." - $dotnetProcesses | Stop-Process -Force - Start-Sleep -Seconds 1 -} - -dotnet nuget locals all --clear diff --git a/pack-dataverse-selected.ps1 b/pack-dataverse-selected.ps1 deleted file mode 100644 index b6456c4..0000000 --- a/pack-dataverse-selected.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -$ErrorActionPreference = "Stop" - -$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path - -$projects = @( - "src\\Dataverse\\PDPackage\\TALXIS.DevKit.Build.Dataverse.PdPackage.csproj", - "src\\Dataverse\\Plugin\\TALXIS.DevKit.Build.Dataverse.Plugin.csproj", - "src\\Dataverse\\Solution\\TALXIS.DevKit.Build.Dataverse.Solution.csproj", - "src\\Dataverse\\ScriptLibrary\\TALXIS.DevKit.Build.Dataverse.ScriptLibrary.csproj", - "src\\Dataverse\\Tasks\\TALXIS.DevKit.Build.Dataverse.Tasks.csproj", - "src\\Dataverse\\WorkflowActivity\\TALXIS.DevKit.Build.Dataverse.WorkflowActivity.csproj" -) - -foreach ($project in $projects) { - $projectPath = Join-Path $repoRoot $project - if (-not (Test-Path -Path $projectPath)) { - throw "Project not found: $projectPath" - } - - dotnet build $projectPath -c Release && dotnet pack $projectPath -c Release -} diff --git a/pack-set.ps1 b/pack-set.ps1 deleted file mode 100644 index f70e293..0000000 --- a/pack-set.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -./pack-dataverse-selected.ps1 -./pack-dataverse-latest-to-local.ps1 \ No newline at end of file diff --git a/src/Dataverse/PDPackage/README.md b/src/Dataverse/PDPackage/README.md index 2b04224..10c26c9 100644 --- a/src/Dataverse/PDPackage/README.md +++ b/src/Dataverse/PDPackage/README.md @@ -44,9 +44,9 @@ Props and targets from `Microsoft.PowerApps.MSBuild.PDPackage` are imported auto `TalxisPrepareCmtPackageMetadata` merges `data.xml` and `data_schema.xml` from all CMT packages into a single combined package, generates `[Content_Types].xml`, zips it, and appends a reference to `ImportConfig.xml`. -### NuGet packing +### Publishing and NuGet packing -`dotnet pack` produces a `.nupkg` with the `.pdpkg.zip`. +`dotnet publish` is the primary build command. It publishes the project, generates the `.pdpkg.zip` via `GeneratePdPackage`, and then automatically runs `Pack` to produce a `.nupkg` containing the `.pdpkg.zip` (controlled by `GeneratePackageOnPublish`). ## MSBuild Properties @@ -55,7 +55,8 @@ Props and targets from `Microsoft.PowerApps.MSBuild.PDPackage` are imported auto | Property | Default | Description | |----------|---------|-------------| | `PdPackageMsBuildVersion` | `1.50.1` | Version of `Microsoft.PowerApps.MSBuild.PDPackage` imported by the package. | -| `GeneratePdPackageOnBuild` | `true` | Runs `GeneratePdPackage` after build/publish. | +| `GeneratePdPackageOnBuild` | `true` | Runs `GeneratePdPackage` after publish. | +| `GeneratePackageOnPublish` | `true` | Triggers NuGet pack after `dotnet publish` to produce a `.nupkg` containing the `.pdpkg.zip`. | ### ILRepack diff --git a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props index b07f5d7..166aaad 100644 --- a/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props +++ b/src/Dataverse/PDPackage/msbuild/build/TALXIS.DevKit.Build.Dataverse.PdPackage.props @@ -3,6 +3,8 @@ 1.50.1 + + false <_PdPackageMsBuildProps Condition="'$(_PdPackageMsBuildProps)'==''"> $(NuGetPackageRoot)microsoft.powerapps.msbuild.pdpackage\$(PdPackageMsBuildVersion)\build\Microsoft.PowerApps.MSBuild.PDPackage.props diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets index 30a2cf6..b2c353b 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.targets @@ -27,7 +27,6 @@ - From acb0216e4622c6da0dc731308fd5084f320813ec Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 10 Feb 2026 10:24:11 +0100 Subject: [PATCH 47/52] cmt package dependency --- ...TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec | 1 + ...DevKit.Build.Dataverse.CmtPackage.Main.targets | 15 --------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec index ccdb9c6..47cf149 100644 --- a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec @@ -18,6 +18,7 @@ + diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets index 2fd8273..2a35911 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CmtPackage.Main.targets @@ -1,16 +1,4 @@ - - 0.0.0.1 - - $([System.IO.Path]::Combine('$(NuGetPackageRoot)','talxis.devkit.build.dataverse.tasks','$(TalxisDevKitTasksVersion)','tasks','TALXIS.DevKit.Build.Dataverse.Tasks.targets')) - - - $(MSBuildThisFileDirectory)..\..\Tasks\msbuild\tasks\TALXIS.DevKit.Build.Dataverse.Tasks.targets - - - - - <_CmtMetadataImportConfigDir Condition="'$(_CmtMetadataImportConfigDir)'=='' and '$(_GeneratedPdImportConfig)'!=''">$([System.IO.Path]::GetDirectoryName('$(_GeneratedPdImportConfig)')) <_CmtMetadataImportConfigDir Condition="'$(_CmtMetadataImportConfigDir)'==''">$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)')) @@ -68,9 +56,6 @@ $(_CmtWorkingImportConfig) - - <_CmtPackageMetadata Include="@(_CmtPackageDirs)"> $([System.IO.Path]::Combine('%(_CmtPackageDirs.Identity)','data.xml')) From 99a247c8870b4472e87a2e2aab54aacb7348c897 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 10 Feb 2026 10:44:23 +0100 Subject: [PATCH 48/52] Ilrepac paraniter --- README.md | 13 +++++++++++++ .../TALXIS.DevKit.Build.Dataverse.PdPackage.csproj | 5 ++--- .../TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fe13ac4..a3e1019 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,19 @@ Generates version numbers based on Git commit history, applying these versions a #### Build Number From Dependencies Project dependency folders are analyzed for Git changes to be reflected in generated version numbers. See [here](/docs/Versioning.md) for more details. +## Packages + +| Package | Description | +|---------|-------------| +| [TALXIS.DevKit.Build.Sdk](src/Dataverse/Sdk/README.md) | MSBuild SDK that auto-resolves the correct package based on `ProjectType`. Entry point for new projects. | +| [TALXIS.DevKit.Build.Dataverse.Tasks](src/Dataverse/Tasks/README.md) | Core MSBuild tasks shared by all packages: Git versioning, schema validation, solution packaging, CMT data merging. | +| [TALXIS.DevKit.Build.Dataverse.Solution](src/Dataverse/Solution/README.md) | Orchestrates the full Dataverse solution build: component discovery, XML patching, PAC solution packager, NuGet packing. | +| [TALXIS.DevKit.Build.Dataverse.Plugin](src/Dataverse/Plugin/README.md) | MSBuild integration for Dataverse plugin assemblies with auto-versioning and metadata exposure for Solution projects. | +| [TALXIS.DevKit.Build.Dataverse.Pcf](src/Dataverse/Pcf/README.md) | MSBuild integration for PCF controls. Wraps `Microsoft.PowerApps.MSBuild.Pcf` with Git-based versioning. | +| [TALXIS.DevKit.Build.Dataverse.WorkflowActivity](src/Dataverse/WorkflowActivity/README.md) | MSBuild integration for custom workflow activity assemblies with auto-versioning and Solution project integration. | +| [TALXIS.DevKit.Build.Dataverse.ScriptLibrary](src/Dataverse/ScriptLibrary/README.md) | Builds TypeScript/JS web resource projects (`npm install` + `npm run build`) and integrates them into Solution builds. | +| [TALXIS.DevKit.Build.Dataverse.PdPackage](src/Dataverse/PDPackage/README.md) | Package Deployer integration with ILRepack assembly merging and CMT metadata merge/zip support. | + ## Getting Started > [!TIP] > You can find demo steps for creating a new solution using PAC CLI and this package [here](https://tntg.cz/repo-init-demo). diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj index 2cc6eab..938c901 100644 --- a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.csproj @@ -4,10 +4,9 @@ net8.0 true 0.0.0.1 + 2.0.18 TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec - - Version=$(Version) - + Version=$(Version);ILRepackVersion=$(ILRepackVersion) \ No newline at end of file diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec index 47cf149..bd921d3 100644 --- a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec @@ -17,7 +17,7 @@ commit="c941d55d453c1c64a1e248a240b0327d2dcd76ee" /> - + From acc2b39d8789b64918e16d4ab6ab3f92db4e81bf Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 10 Feb 2026 10:59:20 +0100 Subject: [PATCH 49/52] NugetPackageTypeValue --- .../tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets | 2 +- .../tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets index 7dd4756..d40d23b 100644 --- a/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets +++ b/src/Dataverse/PDPackage/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.PdPackage.Pack.targets @@ -7,7 +7,7 @@ false true true - pp-pdpackage + pp-pdpackage true true diff --git a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets index 671d171..a4e3bd3 100644 --- a/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets +++ b/src/Dataverse/Solution/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Solution.Pack.targets @@ -7,7 +7,7 @@ false true true - pp-solution + pp-solution true true From dc5ea4fab4e487b00d2c71d1bca9510392f300f0 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 10 Feb 2026 11:22:24 +0100 Subject: [PATCH 50/52] MicrosoftPowerAppsTargetsVersion --- .../PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec | 2 +- .../Plugin/TALXIS.DevKit.Build.Dataverse.Plugin.nuspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec index bd921d3..ab69daf 100644 --- a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec @@ -16,7 +16,7 @@ - + diff --git a/src/Dataverse/Plugin/TALXIS.DevKit.Build.Dataverse.Plugin.nuspec b/src/Dataverse/Plugin/TALXIS.DevKit.Build.Dataverse.Plugin.nuspec index e43a109..1ce91af 100644 --- a/src/Dataverse/Plugin/TALXIS.DevKit.Build.Dataverse.Plugin.nuspec +++ b/src/Dataverse/Plugin/TALXIS.DevKit.Build.Dataverse.Plugin.nuspec @@ -14,7 +14,7 @@ 2025 NETWORG - + From 294ff111e1d436f43db38f0db91def0031ab1dd0 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 10 Feb 2026 11:25:52 +0100 Subject: [PATCH 51/52] Version --- .../PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec index ab69daf..b47a5aa 100644 --- a/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec +++ b/src/Dataverse/PDPackage/TALXIS.DevKit.Build.Dataverse.PdPackage.nuspec @@ -18,7 +18,7 @@ - + From 567c0c5c2bbb6e8d31453bbcd20efe8ab8e85c11 Mon Sep 17 00:00:00 2001 From: Alexander Zekelin Date: Tue, 10 Feb 2026 11:29:32 +0100 Subject: [PATCH 52/52] MicrosoftPowerAppsTargetsVersion --- .../TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec b/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec index b4d837f..8fe1df1 100644 --- a/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec +++ b/src/Dataverse/WorkflowActivity/TALXIS.DevKit.Build.Dataverse.WorkflowActivity.nuspec @@ -14,7 +14,7 @@ 2025 NETWORG - +