diff --git a/src/Blog/PostPage/IndexHtmlFile.cs b/src/Blog/PostPage/IndexHtmlFile.cs
new file mode 100644
index 0000000..0e4435f
--- /dev/null
+++ b/src/Blog/PostPage/IndexHtmlFile.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Markdig;
+using OwlCore.Storage;
+using Scriban;
+using YamlDotNet.Serialization;
+
+namespace WindowsAppCommunity.Blog.PostPage
+{
+ ///
+ /// Virtual IChildFile representing index.html generated from markdown source.
+ /// Implements lazy generation - markdown→HTML transformation occurs on OpenStreamAsync.
+ /// Read-only - throws NotSupportedException for write operations.
+ ///
+ public sealed class IndexHtmlFile : IChildFile
+ {
+ private readonly string _id;
+ private readonly IFile _markdownSource;
+ private readonly IStorable _templateSource;
+ private readonly string? _templateFileName;
+ private readonly IFolder? _parent;
+
+ ///
+ /// Creates virtual index.html file with lazy markdown→HTML generation.
+ ///
+ /// Unique identifier for this file (parent-derived)
+ /// Source markdown file to transform
+ /// Template as IFile or IFolder
+ /// Template file name when source is IFolder (defaults to "template.html")
+ /// Parent folder in virtual hierarchy (optional)
+ public IndexHtmlFile(string id, IFile markdownSource, IStorable templateSource, string? templateFileName, IFolder? parent = null)
+ {
+ _id = id ?? throw new ArgumentNullException(nameof(id));
+ _markdownSource = markdownSource ?? throw new ArgumentNullException(nameof(markdownSource));
+ _templateSource = templateSource ?? throw new ArgumentNullException(nameof(templateSource));
+ _templateFileName = templateFileName;
+ _parent = parent;
+ }
+
+ ///
+ public string Id => _id;
+
+ ///
+ public string Name => "index.html";
+
+ ///
+ /// File creation timestamp from filesystem metadata.
+ ///
+ public DateTime? Created { get; set; }
+
+ ///
+ /// File modification timestamp from filesystem metadata.
+ ///
+ public DateTime? Modified { get; set; }
+
+ ///
+ public Task GetParentAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(_parent);
+ }
+
+ ///
+ public async Task OpenStreamAsync(FileAccess accessMode, CancellationToken cancellationToken = default)
+ {
+ // Read-only file - reject write operations
+ if (accessMode == FileAccess.Write || accessMode == FileAccess.ReadWrite)
+ {
+ throw new NotSupportedException($"IndexHtmlFile is read-only. Cannot open with access mode: {accessMode}");
+ }
+
+ // Lazy generation: Transform markdown→HTML on every call (no caching)
+ var html = await GenerateHtmlAsync(cancellationToken);
+
+ // Convert HTML string to UTF-8 byte stream
+ var bytes = Encoding.UTF8.GetBytes(html);
+ var stream = new MemoryStream(bytes);
+ stream.Position = 0;
+
+ return stream;
+ }
+
+ ///
+ /// Generate HTML by transforming markdown source with template.
+ /// Orchestrates: Parse markdown → Transform to HTML → Render template.
+ ///
+ private async Task GenerateHtmlAsync(CancellationToken cancellationToken)
+ {
+ // Parse markdown file (extract front-matter + content)
+ var (frontmatter, content) = await ParseMarkdownAsync(_markdownSource);
+
+ // Transform markdown content to HTML body
+ var htmlBody = TransformMarkdownToHtml(content);
+
+ // Parse front-matter YAML to dictionary
+ var frontmatterDict = ParseFrontmatter(frontmatter);
+
+ // Resolve template file from IStorable source
+ var templateFile = await ResolveTemplateFileAsync(_templateSource, _templateFileName);
+
+ // Create data model for template
+ var model = new PostPageDataModel
+ {
+ Body = htmlBody,
+ Frontmatter = frontmatterDict,
+ Filename = _markdownSource.Name,
+ Created = Created,
+ Modified = Modified
+ };
+
+ // Render template with model
+ var html = await RenderTemplateAsync(templateFile, model);
+
+ return html;
+ }
+
+ #region Transformation Helpers
+
+ ///
+ /// Extract YAML front-matter block from markdown file.
+ /// Front-matter is delimited by "---" at start and end.
+ /// Handles files without front-matter (returns empty string for frontmatter).
+ ///
+ /// Markdown file to parse
+ /// Tuple of (frontmatter YAML string, content markdown string)
+ private async Task<(string frontmatter, string content)> ParseMarkdownAsync(IFile file)
+ {
+ var text = await file.ReadTextAsync();
+
+ // Check for front-matter delimiters
+ if (!text.StartsWith("---"))
+ {
+ // No front-matter present
+ return (string.Empty, text);
+ }
+
+ // Find the closing delimiter
+ var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.None);
+ var closingDelimiterIndex = -1;
+
+ for (int i = 1; i < lines.Length; i++)
+ {
+ if (lines[i].Trim() == "---")
+ {
+ closingDelimiterIndex = i;
+ break;
+ }
+ }
+
+ if (closingDelimiterIndex == -1)
+ {
+ // No closing delimiter found - treat entire file as content
+ return (string.Empty, text);
+ }
+
+ // Extract front-matter (lines between delimiters)
+ var frontmatterLines = lines.Skip(1).Take(closingDelimiterIndex - 1);
+ var frontmatter = string.Join(Environment.NewLine, frontmatterLines);
+
+ // Extract content (everything after closing delimiter)
+ var contentLines = lines.Skip(closingDelimiterIndex + 1);
+ var content = string.Join(Environment.NewLine, contentLines);
+
+ return (frontmatter, content);
+ }
+
+ ///
+ /// Transform markdown content to HTML body using Markdig.
+ /// Returns HTML without wrapping elements - template controls structure.
+ /// Uses Advanced Extensions pipeline for full Markdown feature support.
+ ///
+ /// Markdown content string
+ /// HTML body content
+ private string TransformMarkdownToHtml(string markdown)
+ {
+ var pipeline = new MarkdownPipelineBuilder()
+ .UseAdvancedExtensions()
+ .UseSoftlineBreakAsHardlineBreak()
+ .Build();
+
+ return Markdown.ToHtml(markdown, pipeline);
+ }
+
+ ///
+ /// Parse YAML front-matter string to arbitrary dictionary.
+ /// No schema enforcement - accepts any valid YAML structure.
+ /// Handles empty/missing front-matter gracefully.
+ ///
+ /// YAML string from front-matter
+ /// Dictionary with arbitrary keys and values
+ private Dictionary ParseFrontmatter(string yaml)
+ {
+ // Handle empty front-matter
+ if (string.IsNullOrWhiteSpace(yaml))
+ {
+ return new Dictionary();
+ }
+
+ try
+ {
+ var deserializer = new DeserializerBuilder()
+ .Build();
+
+ var result = deserializer.Deserialize>(yaml);
+ return result ?? new Dictionary();
+ }
+ catch (YamlDotNet.Core.YamlException ex)
+ {
+ throw new InvalidOperationException($"Failed to parse YAML front-matter: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// Resolve template file from IStorable source.
+ /// Handles both IFile (single template) and IFolder (template + assets).
+ /// Uses convention-based lookup ("template.html") when source is folder.
+ ///
+ /// Template as IFile or IFolder
+ /// File name when source is IFolder (defaults to "template.html")
+ /// Resolved template IFile
+ private async Task ResolveTemplateFileAsync(
+ IStorable templateSource,
+ string? templateFileName)
+ {
+ if (templateSource is IFile file)
+ {
+ return file;
+ }
+
+ if (templateSource is IFolder folder)
+ {
+ var fileName = templateFileName ?? "template.html";
+ var templateFile = await folder.GetFirstByNameAsync(fileName);
+
+ if (templateFile is not IFile resolvedFile)
+ {
+ throw new FileNotFoundException(
+ $"Template file '{fileName}' not found in folder '{folder.Name}'.");
+ }
+
+ return resolvedFile;
+ }
+
+ throw new ArgumentException(
+ $"Template source must be IFile or IFolder, got: {templateSource.GetType().Name}",
+ nameof(templateSource));
+ }
+
+ ///
+ /// Render Scriban template with data model to produce final HTML.
+ /// Template generates all HTML including meta tags from model.frontmatter.
+ /// Flow boundary: Generator provides data model, template generates HTML.
+ ///
+ /// Scriban template file
+ /// PostPageDataModel with body, frontmatter, metadata
+ /// Rendered HTML string
+ private async Task RenderTemplateAsync(
+ IFile templateFile,
+ PostPageDataModel model)
+ {
+ var templateContent = await templateFile.ReadTextAsync();
+
+ var template = Template.Parse(templateContent);
+
+ if (template.HasErrors)
+ {
+ var errors = string.Join(Environment.NewLine, template.Messages);
+ throw new InvalidOperationException($"Template parsing failed:{Environment.NewLine}{errors}");
+ }
+
+ var html = template.Render(model);
+
+ return html;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Blog/PostPage/PostPageAssetFolder.cs b/src/Blog/PostPage/PostPageAssetFolder.cs
new file mode 100644
index 0000000..9771014
--- /dev/null
+++ b/src/Blog/PostPage/PostPageAssetFolder.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using OwlCore.Storage;
+
+namespace WindowsAppCommunity.Blog.PostPage
+{
+ ///
+ /// Virtual IChildFolder that recursively wraps template asset folders.
+ /// Mirrors template folder structure with recursive PostPageAssetFolder wrapping.
+ /// Passes through files directly (preserves type identity for fastpath extension methods).
+ /// Propagates template file exclusion down hierarchy.
+ ///
+ public sealed class PostPageAssetFolder : IChildFolder
+ {
+ private readonly IFolder _wrappedFolder;
+ private readonly IFolder _parent;
+ private readonly IFile? _templateFileToExclude;
+
+ ///
+ /// Creates virtual asset folder wrapping template folder structure.
+ ///
+ /// Template folder to mirror
+ /// Parent folder in virtual hierarchy
+ /// Template HTML file to exclude from enumeration
+ public PostPageAssetFolder(IFolder wrappedFolder, IFolder parent, IFile? templateFileToExclude)
+ {
+ _wrappedFolder = wrappedFolder ?? throw new ArgumentNullException(nameof(wrappedFolder));
+ _parent = parent ?? throw new ArgumentNullException(nameof(parent));
+ _templateFileToExclude = templateFileToExclude;
+ }
+
+ ///
+ public string Id => _wrappedFolder.Id;
+
+ ///
+ public string Name => _wrappedFolder.Name;
+
+ ///
+ /// Parent folder in virtual hierarchy (not interface requirement, internal storage).
+ ///
+ public IFolder Parent => _parent;
+
+ ///
+ public Task GetParentAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(_parent);
+ }
+
+ ///
+ public async IAsyncEnumerable GetItemsAsync(
+ StorableType type = StorableType.All,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ OwlCore.Diagnostics.Logger.LogInformation($"PostPageAssetFolder.GetItemsAsync starting for: {_wrappedFolder.Id}");
+
+ // Enumerate wrapped folder items
+ await foreach (var item in _wrappedFolder.GetItemsAsync(type, cancellationToken))
+ {
+ // Recursively wrap subfolders with this as parent
+ if (item is IFolder subfolder && (type == StorableType.All || type == StorableType.Folder))
+ {
+ yield return new PostPageAssetFolder(subfolder, this, _templateFileToExclude);
+ continue;
+ }
+
+ // Pass through files directly (preserves type identity)
+ if (item is IChildFile file && (type == StorableType.All || type == StorableType.File))
+ {
+ // Exclude template HTML file if specified
+ if (_templateFileToExclude != null && file.Id == _templateFileToExclude.Id)
+ {
+ continue;
+ }
+
+ yield return file;
+ }
+ }
+
+ OwlCore.Diagnostics.Logger.LogInformation($"PostPageAssetFolder.GetItemsAsync complete for: {_wrappedFolder.Id}");
+ }
+ }
+}
diff --git a/src/Blog/PostPage/PostPageDataModel.cs b/src/Blog/PostPage/PostPageDataModel.cs
new file mode 100644
index 0000000..8074e6e
--- /dev/null
+++ b/src/Blog/PostPage/PostPageDataModel.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+
+namespace WindowsAppCommunity.Blog.PostPage
+{
+ ///
+ /// Data model for Scriban template rendering in Post/Page scenario.
+ /// Provides the data contract that templates can access via dot notation.
+ ///
+ public class PostPageDataModel
+ {
+ ///
+ /// Transformed HTML content from markdown body.
+ /// Generated via Markdig pipeline, ready to insert into template.
+ /// No wrapping elements - template controls structure.
+ ///
+ public string Body { get; set; } = string.Empty;
+
+ ///
+ /// Arbitrary key-value pairs from YAML front-matter.
+ /// Keys are user-defined field names, values can be string, number, boolean, or structured data.
+ /// No required keys, no filtering - entirely user-defined.
+ /// Template accesses via frontmatter.key or frontmatter["key"] syntax.
+ ///
+ public Dictionary Frontmatter { get; set; } = new Dictionary();
+
+ ///
+ /// Original markdown filename without path or extension.
+ /// Useful for debugging, display, or conditional logic.
+ /// Null if not available or not provided.
+ ///
+ public string? Filename { get; set; }
+
+ ///
+ /// File creation timestamp from filesystem metadata.
+ /// May not be available on all platforms.
+ /// Null if unavailable.
+ ///
+ public DateTime? Created { get; set; }
+
+ ///
+ /// File modification timestamp from filesystem metadata.
+ /// More reliable than creation time.
+ /// Null if unavailable.
+ ///
+ public DateTime? Modified { get; set; }
+ }
+}
diff --git a/src/Blog/PostPage/PostPageFolder.cs b/src/Blog/PostPage/PostPageFolder.cs
new file mode 100644
index 0000000..2ee7e28
--- /dev/null
+++ b/src/Blog/PostPage/PostPageFolder.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using OwlCore.Storage;
+
+namespace WindowsAppCommunity.Blog.PostPage
+{
+ ///
+ /// Virtual IFolder representing folderized single-page output structure.
+ /// Wraps markdown source file and template to provide virtual {filename}/index.html + assets structure.
+ /// Implements lazy generation - no file system operations during construction.
+ ///
+ public sealed class PostPageFolder : IFolder
+ {
+ private readonly IFile _markdownSource;
+ private readonly IStorable _templateSource;
+ private readonly string? _templateFileName;
+
+ ///
+ /// Creates virtual folder representing single-page output structure.
+ /// No file system operations occur during construction (lazy generation).
+ ///
+ /// Source markdown file to transform
+ /// Template as IFile or IFolder
+ /// Template file name when source is IFolder (defaults to "template.html")
+ public PostPageFolder(IFile markdownSource, IStorable templateSource, string? templateFileName = null)
+ {
+ _markdownSource = markdownSource ?? throw new ArgumentNullException(nameof(markdownSource));
+ _templateSource = templateSource ?? throw new ArgumentNullException(nameof(templateSource));
+ _templateFileName = templateFileName;
+ }
+
+ ///
+ public string Id => _markdownSource.Id;
+
+ ///
+ public string Name => SanitizeFilename(_markdownSource.Name);
+
+ ///
+ public async IAsyncEnumerable GetItemsAsync(
+ StorableType type = StorableType.All,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ // Resolve template file for exclusion and IndexHtmlFile construction
+ var templateFile = await ResolveTemplateFileAsync(_templateSource, _templateFileName);
+
+ // Yield IndexHtmlFile (virtual index.html)
+ if (type == StorableType.All || type == StorableType.File)
+ {
+ var indexHtmlId = $"{Id}/index.html";
+ yield return new IndexHtmlFile(indexHtmlId, _markdownSource, _templateSource, _templateFileName);
+ }
+
+ // If template is folder, yield wrapped asset structure
+ if (_templateSource is IFolder templateFolder)
+ {
+ await foreach (var item in templateFolder.GetItemsAsync(StorableType.All, cancellationToken))
+ {
+ // Wrap subfolders as PostPageAssetFolder
+ if (item is IFolder subfolder && (type == StorableType.All || type == StorableType.Folder))
+ {
+ yield return new PostPageAssetFolder(subfolder, this, templateFile);
+ continue;
+ }
+
+ // Pass through files directly (excluding template HTML file)
+ if (item is IChildFile file && (type == StorableType.All || type == StorableType.File))
+ {
+ // Exclude template HTML file (already rendered as index.html)
+ if (file.Id == templateFile.Id)
+ {
+ continue;
+ }
+
+ yield return file;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Sanitize markdown filename for use as folder name.
+ /// Removes file extension and replaces invalid filename characters with underscore.
+ ///
+ /// Original markdown filename with extension
+ /// Sanitized folder name
+ private string SanitizeFilename(string markdownFilename)
+ {
+ // Remove file extension
+ var nameWithoutExtension = Path.GetFileNameWithoutExtension(markdownFilename);
+
+ // Replace invalid filename characters with underscore
+ var invalidChars = Path.GetInvalidFileNameChars();
+ var sanitized = string.Concat(nameWithoutExtension.Select(c =>
+ invalidChars.Contains(c) ? '_' : c));
+
+ return sanitized;
+ }
+
+ ///
+ /// Resolve template file from IStorable source.
+ /// Handles both IFile (single template) and IFolder (template + assets).
+ /// Uses convention-based lookup ("template.html") when source is folder.
+ ///
+ /// Template as IFile or IFolder
+ /// File name when source is IFolder (defaults to "template.html")
+ /// Resolved template IFile
+ private async Task ResolveTemplateFileAsync(
+ IStorable templateSource,
+ string? templateFileName)
+ {
+ if (templateSource is IFile file)
+ {
+ return file;
+ }
+
+ if (templateSource is IFolder folder)
+ {
+ var fileName = templateFileName ?? "template.html";
+ var templateFile = await folder.GetFirstByNameAsync(fileName);
+
+ if (templateFile is not IFile resolvedFile)
+ {
+ throw new FileNotFoundException(
+ $"Template file '{fileName}' not found in folder '{folder.Name}'.");
+ }
+
+ return resolvedFile;
+ }
+
+ throw new ArgumentException(
+ $"Template source must be IFile or IFolder, got: {templateSource.GetType().Name}",
+ nameof(templateSource));
+ }
+ }
+}
diff --git a/src/Commands/Blog/PostPage/PostPageCommand.cs b/src/Commands/Blog/PostPage/PostPageCommand.cs
new file mode 100644
index 0000000..832db6a
--- /dev/null
+++ b/src/Commands/Blog/PostPage/PostPageCommand.cs
@@ -0,0 +1,143 @@
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.IO;
+using System.Threading.Tasks;
+using OwlCore.Storage;
+using OwlCore.Storage.System.IO;
+using WindowsAppCommunity.Blog.PostPage;
+
+namespace WindowsAppCommunity.CommandLine.Blog.PostPage
+{
+ ///
+ /// CLI command for Post/Page scenario blog generation.
+ /// Handles command-line parsing and invokes PostPageGenerator.
+ ///
+ public class PostPageCommand : Command
+ {
+ ///
+ /// Initialize Post/Page command with CLI options.
+ ///
+ public PostPageCommand()
+ : base("postpage", "Generate HTML from markdown using template")
+ {
+ // Define CLI options
+ var markdownOption = new Option(
+ name: "--markdown",
+ description: "Path to markdown file to transform")
+ {
+ IsRequired = true
+ };
+
+ var templateOption = new Option(
+ name: "--template",
+ description: "Path to template file or folder")
+ {
+ IsRequired = true
+ };
+
+ var outputOption = new Option(
+ name: "--output",
+ description: "Path to output destination folder")
+ {
+ IsRequired = true
+ };
+
+ var templateFileNameOption = new Option(
+ name: "--template-file",
+ description: "Template file name when --template is folder (optional, defaults to 'template.html')",
+ getDefaultValue: () => null);
+
+ // Register options
+ AddOption(markdownOption);
+ AddOption(templateOption);
+ AddOption(outputOption);
+ AddOption(templateFileNameOption);
+
+ // Set handler with option parameters
+ this.SetHandler(ExecuteAsync, markdownOption, templateOption, outputOption, templateFileNameOption);
+ }
+
+ ///
+ /// Execute Post/Page generation command.
+ /// Orchestrates: Parse arguments → Resolve storage → Invoke generator → Report results
+ ///
+ /// Path to markdown file
+ /// Path to template file or folder
+ /// Path to output destination folder
+ /// Template file name when template is folder (optional)
+ /// Exit code (0 = success, non-zero = error)
+ private async Task ExecuteAsync(
+ string markdownPath,
+ string templatePath,
+ string outputPath,
+ string? templateFileName)
+ {
+ // Gap #5 resolution: SystemFile/SystemFolder constructors validate existence
+ // Gap #10 resolution: Directory.Exists distinguishes folders from files
+
+ // 1. Resolve markdown file (SystemFile throws if doesn't exist)
+ var markdownFile = new SystemFile(markdownPath);
+
+ // 2. Resolve template source (file or folder)
+ IStorable templateSource;
+ if (Directory.Exists(templatePath))
+ {
+ templateSource = new SystemFolder(templatePath);
+ }
+ else
+ {
+ // SystemFile throws if doesn't exist
+ templateSource = new SystemFile(templatePath);
+ }
+
+ // 3. Resolve output folder (SystemFolder throws if doesn't exist)
+ IModifiableFolder outputFolder = new SystemFolder(outputPath);
+
+ // 4. Create virtual PostPageFolder (lazy generation - no I/O during construction)
+ var postPageFolder = new PostPageFolder(markdownFile, templateSource, templateFileName);
+
+ // 5. Create output folder for this page
+ var pageOutputFolder = await outputFolder.CreateFolderAsync(postPageFolder.Name, overwrite: true);
+
+ // 6. Materialize virtual structure by recursively copying all files
+ var recursiveFolder = new DepthFirstRecursiveFolder(postPageFolder);
+ await foreach (var item in recursiveFolder.GetItemsAsync(StorableType.File))
+ {
+ if (item is not IChildFile file)
+ continue;
+
+ // Get relative path from appropriate root based on file type
+ string relativePath;
+ if (file is IndexHtmlFile)
+ {
+ // IndexHtmlFile is virtual, use simple name-based path
+ relativePath = $"/{file.Name}";
+ }
+ else if (templateSource is IFolder templateFolder)
+ {
+ // Asset files from template folder - get path relative to template root
+ relativePath = await templateFolder.GetRelativePathToAsync(file);
+ }
+ else
+ {
+ // Template is file, no assets exist - skip
+ continue;
+ }
+
+ // Create containing folder for this file (or open if exists)
+ var containingFolder = await pageOutputFolder.CreateFoldersAlongRelativePathAsync(relativePath, overwrite: false).LastAsync();
+
+ // Copy file using ICreateCopyOf fastpath
+ await ((IModifiableFolder)containingFolder).CreateCopyOfAsync(file, overwrite: true);
+ }
+
+ // 7. Report success
+ var outputFolderName = Path.GetFileNameWithoutExtension(markdownFile.Name);
+ Console.WriteLine($"Generated: {Path.Combine(outputPath, outputFolderName, "index.html")}");
+
+ // 7. Return success exit code
+ return 0;
+ }
+ }
+}
diff --git a/src/Commands/Blog/WacsdkBlogCommands.cs b/src/Commands/Blog/WacsdkBlogCommands.cs
new file mode 100644
index 0000000..bb8bbf0
--- /dev/null
+++ b/src/Commands/Blog/WacsdkBlogCommands.cs
@@ -0,0 +1,29 @@
+using System.CommandLine;
+using WindowsAppCommunity.CommandLine.Blog.PostPage;
+
+namespace WindowsAppCommunity.CommandLine.Blog
+{
+ ///
+ /// Command aggregator for blog generation scenarios.
+ /// Registers Post/Page, Pages, and Site scenario commands.
+ ///
+ public class WacsdkBlogCommands : Command
+ {
+ ///
+ /// Initialize blog commands aggregator.
+ /// Registers all blog generation scenario subcommands.
+ ///
+ public WacsdkBlogCommands()
+ : base("blog", "Blog generation commands")
+ {
+ // Register Post/Page scenario
+ AddCommand(new PostPageCommand());
+
+ // Future: Register Pages scenario
+ // AddCommand(new PagesCommand());
+
+ // Future: Register Site scenario
+ // AddCommand(new SiteCommand());
+ }
+ }
+}
diff --git a/src/Program.cs b/src/Program.cs
index 2dd6c94..f5033ee 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,4 +1,4 @@
-using OwlCore.Diagnostics;
+using OwlCore.Diagnostics;
using WindowsAppCommunity.CommandLine;
using System.CommandLine;
using OwlCore.Nomad.Kubo;
@@ -8,6 +8,7 @@
using WindowsAppCommunity.CommandLine.User;
using WindowsAppCommunity.CommandLine.Project;
using WindowsAppCommunity.CommandLine.Publisher;
+using WindowsAppCommunity.CommandLine.Blog;
// Logging
var startTime = DateTime.Now;
@@ -85,5 +86,6 @@ void Logger_MessageReceived(object? sender, LoggerMessageEventArgs e)
rootCommand.AddCommand(new WacsdkUserCommands(config, repoOption));
rootCommand.AddCommand(new WacsdkProjectCommands(config, repoOption));
rootCommand.AddCommand(new WacsdkPublisherCommands(config, repoOption));
+rootCommand.AddCommand(new WacsdkBlogCommands());
-await rootCommand.InvokeAsync(args);
\ No newline at end of file
+await rootCommand.InvokeAsync(args);
diff --git a/src/WindowsAppCommunity.CommandLine.csproj b/src/WindowsAppCommunity.CommandLine.csproj
index f8d4d2a..e13cbed 100644
--- a/src/WindowsAppCommunity.CommandLine.csproj
+++ b/src/WindowsAppCommunity.CommandLine.csproj
@@ -49,10 +49,14 @@ Initial release of WindowsAppCommunity.CommandLine.
+
+
+
+