Skip to content

API docs: add default language classes for code examples#53

Merged
PrzemyslawKlys merged 2 commits intoweb/apidocs-creffrom
web/apidocs-code-lang
Feb 5, 2026
Merged

API docs: add default language classes for code examples#53
PrzemyslawKlys merged 2 commits intoweb/apidocs-creffrom
web/apidocs-code-lang

Conversation

@PrzemyslawKlys
Copy link
Member

Summary

  • Add default language class (csharp/powershell) to API example code blocks
  • Keep docs markup consistent for Prism highlighting without JS fallback

Testing

  • Not run (rendering change only)

Copilot AI review requested due to automatic review settings February 5, 2026 07:34
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b8964be29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +217 to +219
if (expectedMap.TryGetValue(project.ProjectName, out var overrideVersion) && !string.IsNullOrWhiteSpace(overrideVersion))
{
expectedVersion = overrideVersion;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply wildcard version-map entries when resolving versions

When ExpectedVersionMapUseWildcards is enabled, project filtering uses wildcard matching, but version resolution still uses expectedMap.TryGetValue(project.ProjectName, ...), which only supports exact keys. In configs like { "ExpectedVersionMap": { "My.*": "2.0.*" }, "ExpectedVersionMapUseWildcards": true }, matching projects are included but do not receive the mapped version pattern, so they fall back to global/csproj versions and can be packed/published with unintended versions.

Useful? React with 👍 / 👎.

@claude
Copy link

claude bot commented Feb 5, 2026

Code Review: PR #53

Overview

Title: API docs - add default language classes for code examples
Scope Mismatch: Despite the title suggesting documentation-only changes, this PR includes significant new features for build configuration, .NET repository releases, certificate-based package signing, and module validation.
Changes: +5,824 / -3,037 lines across 30 files

Critical Issues (Must Fix Before Merge)

1. Path Traversal Vulnerability in BuildReleaseZipPath

Location: DotNetRepositoryReleaseService.cs:825-834

Issue: spec.ReleaseZipOutputPath is used directly without validation. Malicious configurations could escape the working directory.

Risk: An attacker could set ReleaseZipOutputPath to ../../../../etc/ and write files outside the intended directory.

Fix: Validate paths to ensure they remain within the root path before use.

2. Race Condition (TOCTOU) in Zip Creation

Location: DotNetRepositoryReleaseService.cs:859-862

Issue: Time-of-check-time-of-use bug between File.Delete and FileStream creation.

Fix: Use FileMode.Create which handles both cases atomically instead of FileMode.CreateNew.

High Priority Issues

3. Configuration Injection Risk

Location: Lines 828, 844. spec.Configuration is used in path construction without validation. Validate against a whitelist of allowed configurations.

4. Silent Certificate Retrieval Failures

Location: GetCertificateSha256 (lines 1011-1033). Empty catch blocks suppress all exceptions without logging.

5. Code Duplication

GetCertificateSha256 appears identically in both DotNetRepositoryReleaseService.cs and DotNetReleaseBuildService.cs. Extract to a shared utility class.

Strengths

  1. Excellent error handling patterns with structured tuples
  2. Proper use of nullable reference types throughout
  3. Modern C# patterns: using declarations, pattern matching, null-conditional operators
  4. Comprehensive logging at appropriate levels
  5. Good separation of concerns with helper methods
  6. Secure argument handling with ProcessStartInfo.ArgumentList
  7. Proper zip slip prevention with ComputeRelativePath

Test Coverage Recommendations

Add tests for: path traversal attempts, invalid certificate thumbprints, configuration injection attempts, concurrent zip creation scenarios, FailOnMissingCommands behavior, and wildcard matching.

Overall Assessment

Code Quality: 8/10 - Generally excellent with modern C# patterns
Security: 5/10 - Critical path traversal vulnerability must be fixed
Test Coverage: Unknown - Recommend adding tests for security-sensitive features

Recommendation

REQUEST CHANGES - Fix critical security issues (path traversal and race condition) and high-priority items before merging. Once the path validation and race condition are addressed, this will be a strong addition to the codebase.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request claims to add default language classes (csharp/powershell) for API example code blocks to support Prism highlighting without JavaScript fallback. However, the PR contains extensive additional changes far beyond documentation rendering.

Changes:

  • Documentation rendering: Added language class attributes to code blocks in WebApiDocsGenerator.cs
  • New project build schema and Invoke-ProjectBuild cmdlet for repository-wide .NET build/release pipelines
  • Module dependency resolution with online/offline version resolution capabilities
  • NuGet package signing with certificate support
  • Numerous schema updates and model additions

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 43 comments.

Show a summary per file
File Description
PowerForge.Web/Services/WebApiDocsGenerator.cs Adds language class attributes to code/pre tags for Prism syntax highlighting
schemas/project.build.schema.json New schema for .NET project build configuration (entire new file)
PowerForge/Services/ModulePipelineRunner.cs Major feature additions for module dependency resolution, external modules, online version checking
PowerForge/Services/DotNetRepositoryReleaseService.cs Extensive additions for package signing, release zip creation, duplicate detection, wildcard matching
PSPublishModule/Cmdlets/InvokeProjectBuildCommand.cs Entirely new cmdlet for repository-wide build/release pipeline (815 lines)
PowerForge/Scripts//.ps1 Added GUID extraction for PowerShellGet and PSResourceGet module queries
Multiple documentation files Updated cmdlet documentation reflecting new parameters and functionality
Comments suppressed due to low confidence (1)

PowerForge/Services/ModulePipelineRunner.cs:251

                    if (moduleSeg.Kind == ModuleDependencyKind.RequiredModule)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +745 to +746
if (plan.ExternalModuleDependencies is { Length: > 0 })
ManifestEditor.TrySetPsDataStringArray(buildResult.ManifestPath, "ExternalModuleDependencies", plan.ExternalModuleDependencies);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate code: ExternalModuleDependencies is being set twice on consecutive lines with identical logic. Remove one of these duplicate lines.

Suggested change
if (plan.ExternalModuleDependencies is { Length: > 0 })
ManifestEditor.TrySetPsDataStringArray(buildResult.ManifestPath, "ExternalModuleDependencies", plan.ExternalModuleDependencies);

Copilot uses AI. Check for mistakes.
Comment on lines +2238 to +2242
var codeLanguage = GetDefaultCodeLanguage(options);
AppendMembers(memberHtml, "Methods", type.Methods, codeLanguage);
AppendMembers(memberHtml, "Properties", type.Properties, codeLanguage);
AppendMembers(memberHtml, "Fields", type.Fields, codeLanguage);
AppendMembers(memberHtml, "Events", type.Events, codeLanguage);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title and description state this is about "API docs: add default language classes for code examples" but this pull request contains extensive unrelated changes including: new JSON schemas, PowerShell module resolution features, NuGet package signing, GitHub release functionality, and many other features. These substantial functional changes should be in separate PRs with appropriate descriptions. The documentation rendering change is a small part of this PR (lines in WebApiDocsGenerator.cs).

Copilot uses AI. Check for mistakes.
Comment on lines +271 to +384
foreach (var project in release.Projects)
{
if (!project.IsPackable) continue;
var r = new ProjectBuildGitHubResult { ProjectName = project.ProjectName };

if (string.IsNullOrWhiteSpace(project.NewVersion))
{
r.Success = false;
r.ErrorMessage = "Missing project version for GitHub release.";
result.GitHub.Add(r);
if (spec.PublishFailFast)
{
result.Success = false;
result.ErrorMessage = r.ErrorMessage;
break;
}
continue;
}

if (string.IsNullOrWhiteSpace(project.ReleaseZipPath))
{
r.Success = false;
r.ErrorMessage = "No release zip available for GitHub release.";
result.GitHub.Add(r);
if (spec.PublishFailFast)
{
result.Success = false;
result.ErrorMessage = r.ErrorMessage;
break;
}
continue;
}

if (!File.Exists(project.ReleaseZipPath))
{
r.Success = false;
r.ErrorMessage = $"Release zip not found: {project.ReleaseZipPath}";
result.GitHub.Add(r);
if (spec.PublishFailFast)
{
result.Success = false;
result.ErrorMessage = r.ErrorMessage;
break;
}
continue;
}

var tag = string.IsNullOrWhiteSpace(config.GitHubTagName)
? (config.GitHubIncludeProjectNameInTag == false ? $"v{project.NewVersion}" : $"{project.ProjectName}-v{project.NewVersion}")
: config.GitHubTagName!;
if (!string.IsNullOrWhiteSpace(config.GitHubTagTemplate))
{
tag = ApplyTemplate(
config.GitHubTagTemplate!,
project.ProjectName,
project.NewVersion ?? project.OldVersion ?? string.Empty,
config.GitHubPrimaryProject ?? project.ProjectName,
project.NewVersion ?? project.OldVersion ?? string.Empty,
repoName,
dateToken,
utcDateToken);
}

var releaseName = string.IsNullOrWhiteSpace(config.GitHubReleaseName)
? tag
: ApplyTemplate(
config.GitHubReleaseName!,
project.ProjectName,
project.NewVersion ?? project.OldVersion ?? string.Empty,
config.GitHubPrimaryProject ?? project.ProjectName,
project.NewVersion ?? project.OldVersion ?? string.Empty,
repoName,
dateToken,
utcDateToken);

var output = sb.Invoke(config.GitHubUsername, config.GitHubRepositoryName, gitHubToken, tag, releaseName, new[] { project.ReleaseZipPath }, config.GitHubIsPreRelease, config.GitHubGenerateReleaseNotes);
var status = output.Count > 0 ? output[0]?.BaseObject : null;

bool ok = false;
string? releaseUrl = null;
string? errorMessage = null;

if (status is SendGitHubReleaseCommand.GitHubReleaseResult gr)
{
ok = gr.Succeeded;
releaseUrl = gr.ReleaseUrl;
errorMessage = gr.Succeeded ? null : gr.ErrorMessage;
}
else if (status is PSObject pso)
{
var succeeded = pso.Properties["Succeeded"]?.Value as bool?;
ok = succeeded ?? false;
releaseUrl = pso.Properties["ReleaseUrl"]?.Value?.ToString();
errorMessage = pso.Properties["ErrorMessage"]?.Value?.ToString();
}
else
{
errorMessage = "Unexpected result from Send-GitHubRelease.";
}

r.Success = ok;
r.TagName = tag;
r.ReleaseUrl = releaseUrl;
r.ErrorMessage = errorMessage;
result.GitHub.Add(r);

if (!ok)
{
result.Success = false;
result.ErrorMessage = errorMessage ?? "GitHub publish failed.";
if (spec.PublishFailFast)
break;
}
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines +389 to +396
foreach (var project in release.Projects)
{
if (!project.IsPackable) continue;
var zip = project.ReleaseZipPath;
if (string.IsNullOrWhiteSpace(zip))
continue;
if (File.Exists(zip))
assets.Add(zip!);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Suggested change
foreach (var project in release.Projects)
{
if (!project.IsPackable) continue;
var zip = project.ReleaseZipPath;
if (string.IsNullOrWhiteSpace(zip))
continue;
if (File.Exists(zip))
assets.Add(zip!);
foreach (var project in release.Projects.Where(p => p.IsPackable && !string.IsNullOrWhiteSpace(p.ReleaseZipPath)))
{
var zip = project.ReleaseZipPath!;
if (File.Exists(zip))
assets.Add(zip);

Copilot uses AI. Check for mistakes.
Comment on lines +414 to +420
foreach (var line in SplitLines(stdout))
{
if (!line.StartsWith("PFDOCS::ERROR::", StringComparison.Ordinal)) continue;
var b64 = line.Substring("PFDOCS::ERROR::".Length);
try { return Encoding.UTF8.GetString(Convert.FromBase64String(b64)); }
catch { return null; }
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines +373 to +387
if (!spec.WhatIf)
{
if (!TryCreateReleaseZip(project, spec.Configuration, zipPath, out var zipError))
{
project.ErrorMessage = zipError;
_logger.Warn($"{project.ProjectName}: {zipError}");
result.Success = false;
if (spec.PublishFailFast)
return result;
}
else
{
_logger.Success($"{project.ProjectName}: release zip created.");
}
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Condition is always false because of access to property WhatIf.

Suggested change
if (!spec.WhatIf)
{
if (!TryCreateReleaseZip(project, spec.Configuration, zipPath, out var zipError))
{
project.ErrorMessage = zipError;
_logger.Warn($"{project.ProjectName}: {zipError}");
result.Success = false;
if (spec.PublishFailFast)
return result;
}
else
{
_logger.Success($"{project.ProjectName}: release zip created.");
}
}
if (!TryCreateReleaseZip(project, spec.Configuration, zipPath, out var zipError))
{
project.ErrorMessage = zipError;
_logger.Warn($"{project.ProjectName}: {zipError}");
result.Success = false;
if (spec.PublishFailFast)
return result;
}
else
{
_logger.Success($"{project.ProjectName}: release zip created.");
}

Copilot uses AI. Check for mistakes.
var synopsisPercent = Percent(synopsisCount, total);
var descriptionPercent = Percent(descriptionCount, total);
var examplesPercent = Percent(exampleCount, total);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to examplesPercent is useless, since its value is never read.

Suggested change
var examplesPercent = Percent(exampleCount, total);

Copilot uses AI. Check for mistakes.
var repos = ParseRepositoryList(repository);
var runner = new PowerShellRunner();

IReadOnlyList<PSResourceInfo> items = Array.Empty<PSResourceInfo>();
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to items is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +153
!string.IsNullOrWhiteSpace(root))
{
if (!File.Exists(Path.Combine(moduleRoot, root)))
issues.Add($"RootModule missing: {root}");
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 'if' statements can be combined.

Suggested change
!string.IsNullOrWhiteSpace(root))
{
if (!File.Exists(Path.Combine(moduleRoot, root)))
issues.Add($"RootModule missing: {root}");
!string.IsNullOrWhiteSpace(root) &&
!File.Exists(Path.Combine(moduleRoot, root)))
{
issues.Add($"RootModule missing: {root}");

Copilot uses AI. Check for mistakes.
}

var tag = string.IsNullOrWhiteSpace(config.GitHubTagName)
? (config.GitHubIncludeProjectNameInTag == false ? $"v{project.NewVersion}" : $"{project.ProjectName}-v{project.NewVersion}")
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expression 'A == false' can be simplified to '!A'.

Suggested change
? (config.GitHubIncludeProjectNameInTag == false ? $"v{project.NewVersion}" : $"{project.ProjectName}-v{project.NewVersion}")
? (!config.GitHubIncludeProjectNameInTag ? $"v{project.NewVersion}" : $"{project.ProjectName}-v{project.NewVersion}")

Copilot uses AI. Check for mistakes.
@PrzemyslawKlys
Copy link
Member Author

Rebased onto origin/web/apidocs-cref and force-pushed. PR now only touches WebApiDocsGenerator.cs (default language classes). Previous scope/extra-files comments should now be outdated. Please re-review.

@claude
Copy link

claude bot commented Feb 5, 2026

Code Review for PR #53: API docs - add default language classes for code examples

Summary

This PR adds default language classes (language-csharp or language-powershell) to code example blocks in the API documentation generator based on the documentation type. This enables proper syntax highlighting with Prism.js without requiring JavaScript fallback logic.

Positives

  1. Clean Implementation: The new GetDefaultCodeLanguage helper method uses a pattern-matching switch expression, which is idiomatic modern C#.

  2. Consistent Approach: The codeLanguage parameter is properly threaded through the call chain from the top-level generation methods down to AppendExamples.

  3. Backward Compatible: When codeLanguage is empty, no class attributes are added, maintaining backward compatibility.

  4. Appropriate Scope: The change is focused and does not add unnecessary complexity or features beyond the stated goal.

Issues Found

CRITICAL: Missing parameter propagation

The PR is incomplete. The codeLanguage parameter needs to be passed through additional method signatures but is currently missing:

  1. AppendMemberSections (line 2829): This method calls AppendMemberCards, which eventually calls AppendMemberCard, which calls AppendExamples. The codeLanguage parameter needs to be added to this chain.

  2. AppendMemberCards (line 2852): Needs to accept and pass through codeLanguage.

  3. AppendMemberCard (line 2915): Needs to accept codeLanguage and pass it to AppendExamples at line 3016.

Current impact: Member-level examples (in methods, properties, etc.) will not have language classes applied, only type-level examples will work correctly.

The parameter needs to be added to these three methods and all their call sites updated (lines 2817-2823, 2847-2849, 2887, 2898, 2908).

Other Observations

  1. Test Coverage: The PR description mentions not running tests for rendering changes. Consider adding a simple test to verify the language class is correctly applied for both PowerShell and CSharp doc types.

  2. Security: The code properly uses System.Web.HttpUtility.HtmlEncode for user content. No XSS concerns identified.

  3. Performance: The GetDefaultCodeLanguage method is called once per type and cached in a local variable, which is efficient. No performance concerns.

  4. Code Quality: The implementation follows the existing code style and patterns in the file.

Recommendation

Request Changes - The PR needs the additional parameter threading to be complete before merging. The current implementation will only apply language classes to type-level examples but not member-level examples.

@PrzemyslawKlys
Copy link
Member Author

Addressed missing codeLanguage propagation. Threaded codeLanguage through AppendMemberSections/AppendMemberCards/AppendMemberCard so member-level examples receive language classes. Pushed commit 8bee2f0.

@claude
Copy link

claude bot commented Feb 5, 2026

Pull Request Review - APPROVED

Summary

This PR adds default language classes (csharp or powershell) to code blocks in API documentation examples based on the ApiDocsType configuration. This improves syntax highlighting compatibility with Prism.js and other client-side highlighters.

Positives

  • Clean implementation with GetDefaultCodeLanguage() helper method using switch expressions
  • Consistent threading of codeLanguage parameter through all relevant methods
  • Backward compatibility with empty string fallback for unknown types
  • Focused change without unnecessary refactoring
  • Standards-compliant HTML output with language-{lang} class naming

Code Quality (PowerForge.Web/Services/WebApiDocsGenerator.cs)

  • Lines 2238-2242, 2311, 2602-2605, 2833-2920, 3035-3043: Consistent parameter addition
  • Lines 3058-3066: Simple, testable switch expression with defensive default case
  • Lines 3045-3046: Proper handling of both pre and code elements

Considerations

  1. Test Coverage: No tests added for GetDefaultCodeLanguage(). Recommend adding test to PowerForge.Tests/WebApiDocsGeneratorCrefTests.cs
  2. Documentation: Consider adding XML doc comment to explain Prism.js compatibility

Security & Performance

  • Security: No concerns (hardcoded strings, not user input)
  • Performance: Minimal impact (one switch expression per type)
  • Memory: No issues (string computed once per type)

Repository Conventions (per CONTRIBUTING.md)

  • Keeps core logic in PowerForge (C#)
  • Uses typed enums (ApiDocsType)
  • No unsafe code

Verdict

APPROVED - Clean, focused change that improves syntax highlighting. Implementation is solid and follows repository conventions. Safe to merge as-is, though adding tests would strengthen confidence in future refactorings.

@PrzemyslawKlys PrzemyslawKlys merged commit 279fbe3 into web/apidocs-cref Feb 5, 2026
1 check passed
@PrzemyslawKlys PrzemyslawKlys deleted the web/apidocs-code-lang branch February 5, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants