diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Commands/PublishCommand.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Commands/PublishCommand.cs index dd64de81..37d96bed 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Commands/PublishCommand.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Commands/PublishCommand.cs @@ -167,6 +167,19 @@ public static Command CreateCommand( logger.LogInformation("Please customize the manifest files before publishing"); } + // Ensure agenticUserTemplateManifest.json exists in the manifest directory. + // It may be missing if the manifest directory was created by a previous partial run + // or an older CLI version that did not include this file. + if (!File.Exists(agenticUserManifestTemplatePath)) + { + logger.LogInformation("agenticUserTemplateManifest.json not found. Extracting from embedded resources..."); + if (!manifestTemplateService.EnsureTemplateFile(manifestDir, "agenticUserTemplateManifest.json")) + { + logger.LogError("Failed to extract agenticUserTemplateManifest.json from embedded resources"); + return; + } + } + if (!File.Exists(manifestPath)) { logger.LogError("Manifest file not found at {Path}", manifestPath); @@ -258,9 +271,10 @@ public static Command CreateCommand( try { File.Delete(zipPath); } catch { /* ignore */ } } - // Identify up to 4 files (manifest.json + icons + any additional up to 4 total) + // Identify files to include in zip; agenticUserTemplateManifest.json is explicitly listed + // to ensure it is always included regardless of other files present in the directory var expectedFiles = new List(); - string[] candidateNames = ["manifest.json", "color.png", "outline.png", "logo.png", "icon.png"]; + string[] candidateNames = ["manifest.json", "agenticUserTemplateManifest.json", "color.png", "outline.png", "logo.png", "icon.png"]; foreach (var name in candidateNames) { var p = Path.Combine(manifestDir, name); diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/ManifestTemplateService.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/ManifestTemplateService.cs index c09217c7..00d254d6 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/ManifestTemplateService.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/ManifestTemplateService.cs @@ -74,6 +74,47 @@ public bool ExtractTemplates(string workingDirectory) } } + /// + /// Extracts a single embedded template file to the working directory only if it does not already exist. + /// Existing files are not overwritten, preserving any user customizations or previously applied blueprint IDs. + /// Use this method to recover missing files from a manifest directory created by an older CLI version + /// or a partial previous run, without disturbing other files in the directory. + /// + /// Directory to extract the template to + /// Name of the embedded template file (e.g., "agenticUserTemplateManifest.json") + /// True if the file already exists or was successfully extracted + public bool EnsureTemplateFile(string workingDirectory, string fileName) + { + var targetPath = Path.Combine(workingDirectory, fileName); + if (File.Exists(targetPath)) + { + return true; + } + + try + { + var fullResourceName = $"{ResourcePrefix}{fileName}"; + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fullResourceName); + + if (stream == null) + { + _logger.LogError("Embedded resource not found: {Resource}", fullResourceName); + return false; + } + + using var fileStream = File.Create(targetPath); + stream.CopyTo(fileStream); + + _logger.LogDebug("Extracted template: {File}", fileName); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to extract template file {File}", fileName); + return false; + } + } + /// /// Updates manifest files with agent-specific identifiers. /// diff --git a/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/ManifestTemplateServiceTests.cs b/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/ManifestTemplateServiceTests.cs index 966b2f28..d65719ff 100644 --- a/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/ManifestTemplateServiceTests.cs +++ b/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/ManifestTemplateServiceTests.cs @@ -486,6 +486,65 @@ public void TryGetExistingManifestDirectory_ReturnsFalse_WhenProjectPathIsNull() #endregion + #region EnsureTemplateFile Tests + + [Fact] + public void EnsureTemplateFile_ReturnsTrue_WhenFileAlreadyExists() + { + // Arrange + var existingContent = "existing content"; + var filePath = Path.Combine(_testDirectory, "agenticUserTemplateManifest.json"); + File.WriteAllText(filePath, existingContent); + + // Act + var result = _service.EnsureTemplateFile(_testDirectory, "agenticUserTemplateManifest.json"); + + // Assert + result.Should().BeTrue(); + // Existing file should NOT be overwritten + File.ReadAllText(filePath).Should().Be(existingContent); + } + + [Fact] + public void EnsureTemplateFile_ExtractsFile_WhenFileMissing() + { + // Arrange - directory exists but file is absent + var filePath = Path.Combine(_testDirectory, "agenticUserTemplateManifest.json"); + File.Exists(filePath).Should().BeFalse(); + + // Act + var result = _service.EnsureTemplateFile(_testDirectory, "agenticUserTemplateManifest.json"); + + // Assert + result.Should().BeTrue(); + File.Exists(filePath).Should().BeTrue(); + new FileInfo(filePath).Length.Should().BeGreaterThan(0); + } + + [Fact] + public void EnsureTemplateFile_ExtractedFile_ContainsValidJson() + { + // Act + _service.EnsureTemplateFile(_testDirectory, "agenticUserTemplateManifest.json"); + + // Assert + var content = File.ReadAllText(Path.Combine(_testDirectory, "agenticUserTemplateManifest.json")); + var act = () => System.Text.Json.JsonDocument.Parse(content); + act.Should().NotThrow(); + } + + [Fact] + public void EnsureTemplateFile_ReturnsFalse_WhenResourceNameIsInvalid() + { + // Act + var result = _service.EnsureTemplateFile(_testDirectory, "nonexistent-file.json"); + + // Assert + result.Should().BeFalse(); + } + + #endregion + #region ValidateManifestFormatAsync Tests [Fact]