diff --git a/src/PAModelTests/DataSourceTests.cs b/src/PAModelTests/DataSourceTests.cs
index 84a2aac7..5f7e7d59 100644
--- a/src/PAModelTests/DataSourceTests.cs
+++ b/src/PAModelTests/DataSourceTests.cs
@@ -9,7 +9,7 @@
using Microsoft.PowerPlatform.Formulas.Tools;
using Microsoft.PowerPlatform.Formulas.Tools.Extensions;
using Microsoft.PowerPlatform.Formulas.Tools.IO;
-using Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
+using Microsoft.PowerPlatform.PowerApps.Persistence;
namespace PAModelTests;
diff --git a/src/Persistence.Tests/MsApp/MsappArchiveTests.cs b/src/Persistence.Tests/MsApp/MsappArchiveTests.cs
index 0751b182..89b18504 100644
--- a/src/Persistence.Tests/MsApp/MsappArchiveTests.cs
+++ b/src/Persistence.Tests/MsApp/MsappArchiveTests.cs
@@ -2,7 +2,9 @@
// Licensed under the MIT License.
using System.IO.Compression;
-using Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
+using Microsoft.PowerPlatform.PowerApps.Persistence;
+using Microsoft.PowerPlatform.PowerApps.Persistence.Extensions;
+using Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;
namespace Persistence.Tests.MsApp;
@@ -10,21 +12,21 @@ namespace Persistence.Tests.MsApp;
[TestClass]
public class MsappArchiveTests
{
- [DataRow(new string[] { "abc.txt" }, MsappArchive.ResourcesDirectory, 0)]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" }, MsappArchive.ResourcesDirectory, 1)]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" }, $@" \{MsappArchive.ResourcesDirectory}/", 1)]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.ResourcesDirectory}/", 1)]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.ResourcesDirectory}\", 1)]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" }, "NotFound", 0)]
+ [DataRow(new string[] { "abc.txt" }, MsappArchive.Directories.ResourcesDirectory, 0)]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" }, MsappArchive.Directories.ResourcesDirectory, 1)]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" }, $@" \{MsappArchive.Directories.ResourcesDirectory}/", 1)]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.Directories.ResourcesDirectory}/", 1)]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.Directories.ResourcesDirectory}\", 1)]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" }, "NotFound", 0)]
[DataRow(new string[] {"abc.txt",
- @$"{MsappArchive.ResourcesDirectory}\abc.txt",
- @$"ReSoUrCeS/efg.txt"}, MsappArchive.ResourcesDirectory, 2)]
+ @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt",
+ @$"ReSoUrCeS/efg.txt"}, MsappArchive.Directories.ResourcesDirectory, 2)]
[DataRow(new string[] {"abc.txt",
- @$"{MsappArchive.ResourcesDirectory}\abc.txt",
- @$"{MsappArchive.ResourcesDirectory}/efg.txt"}, "RESOURCES", 2)]
+ @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt",
+ @$"{MsappArchive.Directories.ResourcesDirectory}/efg.txt"}, "RESOURCES", 2)]
[DataRow(new string[] {"abc.txt",
- @$"{MsappArchive.ResourcesDirectory}New\abc.txt",
- @$"{MsappArchive.ResourcesDirectory}/efg.txt"}, MsappArchive.ResourcesDirectory, 1)]
+ @$"{MsappArchive.Directories.ResourcesDirectory}New\abc.txt",
+ @$"{MsappArchive.Directories.ResourcesDirectory}/efg.txt"}, MsappArchive.Directories.ResourcesDirectory, 1)]
[TestMethod]
public void GetDirectoryEntriesTests(string[] entries, string directoryName, int expectedDirectoryCount)
{
@@ -46,9 +48,9 @@ public void GetDirectoryEntriesTests(string[] entries, string directoryName, int
}
[DataRow(new string[] { "abc.txt" })]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" })]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\DEF.txt" })]
- [DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\DEF.txt", @"\start-with-slash\test.json" })]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" })]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\DEF.txt" })]
+ [DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\DEF.txt", @"\start-with-slash\test.json" })]
[TestMethod]
public void AddEntryTests(string[] entries)
{
@@ -64,7 +66,7 @@ public void AddEntryTests(string[] entries)
msappArchive.CanonicalEntries.Count.Should().Be(entries.Length);
foreach (var entry in entries)
{
- msappArchive.CanonicalEntries.ContainsKey(MsappArchive.NormalizePath(entry)).Should().BeTrue();
+ msappArchive.CanonicalEntries.ContainsKey(FileUtils.NormalizePath(entry)).Should().BeTrue();
}
// Get the required entry should throw if it doesn't exist
diff --git a/src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs b/src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs
index 3b30b0f7..db73f98f 100644
--- a/src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs
+++ b/src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;
namespace Persistence.Tests.Yaml;
@@ -10,12 +9,12 @@ namespace Persistence.Tests.Yaml;
public class DeserializerInvalidTests
{
[TestMethod]
- public void Deserialize_ShouldFail()
+ public void Deserialize_ShouldFailWhenYamlIsInvalid()
{
// Arrange
var deserializer = YamlSerializationFactory.CreateDeserializer();
- var files = Directory.GetFiles(@"_TestData/InvalidYaml", $"*{MsappArchive.YamlFxFileExtension}", SearchOption.AllDirectories);
+ var files = Directory.GetFiles(@"_TestData/InvalidYaml", $"*{YamlUtils.YamlFxFileExtension}", SearchOption.AllDirectories);
// Uncomment to test single file
// var files = new string[] { @"_TestData/InvalidYaml/InvalidName.fx.yaml" };
diff --git a/src/Persistence.Tests/Yaml/ValidSerializerTests.cs b/src/Persistence.Tests/Yaml/ValidSerializerTests.cs
index b61910b5..fd8fe3cb 100644
--- a/src/Persistence.Tests/Yaml/ValidSerializerTests.cs
+++ b/src/Persistence.Tests/Yaml/ValidSerializerTests.cs
@@ -98,4 +98,22 @@ public void Serialize_ShouldCreateValidYamlForCustomControl()
var sut = serializer.Serialize(graph);
sut.Should().Be($"Control: http://localhost/#customcontrol{Environment.NewLine}Name: CustomControl1{Environment.NewLine}Properties:{Environment.NewLine} Text: I am a custom control{Environment.NewLine}");
}
+
+ [TestMethod]
+ public void Serialize_ShouldNotIncludeEnditorState()
+ {
+ var graph = new CustomControl("CustomControl1")
+ {
+ ControlUri = "http://localhost/#customcontrol",
+ EditorState = new()
+ {
+ Name = "CustomControl1",
+ },
+ };
+
+ var serializer = YamlSerializationFactory.CreateSerializer();
+
+ var sut = serializer.Serialize(graph);
+ sut.Should().NotContain(nameof(Control.EditorState));
+ }
}
diff --git a/src/Persistence/Extensions/IMsappArchiveExtensions.cs b/src/Persistence/Extensions/IMsappArchiveExtensions.cs
new file mode 100644
index 00000000..3485f89a
--- /dev/null
+++ b/src/Persistence/Extensions/IMsappArchiveExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.IO.Compression;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.Extensions;
+
+public static class IMsappArchiveExtensions
+{
+ ///
+ /// Returns the entry in the archive with the given name or throws a if it does not exist.
+ ///
+ /// the instance.
+ /// the name of the entry to fetch.
+ ///
+ ///
+ ///
+ public static ZipArchiveEntry GetRequiredEntry(this IMsappArchive archive, string entryName)
+ {
+ var entry = archive.GetEntry(entryName) ??
+ throw new FileNotFoundException($"Entry '{entryName}' not found in msapp archive.");
+
+ return entry;
+ }
+}
+
diff --git a/src/Persistence/MsApp/IMsappArchive.cs b/src/Persistence/IMsappArchive.cs
similarity index 70%
rename from src/Persistence/MsApp/IMsappArchive.cs
rename to src/Persistence/IMsappArchive.cs
index c97b241e..9023bce6 100644
--- a/src/Persistence/MsApp/IMsappArchive.cs
+++ b/src/Persistence/IMsappArchive.cs
@@ -3,7 +3,7 @@
using System.IO.Compression;
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
+namespace Microsoft.PowerPlatform.PowerApps.Persistence;
///
/// base interface for MsappArchive
@@ -26,6 +26,14 @@ public interface IMsappArchive
/// the entry or null when not found.
ZipArchiveEntry? GetEntry(string entryName);
+ ///
+ /// Returns all entries in the archive that are in the given directory.
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetDirectoryEntries(string directoryName, string? extension = null);
+
///
/// Provides access to the underlying zip archive.
///
diff --git a/src/Persistence/MsApp/MsappArchive.cs b/src/Persistence/MsappArchive.cs
similarity index 83%
rename from src/Persistence/MsApp/MsappArchive.cs
rename to src/Persistence/MsappArchive.cs
index 113e3f29..41dbf1fb 100644
--- a/src/Persistence/MsApp/MsappArchive.cs
+++ b/src/Persistence/MsappArchive.cs
@@ -6,9 +6,12 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.PowerPlatform.PowerApps.Persistence.Models;
+using Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
+using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;
using YamlDotNet.Serialization;
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
+namespace Microsoft.PowerPlatform.PowerApps.Persistence;
+
///
/// Represents a .msapp file.
@@ -17,15 +20,6 @@ public class MsappArchive : IMsappArchive, IDisposable
{
#region Constants
- public const string SrcDirectory = "Src";
- public const string ControlsDirectory = "Controls";
- public const string ComponentsDirectory = "Components";
- public const string AppTestDirectory = "AppTests";
- public const string ReferencesDirectory = "References";
- public const string ResourcesDirectory = "Resources";
-
- public const string YamlFileExtension = ".yaml";
- public const string YamlFxFileExtension = ".fx.yaml";
public const string JsonFileExtension = ".json";
#endregion
@@ -106,7 +100,7 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding
foreach (var entry in ZipArchive.Entries)
{
- if (!canonicalEntries.TryAdd(NormalizePath(entry.FullName), entry))
+ if (!canonicalEntries.TryAdd(FileUtils.NormalizePath(entry.FullName), entry))
_logger?.LogInformation($"Duplicate entry found in archive: {entry.FullName}");
}
@@ -143,17 +137,12 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding
#region Methods
- ///
- /// Returns all entries in the archive that are in the given directory.
- ///
- ///
- ///
- ///
+ ///
public IEnumerable GetDirectoryEntries(string directoryName, string? extension = null)
{
_ = directoryName ?? throw new ArgumentNullException(nameof(directoryName));
- directoryName = NormalizePath(directoryName);
+ directoryName = FileUtils.NormalizePath(directoryName);
foreach (var entry in CanonicalEntries)
{
@@ -173,35 +162,20 @@ public IEnumerable GetDirectoryEntries(string directoryName, st
if (string.IsNullOrWhiteSpace(entryName))
return null;
- entryName = NormalizePath(entryName);
+ entryName = FileUtils.NormalizePath(entryName);
if (CanonicalEntries.TryGetValue(entryName, out var entry))
return entry;
return null;
}
- ///
- /// Returns the entry in the archive with the given name or throws if it does not exist.
- ///
- ///
- ///
- ///
- ///
- public ZipArchiveEntry GetRequiredEntry(string entryName)
- {
- var entry = GetEntry(entryName) ??
- throw new FileNotFoundException($"Entry '{entryName}' not found in msapp archive.");
-
- return entry;
- }
-
///
public ZipArchiveEntry CreateEntry(string entryName)
{
if (string.IsNullOrWhiteSpace(entryName))
throw new ArgumentException("Entry name cannot be null or whitespace.", nameof(entryName));
- var canonicalEntryName = NormalizePath(entryName);
+ var canonicalEntryName = FileUtils.NormalizePath(entryName);
if (_canonicalEntries.Value.ContainsKey(canonicalEntryName))
throw new InvalidOperationException($"Entry {entryName} already exists in the archive.");
@@ -211,11 +185,6 @@ public ZipArchiveEntry CreateEntry(string entryName)
return entry;
}
- public static string NormalizePath(string path)
- {
- return path.Trim().Replace('\\', '/').Trim('/').ToLowerInvariant();
- }
-
#endregion
#region Private Methods
@@ -225,7 +194,7 @@ private List LoadScreens()
_logger?.LogInformation("Loading top level screens from Yaml.");
var screens = new Dictionary();
- foreach (var yamlEntry in GetDirectoryEntries(Path.Combine(SrcDirectory, ControlsDirectory), YamlFileExtension))
+ foreach (var yamlEntry in GetDirectoryEntries(Path.Combine(Directories.SrcDirectory, Directories.ControlsDirectory), YamlUtils.YamlFileExtension))
{
using var textReader = new StreamReader(yamlEntry.Open());
try
@@ -241,7 +210,7 @@ private List LoadScreens()
_logger?.LogInformation("Loading top level controls editor state.");
var controlEditorStates = new Dictionary();
- foreach (var editorStateEntry in GetDirectoryEntries(Path.Combine(ControlsDirectory), JsonFileExtension))
+ foreach (var editorStateEntry in GetDirectoryEntries(Path.Combine(Directories.ControlsDirectory), JsonFileExtension))
{
try
{
@@ -317,4 +286,15 @@ public void Dispose()
}
#endregion
+
+ public static class Directories
+ {
+ public const string SrcDirectory = "Src";
+ public const string ControlsDirectory = "Controls";
+ public const string ComponentsDirectory = "Components";
+ public const string AppTestDirectory = "AppTests";
+ public const string ReferencesDirectory = "References";
+ public const string ResourcesDirectory = "Resources";
+ }
}
+
diff --git a/src/Persistence/Utils/FileUtils.cs b/src/Persistence/Utils/FileUtils.cs
new file mode 100644
index 00000000..4e318a6b
--- /dev/null
+++ b/src/Persistence/Utils/FileUtils.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
+
+public static class FileUtils
+{
+ ///
+ /// Converts backslashes to forward slashes, removes trailing slashes, and converts to lowercase.
+ ///
+ ///
+ ///
+ public static string NormalizePath(string path)
+ {
+ return path.Trim().Replace('\\', '/').Trim('/').ToLowerInvariant();
+ }
+}
diff --git a/src/Persistence/Yaml/YamlUtils.cs b/src/Persistence/Yaml/YamlUtils.cs
new file mode 100644
index 00000000..27a86ba1
--- /dev/null
+++ b/src/Persistence/Yaml/YamlUtils.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;
+
+public static class YamlUtils
+{
+ #region Constants
+
+ public const string YamlFileExtension = ".yaml";
+ public const string YmlFileExtension = ".yml";
+ public const string YamlFxFileExtension = ".fx.yaml";
+
+ #endregion
+
+ ///
+ /// Checks if the given path is a yaml file.
+ ///
+ ///
+ ///
+ public static bool IsYamlFile(string path)
+ {
+ return
+ Path.GetExtension(path).Equals(YamlFileExtension, StringComparison.OrdinalIgnoreCase) ||
+ Path.GetExtension(path).Equals(YmlFileExtension, StringComparison.OrdinalIgnoreCase);
+ }
+}
+