Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/source/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ The build subcommand will build a Chloroplast site
chloroplast build --root path/to/SiteConfig/ --out path/to/out
```

**Note:** The build command will clear all contents of the output directory before building. This ensures that no old or extraneous files remain in the output from previous builds.

### parameters

- `root`: the path to the directory that contains a `SiteConfig.yml` file
Expand Down
136 changes: 136 additions & 0 deletions toolsrc/Chloroplast.Test/BuildCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Chloroplast.Core;
using Chloroplast.Core.Extensions;
using Chloroplast.Tool.Commands;
using Microsoft.Extensions.Configuration;
using Xunit;

namespace Chloroplast.Test
{
public class BuildCommandTests
{
[Fact]
public async Task BuildCommand_ShouldClearOutputDirectory_BeforeBuilding()
{
// Arrange: Create temp directory structure
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
var sourceDir = Path.Combine(tempDir, "source");
var outputDir = Path.Combine(tempDir, "out");

Directory.CreateDirectory(sourceDir);
Directory.CreateDirectory(outputDir);

// Create a test markdown file in the source directory
var testMdFile = Path.Combine(sourceDir, "test.md");
File.WriteAllText(testMdFile, "---\ntitle: Test Page\n---\n# Test Content");

// Create a dummy file in the output directory that should be cleared
var dummyFile = Path.Combine(outputDir, "old-file.txt");
File.WriteAllText(dummyFile, "This should be deleted");

// Create a dummy subdirectory in the output directory
var dummyDir = Path.Combine(outputDir, "old-directory");
Directory.CreateDirectory(dummyDir);
var dummyFileInDir = Path.Combine(dummyDir, "nested-file.txt");
File.WriteAllText(dummyFileInDir, "This should also be deleted");

try
{
// Verify files exist before build
Assert.True(File.Exists(dummyFile), "Dummy file should exist before build");
Assert.True(Directory.Exists(dummyDir), "Dummy directory should exist before build");

// Create config
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("root", tempDir),
new KeyValuePair<string, string>("out", outputDir),
new KeyValuePair<string, string>("normalizePaths", "true")
})
.Build();

// Act: Run the ClearOutputDirectory method via reflection
var buildCommand = new FullBuildCommand();
var method = typeof(FullBuildCommand).GetMethod("ClearOutputDirectory",
BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(buildCommand, new object[] { config });

// Assert: Verify files were deleted
Assert.False(File.Exists(dummyFile), "Dummy file should be deleted after clearing");
Assert.False(Directory.Exists(dummyDir), "Dummy directory should be deleted after clearing");
Assert.True(Directory.Exists(outputDir), "Output directory itself should still exist");
}
finally
{
// Cleanup
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, recursive: true);
}
}
}

[Fact]
public void ClearOutputDirectory_ShouldHandleNonExistentDirectory()
{
// Arrange: Create temp directory that doesn't have an output dir yet
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
var outputDir = Path.Combine(tempDir, "out");

try
{
// Create config pointing to non-existent output dir
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("out", outputDir)
})
.Build();

// Act: Run the ClearOutputDirectory method - should not throw
var buildCommand = new FullBuildCommand();
var method = typeof(FullBuildCommand).GetMethod("ClearOutputDirectory",
BindingFlags.NonPublic | BindingFlags.Instance);

// Assert: Should complete without throwing an exception
var exception = Record.Exception(() => method.Invoke(buildCommand, new object[] { config }));
Assert.Null(exception);
}
finally
{
// Cleanup
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, recursive: true);
}
}
}

[Fact]
public void ClearOutputDirectory_ShouldHandleEmptyOutputPath()
{
// Arrange: Create config with empty output path
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("out", "")
})
.Build();

// Act: Run the ClearOutputDirectory method - should not throw
var buildCommand = new FullBuildCommand();
var method = typeof(FullBuildCommand).GetMethod("ClearOutputDirectory",
BindingFlags.NonPublic | BindingFlags.Instance);

// Assert: Should complete without throwing an exception
var exception = Record.Exception(() => method.Invoke(buildCommand, new object[] { config }));
Assert.Null(exception);
}
}
}
2 changes: 1 addition & 1 deletion toolsrc/Chloroplast.Tool/Chloroplast.Tool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageOutputPath>./nupkg</PackageOutputPath>
<IsPackable>true</IsPackable>

<Version Condition="'$(Version)' == ''">0.13.0</Version>
<Version Condition="'$(Version)' == ''">0.14.0</Version>
<Authors>Wilderness Labs</Authors>
<Company>Wilderness Labs</Company>
<PackageDescription>Markdown-based static site generator</PackageDescription>
Expand Down
64 changes: 64 additions & 0 deletions toolsrc/Chloroplast.Tool/Commands/FullBuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public FullBuildCommand ()

public async Task<IEnumerable<Task>> RunAsync (IConfigurationRoot config)
{
// Clear the output directory before building
ClearOutputDirectory(config);

// Set up build version for cache busting
SetupBuildVersion(config);

Expand Down Expand Up @@ -253,6 +256,67 @@ public async Task<IEnumerable<Task>> RunAsync (IConfigurationRoot config)
return tasks;
}

private void ClearOutputDirectory(IConfigurationRoot config)
{
var outPath = config["out"]?.NormalizePath();

if (string.IsNullOrWhiteSpace(outPath))
{
Console.WriteLine("Warning: Output path not configured, skipping directory clearing");
return;
}

if (!Directory.Exists(outPath))
{
Console.WriteLine($"Output directory does not exist yet: {outPath}");
return;
}

try
{
Console.WriteLine($"Clearing output directory: {outPath}");

// Delete all files and subdirectories in the output directory
var dirInfo = new DirectoryInfo(outPath);

foreach (var file in dirInfo.GetFiles())
{
try
{
// Clear read-only attribute if set
if (file.IsReadOnly)
{
file.IsReadOnly = false;
}
file.Delete();
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Could not delete file {file.Name}: {ex.Message}");
}
}

foreach (var dir in dirInfo.GetDirectories())
{
try
{
dir.Delete(recursive: true);
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Could not delete directory {dir.Name}: {ex.Message}");
}
}

Console.WriteLine("Output directory cleared successfully");
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Failed to clear output directory: {ex.Message}");
// Don't fail the build if clearing fails - just warn
}
}

private void SetupBuildVersion(IConfigurationRoot config)
{
// Check if cache busting is enabled (default to true)
Expand Down
Loading