diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index a35eaf30..58396cd3 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -30,16 +30,23 @@ jobs:
- uses: actions/setup-dotnet@v4
with:
- # dotnet-version: '8.0.x'
+ dotnet-version: '8.0.x'
+
+ # Setup .NET SDK (installs .NET 10.0 via global.json)
+ - uses: actions/setup-dotnet@v4
+ with:
global-json-file: 'global.json'
- - run: dotnet --info
+ - name: Display .NET info
+ run: dotnet --info
- name: Build PASopa.sln
run: dotnet build src/PASopa.sln
# Pack so we can validate there are no warnings/errors
# We must explicitly set the configuration parameter otherwise it defaults to Release
+ # Packages will include all target frameworks (net48, net8.0, net10.0 for Persistence projects)
+ # Note: On Windows, net48 binaries are included; on Ubuntu, net48 build is skipped but pack succeeds
- name: Pack - Formulas.Tools
run: dotnet pack --no-build -c ${{ env.Configuration }} src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj
@@ -49,6 +56,23 @@ jobs:
- name: Pack - Persistence.Testing
run: dotnet pack --no-build -c ${{ env.Configuration }} src/Persistence.Testing/Microsoft.PowerPlatform.PowerApps.Persistence.Testing.csproj
- # Run tests
- - name: Test - PASopa.sln
+ # Run tests for all target frameworks
+ # On Windows: run all projects and frameworks together
+ - name: Test - PASopa.sln (Windows - all frameworks)
+ if: runner.os == 'Windows'
run: dotnet test --no-build --solution src/PASopa.sln
+
+ # On non-Windows: run test projects individually, since they don't all support the same TFMs
+ # We are only testing the main test projects & frameworks on Ubuntu
+ - name: Test - Persistence.Tests (net10.0)
+ if: runner.os != 'Windows'
+ run: dotnet test --no-build --project src/Persistence.Tests/Persistence.Tests.csproj --framework net10.0
+ continue-on-error: true
+ - name: Test - PAModelTests (net10.0)
+ if: runner.os != 'Windows'
+ run: dotnet test --no-build --project src/PAModelTests/PAModelTests.csproj --framework net10.0
+ continue-on-error: true
+ - name: Test - YamlValidator.Tests (net8.0)
+ if: runner.os != 'Windows'
+ run: dotnet test --no-build --project src/YamlValidator.Tests/YamlValidator.Tests.csproj --framework net8.0
+ continue-on-error: true
diff --git a/README.md b/README.md
index 5678f0e5..ece3f7d1 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@

```json
"yaml.schemas": {
- "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/pa.yaml-schema.json": "*.pa.yaml"
+ "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/pa.yaml-schema.json": "*.pa.yaml"
}
```
@@ -52,13 +52,15 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
For a developer machine (Windows 10, WSL, Linux, macOS), install:
- [git](https://git-scm.com/downloads)
-- [.NET Core SDK v6.0.x (x64)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
+- [.NET SDK 10.0.x (x64)](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) (version specified in global.json)
- [VS Code](https://code.visualstudio.com/Download)
-- if on Windows: [VS2019 or VS2022 (Community edition will do)](https://visualstudio.microsoft.com/downloads/). Select at least the following workload: .NET Core cross-plat
+- if on Windows: [VS2022 (Community edition will do)](https://visualstudio.microsoft.com/downloads/). Select at least the following workload: .NET desktop development (for .NET Framework 4.8 support)
- recommended VSCode extensions:
- [GitLens (eamodio.gitlens)](https://github.com/eamodio/vscode-gitlens)
- [C# (ms-vscode.csharp)](https://github.com/OmniSharp/omnisharp-vscode)
+**Note:** Some projects target multiple frameworks. Building .NET Framework 4.8 targets requires Windows with the .NET Framework 4.8 Developer Pack installed.
+
### Building and running tests
After cloning this repo (https://github.com/microsoft/PowerApps-Language-Tooling), open a terminal/cmd/PS prompt with the dotnet executable on the path. Check with: ```dotnet --version ```
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 918cbe10..fec7f4c4 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -22,6 +22,7 @@
+
diff --git a/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj b/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj
index 09b991ab..0e9565bf 100644
--- a/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj
+++ b/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net8.0
+ net48;net8.0;net10.0
12
true
@@ -11,12 +11,11 @@
PowerPlatform Canvas App Tools
- Preview Release: This takes a Canvas App (.msapp file) and converts to and from text files that can be checked into source control.
+ DEPRECATED: This takes a Canvas App (.msapp file) and converts to and from text files that can be checked into source control.
Notice:
- This package is a preview release - use at your own risk.
- This package is a .NET Standard 2.0 project, intended to work with .NET Framework 4.7.2 or later, and .NET 6.0 or later
- We have not stabilized on Namespace or Class names with this package as of yet and things will change as we move though the preview.
+ This package is DEPRECATED - use at your own risk. It is no longer receiving any updates and does not support the latest versions of
+ Power Apps Canvas Apps.
See https://github.com/microsoft/PowerApps-Tooling/releases for the latest release notes.
@@ -27,6 +26,7 @@
+
diff --git a/src/PAModel/packages.lock.json b/src/PAModel/packages.lock.json
index 39830097..2cefdcd7 100644
--- a/src/PAModel/packages.lock.json
+++ b/src/PAModel/packages.lock.json
@@ -1,22 +1,19 @@
{
"version": 2,
"dependencies": {
- ".NETStandard,Version=v2.0": {
- "NETStandard.Library": {
- "type": "Direct",
- "requested": "[2.0.3, )",
- "resolved": "2.0.3",
- "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0"
- }
- },
+ ".NETFramework,Version=v4.8": {
"Newtonsoft.Json": {
"type": "Direct",
"requested": "[13.0.3, )",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
+ "System.IO.Compression": {
+ "type": "Direct",
+ "requested": "[4.3.0, )",
+ "resolved": "4.3.0",
+ "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg=="
+ },
"System.Text.Encodings.Web": {
"type": "Direct",
"requested": "[8.0.0, )",
@@ -39,7 +36,8 @@
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "8.0.0",
- "System.Threading.Tasks.Extensions": "4.5.4"
+ "System.Threading.Tasks.Extensions": "4.5.4",
+ "System.ValueTuple": "4.5.0"
}
},
"YamlDotNet": {
@@ -56,11 +54,6 @@
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
- "Microsoft.NETCore.Platforms": {
- "type": "Transitive",
- "resolved": "1.1.0",
- "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
- },
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
@@ -72,14 +65,14 @@
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
- "System.Numerics.Vectors": "4.4.0",
+ "System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
+ "resolved": "4.5.0",
+ "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
@@ -93,6 +86,37 @@
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
+ },
+ "System.ValueTuple": {
+ "type": "Transitive",
+ "resolved": "4.5.0",
+ "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
+ }
+ },
+ "net10.0": {
+ "Newtonsoft.Json": {
+ "type": "Direct",
+ "requested": "[13.0.3, )",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "System.Text.Encodings.Web": {
+ "type": "Direct",
+ "requested": "[8.0.0, )",
+ "resolved": "8.0.0",
+ "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
+ },
+ "System.Text.Json": {
+ "type": "Direct",
+ "requested": "[8.0.5, )",
+ "resolved": "8.0.5",
+ "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg=="
+ },
+ "YamlDotNet": {
+ "type": "Direct",
+ "requested": "[15.1.6, )",
+ "resolved": "15.1.6",
+ "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
}
},
"net8.0": {
diff --git a/src/PAModelTests/DataSourceTests.cs b/src/PAModelTests/DataSourceTests.cs
index b7f7d93c..b5651f13 100644
--- a/src/PAModelTests/DataSourceTests.cs
+++ b/src/PAModelTests/DataSourceTests.cs
@@ -221,7 +221,7 @@ public void TestWhenDataSourcesIsSetToEmptyDictionary(string appName)
Assert.IsTrue(File.Exists(pathToMsApp));
var (msApp, errors) = CanvasDocument.LoadFromMsapp(pathToMsApp);
- msApp._dataSourceReferences["default.cds"].dataSources = new Dictionary();
+ msApp._dataSourceReferences["default.cds"].dataSources = new();
errors.ThrowOnErrors();
using var sourcesTempDir = new TempDir();
@@ -256,7 +256,12 @@ public void TestAllUsedDataSourcesArePreserved(string appName)
errors1.ThrowOnErrors();
Assert.HasCount(msApp._dataSourceReferences["default.cds"].dataSources.Count, msApp._dataSourceReferences["default.cds"].dataSources);
- foreach (var entry in msApp._dataSourceReferences["default.cds"].dataSources.Keys.OrderBy(key => key).Zip(msApp1._dataSourceReferences["default.cds"].dataSources.Keys.OrderBy(key => key)))
+ foreach (var entry in msApp._dataSourceReferences["default.cds"].dataSources.Keys.OrderBy(key => key)
+ .Zip(msApp1._dataSourceReferences["default.cds"].dataSources.Keys.OrderBy(key => key)
+#if NET48 // in net48, Zip does not have an overload that has default result selector, so we need to create the tuple manually.
+ , resultSelector: (k1, k2) => (First: k1, Second: k2)
+#endif
+ ))
{
Assert.AreEqual(entry.First, entry.Second);
}
diff --git a/src/PAModelTests/PAModelTests.csproj b/src/PAModelTests/PAModelTests.csproj
index 5bb8a6b7..4f7c2ed1 100644
--- a/src/PAModelTests/PAModelTests.csproj
+++ b/src/PAModelTests/PAModelTests.csproj
@@ -1,8 +1,8 @@
- net8.0
-
+ net48;net8.0;net10.0
+ 10.0
true
true
diff --git a/src/PAModelTests/SmartMergeTests.cs b/src/PAModelTests/SmartMergeTests.cs
index ca08bf7e..08c33052 100644
--- a/src/PAModelTests/SmartMergeTests.cs
+++ b/src/PAModelTests/SmartMergeTests.cs
@@ -178,20 +178,28 @@ public void ScreenDeletionTest()
(var msapp, var errors) = CanvasDocument.LoadFromMsapp(root);
Assert.IsFalse(errors.HasErrors);
+ const string screenName = "Home Screen";
MergeTester(
- msapp,
- (branchADoc) =>
- {
- // Nothing
- },
- (branchBDoc) =>
- {
- branchBDoc._screens.Remove("Home Screen", out var control);
- },
- (resultDoc) =>
- {
- Assert.IsFalse(resultDoc._screens.ContainsKey("Home Screen"));
- });
+ msapp,
+ (branchADoc) =>
+ {
+ // Nothing
+ },
+ (branchBDoc) =>
+ {
+#if NET48 // in net48, Remove does not have an overload that will return the item removed.
+ if (branchBDoc._screens.TryGetValue(screenName, out var control))
+ {
+ branchBDoc._screens.Remove(screenName);
+ }
+#else
+ branchBDoc._screens.Remove(screenName, out var control);
+#endif
+ },
+ (resultDoc) =>
+ {
+ Assert.IsFalse(resultDoc._screens.ContainsKey(screenName));
+ });
}
[TestMethod]
diff --git a/src/PASopa/README.md b/src/PASopa/README.md
index 5582e70f..6c1fe5c9 100644
--- a/src/PASopa/README.md
+++ b/src/PASopa/README.md
@@ -51,7 +51,7 @@ Latest Yaml version is: https://github.com/microsoft/PowerApps-Language-Tooling/
You can also use this functionality stand alone, using our test console app.
-Download and install the [.NET Core SDK v6.0.x (x64)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) in order to build.
+Download and install the [.NET SDK 10.0.x (x64)](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) (version specified in global.json) in order to build.
Build the test console app by running: `\build.cmd`
This will create: `\bin\Debug\PASopa\PASopa.exe`
diff --git a/src/Persistence.Testing/Microsoft.PowerPlatform.PowerApps.Persistence.Testing.csproj b/src/Persistence.Testing/Microsoft.PowerPlatform.PowerApps.Persistence.Testing.csproj
index 48ff7989..962a3d47 100644
--- a/src/Persistence.Testing/Microsoft.PowerPlatform.PowerApps.Persistence.Testing.csproj
+++ b/src/Persistence.Testing/Microsoft.PowerPlatform.PowerApps.Persistence.Testing.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net8.0;net10.0
enable
enable
diff --git a/src/Persistence.Tests/Persistence.Tests.csproj b/src/Persistence.Tests/Persistence.Tests.csproj
index 435e3e28..e90b80f0 100644
--- a/src/Persistence.Tests/Persistence.Tests.csproj
+++ b/src/Persistence.Tests/Persistence.Tests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net8.0;net10.0
enable
enable
diff --git a/src/Persistence/Compression/PaArchiveExtensions.ExtractAsync.cs b/src/Persistence/Compression/PaArchiveExtensions.ExtractAsync.cs
index da3e742b..11207c33 100644
--- a/src/Persistence/Compression/PaArchiveExtensions.ExtractAsync.cs
+++ b/src/Persistence/Compression/PaArchiveExtensions.ExtractAsync.cs
@@ -9,15 +9,19 @@ namespace Microsoft.PowerPlatform.PowerApps.Persistence.Compression;
public static partial class PaArchiveExtensions
{
- public static ValueTask ExtractToFileAsync(this PaArchiveEntry source, string destinationFileName, bool overwrite = false)
- {
#if NET10_0_OR_GREATER
- // When we support .net 10, use ExtractToFileAsync
+ public static async Task ExtractToFileAsync(this PaArchiveEntry source, string destinationFileName, bool overwrite = false)
+ {
+ // .net 10 supports ExtractToFileAsync
+ await source.ZipEntry.ExtractToFileAsync(destinationFileName, overwrite).ConfigureAwait(false);
+ }
#else
+ public static ValueTask ExtractToFileAsync(this PaArchiveEntry source, string destinationFileName, bool overwrite = false)
+ {
source.ZipEntry.ExtractToFile(destinationFileName, overwrite);
return ValueTask.CompletedTask;
-#endif
}
+#endif
///
/// Extracts the archive to a target directory, preserving relative paths.
diff --git a/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj b/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj
index 087183f5..fe16fd24 100644
--- a/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj
+++ b/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net8.0;net10.0
enable
enable
diff --git a/src/Persistence/packages.lock.json b/src/Persistence/packages.lock.json
index 5476ac7c..0ffca935 100644
--- a/src/Persistence/packages.lock.json
+++ b/src/Persistence/packages.lock.json
@@ -1,6 +1,29 @@
{
- "version": 1,
+ "version": 2,
"dependencies": {
+ "net10.0": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Direct",
+ "requested": "[8.0.2, )",
+ "resolved": "8.0.2",
+ "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg=="
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Direct",
+ "requested": "[8.0.3, )",
+ "resolved": "8.0.3",
+ "contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
+ }
+ },
+ "YamlDotNet": {
+ "type": "Direct",
+ "requested": "[15.1.6, )",
+ "resolved": "15.1.6",
+ "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ }
+ },
"net8.0": {
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Direct",
diff --git a/src/YamlValidator.Tests/YamlValidator.Tests.csproj b/src/YamlValidator.Tests/YamlValidator.Tests.csproj
index c77db480..7434466f 100644
--- a/src/YamlValidator.Tests/YamlValidator.Tests.csproj
+++ b/src/YamlValidator.Tests/YamlValidator.Tests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net8.0
Latest
enable
enable