// Write out to a directory (this shards it) public static void SaveAsSource(CanvasDocument app, string directory2, ErrorContainer errors) { var dir = new DirectoryWriter(directory2); dir.DeleteAllSubdirs(); // Shard templates, parse for default values var templateDefaults = new Dictionary <string, ControlTemplate>(); foreach (var template in app._templates.UsedTemplates) { var filename = $"{template.Name}_{template.Version}.xml"; dir.WriteAllXML(PackagesDir, filename, template.Template); if (!ControlTemplateParser.TryParseTemplate(app._templateStore, template.Template, app._properties.DocumentAppType, templateDefaults, out _, out _)) { throw new NotSupportedException($"Unable to parse template file {template.Name}"); } } // Also add Screen and App templates (not xml, constructed in code on the server) GlobalTemplates.AddCodeOnlyTemplates(app._templateStore, templateDefaults, app._properties.DocumentAppType); var importedComponents = app.GetImportedComponents(); foreach (var control in app._screens) { string controlName = control.Key; var isTest = controlName == AppTestControlName; var subDir = isTest ? TestDir : CodeDir; WriteTopParent(dir, app, control.Key, control.Value, subDir); } foreach (var control in app._components) { string controlName = control.Key; app._templateStore.TryGetTemplate(controlName, out var templateState); bool isImported = importedComponents.Contains(templateState.TemplateOriginalName); var subDir = (isImported) ? ComponentPackageDir : ComponentCodeDir; WriteTopParent(dir, app, control.Key, control.Value, subDir); } // Write out control templates at top level, skipping component templates which are written alongside components var nonComponentControlTemplates = app._templateStore.Contents.Where(kvp => !(kvp.Value.IsComponentTemplate ?? false)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); dir.WriteAllJson("", "ControlTemplates.json", nonComponentControlTemplates); if (app._checksum != null) { app._checksum.ClientBuildDetails = _buildVerJson; dir.WriteAllJson(EntropyDir, FileKind.Checksum, app._checksum); } if (app._appCheckerResultJson != null) { dir.WriteAllJson(EntropyDir, FileKind.AppCheckerResult, app._appCheckerResultJson); } foreach (var file in app._assetFiles.Values) { dir.WriteAllBytes(AssetsDir, file.Name, file.RawBytes); } if (app._logoFile != null) { dir.WriteAllBytes(AssetsDir, app._logoFile.Name, app._logoFile.RawBytes); } if (app._themes != null) { dir.WriteAllJson(CodeDir, "Themes.json", app._themes); } if (app._resourcesJson != null) { dir.WriteAllJson(AssetsDir, "Resources.json", app._resourcesJson); } WriteDataSources(dir, app, errors); // Loose files. foreach (FileEntry file in app._unknownFiles.Values) { // Standardize the .json files so they're determinsitc and comparable if (file.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { ReadOnlyMemory <byte> span = file.RawBytes; var je = JsonDocument.Parse(span).RootElement; var jsonStr = JsonNormalizer.Normalize(je); dir.WriteAllText(OtherDir, file.Name, jsonStr); } else { dir.WriteAllBytes(OtherDir, file.Name, file.RawBytes); } } var manifest = new CanvasManifestJson { FormatVersion = CurrentSourceVersion, Properties = app._properties, Header = app._header, PublishInfo = app._publishInfo, ScreenOrder = app._screenOrder }; dir.WriteAllJson("", FileKind.CanvasManifest, manifest); if (app._connections != null) { dir.WriteAllJson(ConnectionDir, FileKind.Connections, app._connections); } if (app._libraryReferences != null) { dir.WriteAllJson("", FileKind.ComponentReferences, app._libraryReferences); } dir.WriteAllJson(EntropyDir, FileKind.Entropy, app._entropy); }