public static bool DiffStressTest(string pathToMsApp) { (CanvasDocument doc1, var errors) = CanvasDocument.LoadFromMsapp(pathToMsApp); errors.ThrowOnErrors(); return(HasNoDeltas(doc1, doc1)); }
private static void AddControl(CanvasDocument app, string filePath, bool isComponent, string fileContents, ErrorContainer errors) { var filename = Path.GetFileName(filePath); try { var parser = new Parser.Parser(filePath, fileContents, errors); var controlIR = parser.ParseControl(); if (controlIR == null) { return; // error condition } // validate that all the packages refferred are not accidentally deleted from pkgs dierectory ValidateIfTemplateExists(app, controlIR, controlIR, errors); // Since the TestSuites are sharded into individual files make sure to add them as children of AppTest control if (AppTestTransform.IsTestSuite(controlIR.Name.Kind.TypeName)) { AddTestSuiteControl(app, controlIR); } else { var collection = (isComponent) ? app._components : app._screens; collection.Add(controlIR.Name.Identifier, controlIR); } } catch (DocumentException) { // On DocumentException, continue looking for errors in other files. } }
private static void LoadTemplateFiles(ErrorContainer errors, CanvasDocument app, string packagesPath, out Dictionary <string, ControlTemplate> loadedTemplates) { loadedTemplates = new Dictionary <string, ControlTemplate>(); var templateList = new List <TemplatesJson.TemplateJson>(); foreach (var file in new DirectoryReader(packagesPath).EnumerateFiles(string.Empty, "*.xml", searchSubdirectories: false)) { var xmlContents = file.GetContents(); if (!ControlTemplateParser.TryParseTemplate(new TemplateStore(), xmlContents, app._properties.DocumentAppType, loadedTemplates, out var parsedTemplate, out var templateName)) { errors.GenericError($"Unable to parse template file {file._relativeName}"); throw new DocumentException(); } // Some control templates specify a name with an initial capital letter (e.g. rating control) // However, the server doesn't always use that. If the template name doesn't match the one we wrote // as the file name, adjust the template name to lowercase if (!file._relativeName.StartsWith(templateName)) { templateName = templateName.ToLower(); } templateList.Add(new TemplatesJson.TemplateJson() { Name = templateName, Template = xmlContents, Version = parsedTemplate.Version }); } // Also add Screen and App templates (not xml, constructed in code on the server) GlobalTemplates.AddCodeOnlyTemplates(new TemplateStore(), loadedTemplates, app._properties.DocumentAppType); app._templates = new TemplatesJson() { UsedTemplates = templateList.ToArray() }; }
/// <summary> /// Adds the entries of LocalFile assets to the Resources.json in an ordered manner. /// </summary> /// <param name="app">The app.</param> public static void AddLocalAssetEntriesToResourceJson(this CanvasDocument app) { var localFileResourceJsonEntries = new List <ResourceJson>(); // Iterate through local asset files to add their entries back to Resources.Json foreach (var file in app._assetFiles) { var fileName = file.Key.GetFileName(); if (!app._resourcesJson.Resources.Any(x => x.FileName == fileName) && !IsLogoFile(file.Key, app)) { localFileResourceJsonEntries.Add(GenerateResourceJsonEntryFromAssetFile(file.Key)); } } app._resourcesJson.Resources = app._resourcesJson.Resources.Concat(localFileResourceJsonEntries).ToArray(); // Bring the order of resourceJson back to avoid checksum violation. if (app._entropy?.ResourcesJsonIndices != null && app._entropy.ResourcesJsonIndices.Count > 0) { var orderedResourcesList = new List <ResourceJson>(); var orderedIndices = app._entropy.ResourcesJsonIndices.OrderBy(x => x.Value); foreach (var kvp in orderedIndices) { var resourceName = app._entropy.GetResourceNameFromKey(kvp.Key); var resource = app._resourcesJson.Resources.Where(x => x.Name == resourceName); orderedResourcesList.Add(resource.SingleOrDefault()); } // Handle the cases when some new files were added to the asset folder offline. The entries for the new assets would go at the end, after all the ordered resources have been added. orderedResourcesList.AddRange(app._resourcesJson.Resources.Where(x => !app._entropy.ResourcesJsonIndices.ContainsKey(app._entropy.GetResourcesJsonIndicesKey(x)))); app._resourcesJson.Resources = orderedResourcesList.ToArray(); } }
/// <summary> /// Persists the original order of resources in Resources.json in Entropy. /// </summary> /// <param name="app">The app.</param> public static void PersistOrderingOfResourcesJsonEntries(this CanvasDocument app) { for (var i = 0; i < app._resourcesJson.Resources.Length; i++) { app._entropy.Add(app._resourcesJson.Resources[i], i); } }
/// This writes out the IR, editor state cache, and potentially component templates /// for a single top level control, such as the App object, a screen, or component /// Name refers to the control name private static void WriteTopParent( DirectoryWriter dir, CanvasDocument app, string name, BlockNode ir, string subDir) { var controlName = name; var text = PAWriterVisitor.PrettyPrint(ir); string filename = controlName + ".pa.yaml"; dir.WriteAllText(subDir, filename, text); var extraData = new Dictionary <string, ControlState>(); foreach (var item in app._editorStateStore.GetControlsWithTopParent(controlName)) { extraData.Add(item.Name, item); } // Write out of all the other state for roundtripping string extraContent = controlName + ".editorstate.json"; dir.WriteAllJson(EditorStateDir, extraContent, extraData); // Write out component templates next to the component if (app._templateStore.TryGetTemplate(name, out var templateState)) { dir.WriteAllJson(subDir, controlName + ".json", templateState); } }
private static void LoadPcfControlTemplateFiles(ErrorContainer errors, CanvasDocument app, string paControlTemplatesPath) { foreach (var file in new DirectoryReader(paControlTemplatesPath).EnumerateFiles("", "*.json")) { var pcfControl = file.ToObject <PcfControl>(); app._pcfControls.Add(pcfControl.Name, file.ToObject <PcfControl>()); } }
// Given an msapp (original source of truth), stress test the conversions public static bool StressTest(string pathToMsApp) { try { using (var temp1 = new TempFile()) { string outFile = temp1.FullPath; var log = TextWriter.Null; // MsApp --> Model CanvasDocument msapp; ErrorContainer errors = new ErrorContainer(); try { msapp = MsAppSerializer.Load(pathToMsApp, errors); // read errors.ThrowOnErrors(); } catch (NotSupportedException) { errors.FormatNotSupported($"Too old: {pathToMsApp}"); return(false); } // Model --> MsApp errors = msapp.SaveToMsApp(outFile); errors.ThrowOnErrors(); var ok = MsAppTest.Compare(pathToMsApp, outFile, log); if (!ok) { return(false); } // Model --> Source using (var tempDir = new TempDir()) { string outSrcDir = tempDir.Dir; errors = msapp.SaveToSources(outSrcDir); errors.ThrowOnErrors(); // Source --> Model (var msapp2, var errors2) = CanvasDocument.LoadFromSources(outSrcDir); errors2.ThrowOnErrors(); errors2 = msapp2.SaveToMsApp(outFile); // Write out .pa files. errors2.ThrowOnErrors(); var ok2 = MsAppTest.Compare(pathToMsApp, outFile, log); return(ok2); } } } catch (Exception e) { Console.WriteLine(e.ToString()); return(false); } }
private static void LoadSourceFiles(CanvasDocument app, DirectoryReader directory, Dictionary <string, ControlTemplate> templateDefaults, ErrorContainer errors) { foreach (var file in directory.EnumerateFiles(EditorStateDir, "*.json")) { if (!file._relativeName.EndsWith(".editorstate.json")) { errors.FormatNotSupported($"Unexpected file present in {EditorStateDir}"); throw new DocumentException(); } // Json peer to a .pa file. var controlExtraData = file.ToObject <Dictionary <string, ControlState> >(); var topParentName = file._relativeName.Replace(".editorstate.json", ""); foreach (var control in controlExtraData) { control.Value.TopParentName = topParentName; if (!app._editorStateStore.TryAddControl(control.Value)) { // Can't have duplicate control names. // This might happen due to a bad merge. errors.EditorStateError(file.SourceSpan, $"Control '{control.Value.Name}' is already defined."); } } } // For now, the Themes file lives in CodeDir as a json file // We'd like to make this .pa.yaml as well eventually foreach (var file in directory.EnumerateFiles(CodeDir, "*.json", searchSubdirectories: false)) { if (Path.GetFileName(file._relativeName) == "Themes.json") { app._themes = file.ToObject <ThemesJson>(); } } foreach (var file in directory.EnumerateFiles(CodeDir, "*.pa.yaml", searchSubdirectories: false)) { AddControl(app, file._relativeName, false, file.GetContents(), errors); } foreach (var file in EnumerateComponentDirs(directory, "*.pa.yaml")) { AddControl(app, file._relativeName, true, file.GetContents(), errors); } foreach (var file in directory.EnumerateFiles(TestDir, "*.pa.yaml")) { AddControl(app, file._relativeName, false, file.GetContents(), errors); } foreach (var file in EnumerateComponentDirs(directory, "*.json")) { var componentTemplate = file.ToObject <CombinedTemplateState>(); app._templateStore.AddTemplate(componentTemplate.ComponentManifest.Name, componentTemplate); } }
/// This writes out the IR, editor state cache, and potentially component templates /// for a single top level control, such as the App object, a screen, or component /// Name refers to the control name /// Only in case of AppTest, the topParentName is passed down, since for AppTest the TestSuites are sharded into individual files. /// We truncate the control names to limit it to 50 charactes length (escaped name). private static void WriteTopParent( DirectoryWriter dir, CanvasDocument app, string name, BlockNode ir, string subDir, string topParentName = null) { var controlName = name; var newControlName = Utilities.TruncateNameIfTooLong(controlName); string filename = newControlName + ".fx.yaml"; // For AppTest control shard each test suite into individual files. if (controlName == AppTestControlName) { foreach (var child in ir.Children) { WriteTopParent(dir, app, child.Properties.FirstOrDefault(x => x.Identifier == "DisplayName").Expression.Expression.Trim(new char[] { '"' }), child, subDir, controlName); } // Clear the children since they have already been sharded into their individual files. ir.Children.Clear(); } var text = PAWriterVisitor.PrettyPrint(ir); dir.WriteAllText(subDir, filename, text); // For TestSuite controls, only the top parent control has an editor state created. // For other control types, create an editor state. if (string.IsNullOrEmpty(topParentName)) { string editorStateFilename = $"{newControlName}.editorstate.json"; var controlStates = new Dictionary <string, ControlState>(); foreach (var item in app._editorStateStore.GetControlsWithTopParent(controlName)) { controlStates.Add(item.Name, item); } ControlTreeState editorState = new ControlTreeState { ControlStates = controlStates, TopParentName = controlName }; // Write out of all the other state properties on the control for roundtripping. dir.WriteAllJson(EditorStateDir, editorStateFilename, editorState); } // Write out component templates next to the component if (app._templateStore.TryGetTemplate(name, out var templateState)) { dir.WriteAllJson(subDir, newControlName + ".json", templateState); } }
public static bool TestClone(string pathToMsApp) { (CanvasDocument doc1, var errors) = CanvasDocument.LoadFromMsapp(pathToMsApp); errors.ThrowOnErrors(); var docClone = new CanvasDocument(doc1); return(HasNoDeltas(doc1, docClone, strict: true)); }
private static void CreateControls(CanvasDocument app, IList <string> paFiles, Dictionary <string, ControlTemplate> templateDefaults, ErrorContainer errors) { foreach (var file in paFiles) { var fileEntry = new DirectoryReader.Entry(file); AddControl(app, file, false, fileEntry.GetContents(), errors); } }
private static void ComputeAndWriteChecksum(CanvasDocument app, ChecksumMaker checksum, ZipArchive z, ErrorContainer errors, bool isValidation) { var hash = checksum.GetChecksum(); if (app._checksum != null && hash.wholeChecksum != app._checksum.ClientStampedChecksum) { // These warnings are Debug only. Throwing a bunch of warning messages at the customer could lead to them ignoring real errors. #if DEBUG if (app._checksum.ClientPerFileChecksums != null) { foreach (var file in app._checksum.ClientPerFileChecksums) { if (!hash.perFileChecksum.TryGetValue(file.Key, out var fileChecksum)) { errors.ChecksumMismatch("Missing file " + file.Key); } else if (fileChecksum != file.Value) { errors.ChecksumMismatch($"File {file.Key} checksum does not match on extract"); } } foreach (var file in hash.perFileChecksum) { if (!app._checksum.ClientPerFileChecksums.ContainsKey(file.Key)) { errors.ChecksumMismatch("Extra file " + file.Key); } } } #endif #if !DEBUG // These are the non-debug warnings, if it's unpack this was a serious error, on -pack it's most likely not if (isValidation) { errors.PostUnpackValidationFailed(); throw new DocumentException(); } #endif errors.ChecksumMismatch("Checksum indicates that sources have been edited since they were unpacked. If this was intentional, ignore this warning."); } var checksumJson = new ChecksumJson { ClientStampedChecksum = hash.wholeChecksum, ClientPerFileChecksums = hash.perFileChecksum, ServerStampedChecksum = app._checksum?.ServerStampedChecksum, ServerPerFileChecksums = app._checksum?.ServerPerFileChecksums, }; var entry = ToFile(FileKind.Checksum, checksumJson); var e = z.CreateEntry(entry.Name.ToMsAppPath()); using (var dest = e.Open()) { dest.Write(entry.RawBytes, 0, entry.RawBytes.Length); } }
public static CanvasDocument Merge(CanvasDocument ours, CanvasDocument theirs, CanvasDocument commonParent) { var ourDeltas = Diff.ComputeDelta(commonParent, ours); var theirDeltas = Diff.ComputeDelta(commonParent, theirs); var resultDelta = UnionDelta(ourDeltas, theirDeltas); return(ApplyDelta(commonParent, resultDelta)); }
public static bool Compare(CanvasDocument doc1, CanvasDocument doc2, TextWriter log) { using (var temp1 = new TempFile()) using (var temp2 = new TempFile()) { doc1.SaveToMsApp(temp1.FullPath); doc2.SaveToMsApp(temp2.FullPath); return(Compare(temp1.FullPath, temp2.FullPath, log)); } }
private static void AddDefaultTheme(CanvasDocument app) { var assembly = Assembly.GetExecutingAssembly(); using var stream = assembly.GetManifestResourceStream(_defaultThemefileName); using var reader = new StreamReader(stream); var jsonString = reader.ReadToEnd(); app._themes = JsonSerializer.Deserialize <ThemesJson>(jsonString, Utility._jsonOpts); }
/// <summary> /// This method validates if the templates being references in the sources do exist. /// </summary> private static void ValidateIfTemplateExists(CanvasDocument app, BlockNode node, BlockNode root, ErrorContainer errors) { foreach (var child in node.Children) { // group, grouContainer, gallery etc. have nested controls so run the validation for all the children. if (child.Children?.Count > 0) { foreach (var child1 in child.Children) { ValidateIfTemplateExists(app, child1, root, errors); } } CombinedTemplateState templateState; app._templateStore.TryGetTemplate(child.Name.Kind.TypeName, out templateState); // Some of the child components don't have a template eg. TestStep, so we can safely continue if we can't find an entry in the templateStore. if (templateState == null) { continue; } // If its a widget template then there must be a xml file in the pkgs directory. if (templateState.IsWidgetTemplate) { if (!app._templates.UsedTemplates.Any(x => x.Name == child.Name.Kind.TypeName)) { errors.ValidationError(root.SourceSpan.GetValueOrDefault(), $"Widget control template: {templateState.Name}, version {templateState.Version} was not found in the pkgs directory and is referred in {root.Name.Identifier}. " + $"If the template was deleted intentionally please make sure to update the source files to remove the references to this template."); } continue; } // if its a component template then check if the template exists in the Src/Components directory else if (templateState.IsComponentTemplate == true) { if (!app._components.Keys.Any(x => x == child.Name.Kind.TypeName)) { errors.ValidationError(root.SourceSpan.GetValueOrDefault(), $"Component template: {templateState.Name} was not found in Src/Components directory and is referred in {root.Name.Identifier}. " + $"If the template was deleted intentionally please make sure to update the source files to remove the references to this template."); } continue; } // PCF are dynamically imported controls and their template definition is stored in the DynamicControlDefinitionJson property, check if that exists. else if (templateState.Id.StartsWith("http://microsoft.com/appmagic/powercontrol")) { if (!templateState.ExtensionData.ContainsKey(PAConstants.DynamicControlDefinitionJson) || templateState.ExtensionData[PAConstants.DynamicControlDefinitionJson] == null) { errors.ValidationError(root.SourceSpan.GetValueOrDefault(), $"Power control template: {templateState.Name} not found in ControlTemplates.json and is referred in {root.Name.Identifier}. " + $"If the template was deleted intentionally please make sure to update the source files to remove the references to this template. " + $"If not please check DynamicControlDefinitionJson property exists and is not null for this template in ControlTemplates.json"); } } } }
internal CanvasDocument(CanvasDocument other) { foreach (var kvp in other._unknownFiles) { _unknownFiles.Add(kvp.Key, new FileEntry(kvp.Value)); } foreach (var kvp in other._assetFiles) { _assetFiles.Add(kvp.Key, new FileEntry(kvp.Value)); } foreach (var kvp in other._screens) { _screens.Add(kvp.Key, kvp.Value.Clone()); } foreach (var kvp in other._components) { _components.Add(kvp.Key, kvp.Value.Clone()); } _editorStateStore = new EditorStateStore(other._editorStateStore); _templateStore = new TemplateStore(other._templateStore); _dataSources = other._dataSources.JsonClone(); _screenOrder = new List <string>(other._screenOrder); _header = other._header.JsonClone(); _properties = other._properties.JsonClone(); _parameterSchema = other._parameterSchema.JsonClone(); _publishInfo = other._publishInfo.JsonClone(); _templates = other._templates.JsonClone(); _themes = other._themes.JsonClone(); _resourcesJson = other._resourcesJson.JsonClone(); _appCheckerResultJson = other._appCheckerResultJson.JsonClone(); _pcfControls = other._pcfControls.JsonClone(); _appInsights = other._appInsights.JsonClone(); _connections = other._connections.JsonClone(); _dataSourceReferences = other._dataSourceReferences.JsonClone(); _libraryReferences = other._libraryReferences.JsonClone(); _logoFile = other._logoFile != null ? new FileEntry(other._logoFile) : null; _entropy = other._entropy.JsonClone(); _checksum = other._checksum.JsonClone(); this._idRestorer = new UniqueIdRestorer(this._entropy); _localAssetInfoJson = other._localAssetInfoJson.JsonClone(); }
/// This writes out the IR, editor state cache, and potentially component templates /// for a single top level control, such as the App object, a screen, or component /// Name refers to the control name /// Only in case of AppTest, the topParentName is passed down, since for AppTest the TestSuites are sharded into individual files. /// We truncate the control names to limit it to 50 charactes length (escaped name). private static void WriteTopParent( DirectoryWriter dir, CanvasDocument app, string name, BlockNode ir, string subDir, string topParentname = null) { var controlName = name; var newControlName = Utilities.TruncateNameIfTooLong(controlName); string filename = newControlName + ".fx.yaml"; // For AppTest control shard each test suite into individual file. if (controlName == AppTestControlName) { foreach (var child in ir.Children) { WriteTopParent(dir, app, child.Properties.FirstOrDefault(x => x.Identifier == "DisplayName").Expression.Expression.Trim(new char[] { '"' }), child, subDir, controlName); } // Clear the children since they have already been sharded into their individual files. ir.Children.Clear(); } var text = PAWriterVisitor.PrettyPrint(ir); dir.WriteAllText(subDir, filename, text); var extraData = new Dictionary <string, ControlState>(); foreach (var item in app._editorStateStore.GetControlsWithTopParent(topParentname ?? controlName)) { extraData.Add(item.Name, item); } // Write out of all the other state for roundtripping string extraContent = (topParentname ?? newControlName) + ".editorstate.json"; // We write editorstate.json file per top parent control, and hence for the TestSuite control since it is not a top parent // use the top parent name (i.e. Test_7F478737223C4B69) to create the editorstate.json file. if (!dir.FileExists(EditorStateDir, extraContent)) { dir.WriteAllJson(EditorStateDir, extraContent, extraData); } // Write out component templates next to the component if (app._templateStore.TryGetTemplate(name, out var templateState)) { dir.WriteAllJson(subDir, newControlName + ".json", templateState); } }
private static CanvasDocument ApplyDelta(CanvasDocument parent, IEnumerable <IDelta> delta) { var result = new CanvasDocument(parent); foreach (var change in delta) { change.Apply(result); } if (delta.Any()) { result._entropy = new Entropy(); } return(result); }
private static void ComputeAndWriteChecksum(CanvasDocument app, ChecksumMaker checksum, ZipArchive z, ErrorContainer errors) { var hash = checksum.GetChecksum(); if (hash.wholeChecksum != app._checksum.ClientStampedChecksum) { if (app._checksum.ClientPerFileChecksums != null) { // We had offline edits! errors.ChecksumMismatch("Sources have changed since when they were unpacked."); foreach (var file in app._checksum.ClientPerFileChecksums) { if (!hash.perFileChecksum.TryGetValue(file.Key, out var fileChecksum)) { errors.ChecksumMismatch("Missing file " + file.Key); } if (fileChecksum != file.Value) { errors.ChecksumMismatch($"File {file.Key} checksum does not match on extract"); } } foreach (var file in hash.perFileChecksum) { if (!app._checksum.ClientPerFileChecksums.ContainsKey(file.Key)) { errors.ChecksumMismatch("Extra file " + file.Key); } } } } var checksumJson = new ChecksumJson { ClientStampedChecksum = hash.wholeChecksum, ClientPerFileChecksums = hash.perFileChecksum, ServerStampedChecksum = app._checksum.ServerStampedChecksum, ServerPerFileChecksums = app._checksum.ServerPerFileChecksums, }; var entry = ToFile(FileKind.Checksum, checksumJson); var e = z.CreateEntry(entry.Name); using (var dest = e.Open()) { dest.Write(entry.RawBytes, 0, entry.RawBytes.Length); } }
// Verify there are no deltas (detected via smart merge) between doc1 and doc2 // Strict =true, also compare entropy files. private static bool HasNoDeltas(CanvasDocument doc1, CanvasDocument doc2, bool strict = false) { var ourDeltas = Diff.ComputeDelta(doc1, doc1); // ThemeDelta always added ourDeltas = ourDeltas.Where(x => x.GetType() != typeof(ThemeChange)).ToArray(); if (ourDeltas.Any()) { foreach (var diff in ourDeltas) { Console.WriteLine($" {diff.GetType().Name}"); } // Error! app shouldn't have any diffs with itself. return(false); } // Save and verify checksums. using (var temp1 = new TempFile()) using (var temp2 = new TempFile()) { doc1.SaveToMsApp(temp1.FullPath); doc2.SaveToMsApp(temp2.FullPath); bool same; if (strict) { same = Compare(temp1.FullPath, temp2.FullPath, Console.Out); } else { var doc1NoEntropy = RemoveEntropy(temp1.FullPath); var doc2NoEntropy = RemoveEntropy(temp2.FullPath); same = Compare(doc1NoEntropy, doc2NoEntropy, Console.Out); } if (!same) { return(false); } } return(true); }
// Get the original logo file (using entropy to get the old name) // And return a touched publishInfo pointing to it. public static (PublishInfoJson, FileEntry) TransformLogoOnSave(this CanvasDocument app) { FileEntry logoFile = null; var publishInfo = app._publishInfo.JsonClone(); if (!string.IsNullOrEmpty(publishInfo?.LogoFileName)) { app._assetFiles.Remove(app._logoFile.Name); publishInfo.LogoFileName = app._entropy.OldLogoFileName ?? Path.GetFileName(app._logoFile.Name); logoFile = new FileEntry { Name = @"Resources\" + publishInfo.LogoFileName, RawBytes = app._logoFile.RawBytes }; } return(publishInfo, logoFile); }
// The publish info points to the logo file. Grab it from the unknowns. private static void GetLogoFile(this CanvasDocument app) { // Logo file. if (!string.IsNullOrEmpty(app._publishInfo?.LogoFileName)) { string key = app._publishInfo.LogoFileName; FileEntry logoFile; if (app._assetFiles.TryGetValue(key, out logoFile)) { app._unknownFiles.Remove(key); app._logoFile = logoFile; } else { throw new InvalidOperationException($"Missing logo file {key}"); } } }
/// Adds TestSuite as a child control of AppTest control private static void AddTestSuiteControl(CanvasDocument app, BlockNode controlIR) { if (!app._screens.ContainsKey(AppTestControlName)) { app._screens.Add(AppTestControlName, new BlockNode() { Name = new TypedNameNode() { Identifier = AppTestControlName, Kind = new TypeNode() { TypeName = AppTestControlType } } }); } app._screens[AppTestControlName].Children.Add(controlIR); }
/// <summary> /// This method validates if the templates being references in the sources do exist. /// </summary> private static void ValidateIfTemplateExists(CanvasDocument app, BlockNode node, BlockNode root, ErrorContainer errors) { foreach (var child in node.Children) { // group, grouContainer, gallery etc. have nested controls so run the validation for all the children. if (child.Children?.Count > 0) { foreach (var child1 in child.Children) { ValidateIfTemplateExists(app, child1, root, errors); } } CombinedTemplateState templateState; app._templateStore.TryGetTemplate(child.Name.Kind.TypeName, out templateState); // Some of the child components don't have a template eg. TestStep, so we can safely continue if we can't find an entry in the templateStore. if (templateState == null) { continue; } // If its a widget template then there must be a xml file in the pkgs directory. if (templateState.IsWidgetTemplate) { if (!app._templates.UsedTemplates.Any(x => x.Name == child.Name.Kind.TypeName)) { errors.ValidationError(root.SourceSpan.GetValueOrDefault(), $"Widget control template: {templateState.TemplateDisplayName}, version {templateState.Version} was not found in the pkgs directory and is referred in {root.Name.Identifier}. " + $"If the template was deleted intentionally please make sure to update the source files to remove the references to this template."); } continue; } // if its a component template then check if the template exists in the Src/Components directory else if (templateState.IsComponentTemplate == true) { if (!app._components.Keys.Any(x => x == child.Name.Kind.TypeName)) { errors.ValidationError(root.SourceSpan.GetValueOrDefault(), $"Component template: {templateState.TemplateDisplayName} was not found in Src/Components directory and is referred in {root.Name.Identifier}. " + $"If the template was deleted intentionally please make sure to update the source files to remove the references to this template."); } continue; } } }
public static CanvasDocument Create(string appName, string packagesPath, IList <string> paFiles, ErrorContainer errors) { var app = new CanvasDocument(); app._properties = DocumentPropertiesJson.CreateDefault(appName); app._header = HeaderJson.CreateDefault(); LoadTemplateFiles(errors, app, packagesPath, out var loadedTemplates); app._entropy = new Entropy(); app._checksum = new ChecksumJson() { ClientStampedChecksum = "Foo", ClientBuildDetails = _buildVerJson }; AddDefaultTheme(app); CreateControls(app, paFiles, loadedTemplates, errors); return(app); }
private static void AddControl(CanvasDocument app, string filePath, bool isComponent, string fileContents, ErrorContainer errors) { var filename = Path.GetFileName(filePath); try { var parser = new Parser.Parser(filePath, fileContents, errors); var controlIR = parser.ParseControl(); if (controlIR == null) { return; // error condition } var collection = (isComponent) ? app._components : app._screens; collection.Add(controlIR.Name.Identifier, controlIR); } catch (DocumentException) { // On DocumentException, continue looking for errors in other files. } }
// Write back out to a msapp file. public static void SaveAsMsApp(CanvasDocument app, string fullpathToMsApp, ErrorContainer errors, bool isValidation = false) { app.ApplyBeforeMsAppWriteTransforms(errors); if (!fullpathToMsApp.EndsWith(".msapp", StringComparison.OrdinalIgnoreCase) && fullpathToMsApp.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Only works for .msapp files"); } if (File.Exists(fullpathToMsApp)) // Overwrite! { File.Delete(fullpathToMsApp); } var checksum = new ChecksumMaker(); DirectoryWriter.EnsureFileDirExists(fullpathToMsApp); using (var z = ZipFile.Open(fullpathToMsApp, ZipArchiveMode.Create)) { foreach (FileEntry entry in app.GetMsAppFiles(errors)) { if (entry != null) { var e = z.CreateEntry(entry.Name.ToMsAppPath()); using (var dest = e.Open()) { dest.Write(entry.RawBytes, 0, entry.RawBytes.Length); checksum.AddFile(entry.Name.ToMsAppPath(), entry.RawBytes); } } } ComputeAndWriteChecksum(app, checksum, z, errors, isValidation); } // Undo BeforeWrite transforms so CanvasDocument representation is unchanged app.ApplyAfterMsAppLoadTransforms(errors); }
/// <summary> /// Save the document in a textual source format that can be checked into source control /// </summary> /// <param name="pathToSourceDirectory"></param> /// <param name="verifyOriginalPath">true if we should immediately repack the sources to verify they successfully roundtrip. </param> /// <returns></returns> public ErrorContainer SaveToSources(string pathToSourceDirectory, string verifyOriginalPath = null) { Utilities.EnsurePathRooted(pathToSourceDirectory); var errors = new ErrorContainer(); Wrapper(() => SourceSerializer.SaveAsSource(this, pathToSourceDirectory, errors), errors); // Test that we can repack if (!errors.HasErrors && verifyOriginalPath != null) { (CanvasDocument msApp2, ErrorContainer errors2) = CanvasDocument.LoadFromSources(pathToSourceDirectory); if (errors2.HasErrors) { errors2.PostUnpackValidationFailed(); return(errors2); } using (var temp = new TempFile()) { errors2 = msApp2.SaveToMsAppValidation(temp.FullPath); if (errors2.HasErrors) { errors2.PostUnpackValidationFailed(); return(errors2); } bool ok = MsAppTest.Compare(verifyOriginalPath, temp.FullPath, TextWriter.Null); if (!ok) { errors2.PostUnpackValidationFailed(); return(errors2); } } } return(errors); }