Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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>();
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,47 @@ public bool ExtractTemplates(string workingDirectory)
}
}

/// <summary>
/// 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.
/// </summary>
/// <param name="workingDirectory">Directory to extract the template to</param>
/// <param name="fileName">Name of the embedded template file (e.g., "agenticUserTemplateManifest.json")</param>
/// <returns>True if the file already exists or was successfully extracted</returns>
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;
}
}

/// <summary>
/// Updates manifest files with agent-specific identifiers.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down