Skip to content

Commit f0ae6c0

Browse files
authored
Merge pull request #5185 from arturcic/codex/docs-redesign
docs: redesign site with shared versioned guides and API references
2 parents b0cbc6e + bbbe28b commit f0ae6c0

61 files changed

Lines changed: 2766 additions & 533 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs.yml

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ on:
1212
branches:
1313
- main
1414
paths:
15-
- build/docs/**
15+
- build/**
16+
- src/**
17+
- global.json
18+
- .config/dotnet-tools.json
19+
- .github/actions/cache-restore/**
1620
- docs/**
1721
- package*.json
1822
- markdownlint.json
@@ -23,7 +27,11 @@ on:
2327
branches:
2428
- main
2529
paths:
26-
- build/docs/**
30+
- build/**
31+
- src/**
32+
- global.json
33+
- .config/dotnet-tools.json
34+
- .github/actions/cache-restore/**
2735
- docs/**
2836
- package*.json
2937
- markdownlint.json
@@ -112,15 +120,39 @@ jobs:
112120
key: node-${{ runner.os }}-${{ hashFiles('./package-lock.json') }}
113121
restore-keys: node-${{ runner.os }}
114122

123+
- name: Verify documentation input selection
124+
run: dotnet run --file docs/scripts/check-docs-inputs.cs
125+
126+
- name: Restore released documentation inputs
127+
id: docs-cache
128+
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
129+
with:
130+
path: artifacts/docs/cache
131+
key: docs-inputs-v2-${{ runner.os }}-${{ hashFiles('docs/versions.json') }}
132+
restore-keys: |
133+
docs-inputs-v2-${{ runner.os }}-${{ hashFiles('docs/versions.json') }}-
134+
docs-inputs-v2-${{ runner.os }}-
135+
115136
- name: '[Build Documentation]'
137+
env:
138+
GITHUB_TOKEN: ${{ github.token }}
116139
shell: pwsh
117140
run: dotnet run/docs.dll --target=BuildDocs
118141

142+
# New releases can add immutable inputs without changing the manifest.
143+
# Save only when the verified archive set differs from the restored cache.
144+
- name: Save released documentation inputs
145+
if: hashFiles('artifacts/docs/cache/**/*.sha512') != '' && steps.docs-cache.outputs.cache-matched-key != format('docs-inputs-v2-{0}-{1}-{2}', runner.os, hashFiles('docs/versions.json'), hashFiles('artifacts/docs/cache/**/*.sha512'))
146+
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
147+
with:
148+
path: artifacts/docs/cache
149+
key: docs-inputs-v2-${{ runner.os }}-${{ hashFiles('docs/versions.json') }}-${{ hashFiles('artifacts/docs/cache/**/*.sha512') }}
150+
119151
- name: '[HTMLProofer]'
120152
uses: chabad360/htmlproofer@c2750eb7eb937599ac859517e7dd23a29f1b3ed7 # v2
121153
with:
122154
directory: ./artifacts/docs/preview
123-
arguments: --ignore-urls /api/,/docs/,/5.12.0/ --allow-hash-href --allow-missing-href --assume-extension --disable-external --no-check_external_hash
155+
arguments: --ignore-urls /api/,/docs/,/5.12.0/ --allow-hash-href --allow-missing-href --assume-extension .html --disable-external --no-check-external-hash
124156

125157
- name: '[Reviewdog Reporter]'
126158
id: reporter
@@ -173,6 +205,16 @@ jobs:
173205
key: node-${{ runner.os }}-${{ hashFiles('./package-lock.json') }}
174206
restore-keys: node-${{ runner.os }}
175207

208+
- name: Restore released documentation inputs
209+
id: docs-cache
210+
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
211+
with:
212+
path: artifacts/docs/cache
213+
key: docs-inputs-v2-${{ runner.os }}-${{ hashFiles('docs/versions.json') }}
214+
restore-keys: |
215+
docs-inputs-v2-${{ runner.os }}-${{ hashFiles('docs/versions.json') }}-
216+
docs-inputs-v2-${{ runner.os }}-
217+
176218
- name: '[Build Schemas]'
177219
shell: pwsh
178220
run: dotnet run/docs.dll --target=GenerateSchemas
@@ -181,3 +223,10 @@ jobs:
181223
if: github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch'
182224
shell: pwsh
183225
run: dotnet run/docs.dll --target=PublishDocs --force
226+
227+
- name: Save released documentation inputs
228+
if: hashFiles('artifacts/docs/cache/**/*.sha512') != '' && steps.docs-cache.outputs.cache-matched-key != format('docs-inputs-v2-{0}-{1}-{2}', runner.os, hashFiles('docs/versions.json'), hashFiles('artifacts/docs/cache/**/*.sha512'))
229+
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
230+
with:
231+
path: artifacts/docs/cache
232+
key: docs-inputs-v2-${{ runner.os }}-${{ hashFiles('docs/versions.json') }}-${{ hashFiles('artifacts/docs/cache/**/*.sha512') }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,6 @@ new-cli/logs/
131131

132132
# Agent skills (name collides with the VS build-output [Rr]elease/ rule above)
133133
!.agents/skills/release/
134+
135+
# Local agent planning notes
136+
/.devagent/

build/Directory.Packages.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6+
<PackageVersion Include="AngleSharp" Version="1.8.0" />
7+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.9.0" />
68
<PackageVersion Include="Cake.Compression" Version="0.4.0" />
79
<PackageVersion Include="Cake.Codecov" Version="6.0.0" />
810
<PackageVersion Include="Cake.Coverlet" Version="6.0.1" />
@@ -17,4 +19,4 @@
1719
<PackageVersion Include="Cake.Wyam" Version="2.2.14" />
1820
<PackageVersion Include="xunit.assert" Version="2.9.3" />
1921
</ItemGroup>
20-
</Project>
22+
</Project>

build/docs/BuildContext.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Cake.Wyam;
21
using Common.Utilities;
32
using Docs.Utilities;
43

@@ -8,5 +7,5 @@ public class BuildContext(ICakeContext context) : BuildContextBase(context)
87
{
98
public bool ForcePublish { get; set; }
109
public Credentials? Credentials { get; set; }
11-
public WyamSettings? WyamSettings { get; set; }
10+
public PreparedEdition[]? DocumentationInputs { get; set; }
1211
}

build/docs/BuildLifetime.cs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Cake.Wyam;
21
using Common.Lifetime;
32
using Common.Utilities;
43
using Docs.Utilities;
@@ -14,26 +13,6 @@ public override void Setup(BuildContext context, ISetupContext info)
1413
context.Credentials = Credentials.GetCredentials(context);
1514
context.ForcePublish = context.HasArgument("force");
1615

17-
context.WyamSettings = new WyamSettings
18-
{
19-
Recipe = "Docs",
20-
Theme = "Samson",
21-
OutputPath = context.MakeAbsolute(Paths.ArtifactsDocs.Combine("preview")),
22-
RootPath = context.MakeAbsolute(Paths.Docs),
23-
Settings = new Dictionary<string, object>
24-
{
25-
{ "BaseEditUrl", "https://github.com/gittools/GitVersion/tree/main/docs/input/" },
26-
{ "SourceFiles", context.MakeAbsolute(Paths.Src) + "/**/{!bin,!obj,!packages,!*.Tests,!GitTools.*,}/**/*.cs" },
27-
{ "Title", "GitVersion" },
28-
{ "IncludeGlobalNamespace", false },
29-
{ "IgnoreFolders", "**/mdsource" }
30-
},
31-
EnvironmentVariables = new Dictionary<string, string>
32-
{
33-
{ "DOTNET_ROLL_FORWARD", "Major" },
34-
},
35-
};
36-
3716
context.StartGroup("Build Setup");
3817

3918
LogBuildInformation(context);

build/docs/Tasks/BuildDocs.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Cake.Wyam;
21
using Common.Utilities;
32
using Docs.Utilities;
43

@@ -7,6 +6,7 @@ namespace Docs.Tasks;
76
[TaskName(nameof(BuildDocs))]
87
[TaskDescription("Builds the docs to local path")]
98
[IsDependentOn(typeof(Clean))]
9+
[IsDependentOn(typeof(PrepareDocsInputs))]
1010
[IsDependentOn(typeof(ValidateMermaidDiagrams))]
1111
public sealed class BuildDocs : FrostingTask<BuildContext>
1212
{
@@ -18,12 +18,5 @@ public override bool ShouldRun(BuildContext context)
1818
return shouldRun;
1919
}
2020

21-
public override void Run(BuildContext context)
22-
{
23-
if (context.WyamSettings is not null)
24-
{
25-
context.StageMermaidRuntimeForWyam();
26-
context.Wyam(context.WyamSettings);
27-
}
28-
}
21+
public override void Run(BuildContext context) => VersionedDocs.Build(context);
2922
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Docs.Utilities;
2+
3+
namespace Docs.Tasks;
4+
5+
[TaskName(nameof(PrepareDocsInputs))]
6+
[TaskDescription("Resolve documentation trains and cache their release inputs")]
7+
public sealed class PrepareDocsInputs : FrostingTask<BuildContext>
8+
{
9+
public override void Run(BuildContext context) => context.DocumentationInputs = new DocsInputs(
10+
context.Environment.WorkingDirectory.FullPath, message => context.Information(message)).Prepare().GetAwaiter().GetResult();
11+
}

build/docs/Tasks/PreviewDocs.cs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
using Cake.Wyam;
21
using Common.Utilities;
32
using Docs.Utilities;
43

54
namespace Docs.Tasks;
65

76
[TaskName(nameof(PreviewDocs))]
87
[TaskDescription("Run a local server with docs in preview")]
9-
[IsDependentOn(typeof(Clean))]
10-
[IsDependentOn(typeof(ValidateMermaidDiagrams))]
8+
[IsDependentOn(typeof(BuildDocs))]
119
public sealed class PreviewDocs : FrostingTask<BuildContext>
1210
{
1311
public override bool ShouldRun(BuildContext context)
@@ -18,20 +16,5 @@ public override bool ShouldRun(BuildContext context)
1816
return shouldRun;
1917
}
2018

21-
public override void Run(BuildContext context)
22-
{
23-
if (context.WyamSettings is not null)
24-
{
25-
var schemaTargetDir = Paths.ArtifactsDocs.Combine("preview").Combine("schemas");
26-
context.EnsureDirectoryExists(schemaTargetDir);
27-
context.CopyDirectory(Paths.Schemas, schemaTargetDir);
28-
context.StageMermaidRuntimeForWyam();
29-
30-
context.WyamSettings.Preview = true;
31-
context.WyamSettings.Watch = true;
32-
context.WyamSettings.NoClean = true;
33-
context.WyamSettings.Settings.Add("Host", "gittools.github.io");
34-
context.Wyam(context.WyamSettings);
35-
}
36-
}
19+
public override void Run(BuildContext context) => VersionedDocs.Preview(context);
3720
}

build/docs/Tasks/PublishDocs.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using Cake.Git;
2-
using Cake.Npx;
3-
using Cake.Wyam;
42
using Common.Utilities;
53
using Docs.Utilities;
64

@@ -22,8 +20,7 @@ public override bool ShouldRun(BuildContext context)
2220

2321
[TaskName(nameof(PublishDocsInternal))]
2422
[TaskDescription("Published the docs changes to docs specific branch")]
25-
[IsDependentOn(typeof(Clean))]
26-
[IsDependentOn(typeof(ValidateMermaidDiagrams))]
23+
[IsDependentOn(typeof(BuildDocs))]
2724
public sealed class PublishDocsInternal : FrostingTask<BuildContext>
2825
{
2926
public override bool ShouldRun(BuildContext context)
@@ -78,14 +75,16 @@ private static void PublishDocumentation(BuildContext context)
7875
BranchName = publishBranchName
7976
});
8077

81-
if (context.WyamSettings is not null)
78+
foreach (var directory in System.IO.Directory.GetDirectories(publishFolder.FullPath).Where(directory => System.IO.Path.GetFileName(directory) != ".git"))
8279
{
83-
context.WyamSettings.OutputPath = publishFolder;
84-
context.WyamSettings.NoClean = true;
85-
context.StageMermaidRuntimeForWyam();
86-
context.Wyam(context.WyamSettings);
87-
context.Npx("prettier", arguments: "--write **/*.html", configureSettings: settings => settings.WorkingDirectory = publishFolder);
80+
System.IO.Directory.Delete(directory, true);
8881
}
82+
foreach (var file in System.IO.Directory.GetFiles(publishFolder.FullPath).Where(file => System.IO.Path.GetFileName(file) is not ("CNAME" or ".nojekyll")))
83+
{
84+
System.IO.File.Delete(file);
85+
}
86+
87+
DocsInputs.CopyTree(context.MakeAbsolute(Paths.ArtifactsDocs.Combine("preview")).FullPath, publishFolder.FullPath);
8988

9089
var schemaTargetDir = publishFolder.Combine("schemas");
9190
context.EnsureDirectoryExists(schemaTargetDir);

build/docs/Utilities/DocsApi.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Xml.Linq;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Path = System.IO.Path;
6+
7+
namespace Docs.Utilities;
8+
9+
public static class DocsApi
10+
{
11+
// Published binaries define the API. Tagged source supplies comments missing from the packages.
12+
public static void Prepare(string source, string assemblies)
13+
{
14+
var references = Directory.GetFiles(assemblies, "*.dll")
15+
.Select(p => MetadataReference.CreateFromFile(p)).ToArray();
16+
var inventory = new List<string>();
17+
var routes = new List<string>();
18+
foreach (var assembly in references.Where(r => Path.GetFileName(r.FilePath!).StartsWith("GitVersion", StringComparison.OrdinalIgnoreCase)))
19+
{
20+
var name = Path.GetFileNameWithoutExtension(assembly.FilePath!);
21+
var metadata = CSharpCompilation.Create("Inventory", references: references);
22+
var symbol = metadata.GetAssemblyOrModuleSymbol(assembly) as IAssemblySymbol
23+
?? throw new InvalidOperationException($"Could not inspect {name}.");
24+
var exported = Types(symbol.GlobalNamespace).Where(Public).ToArray();
25+
inventory.AddRange(exported.Select(t => t.GetDocumentationCommentId()!).Where(id => id is not null));
26+
routes.AddRange(exported.Select(t => t.ContainingNamespace.ToDisplayString() + "/" + t.MetadataName.Replace('`', '_') + "/index.html"));
27+
SupplementComments(source, assemblies, references, assembly, name);
28+
}
29+
File.WriteAllLines(Path.Combine(assemblies, "public-types.txt"), inventory.Order(StringComparer.Ordinal));
30+
File.WriteAllLines(Path.Combine(assemblies, "public-type-routes.txt"), routes.Order(StringComparer.Ordinal));
31+
}
32+
33+
private static void SupplementComments(string source, string assemblies, PortableExecutableReference[] references, PortableExecutableReference assembly, string name)
34+
{
35+
var project = Path.Combine(source, "src", name == "gitversion" ? "GitVersion.App" : name);
36+
if (!Directory.Exists(project))
37+
{
38+
return;
39+
}
40+
41+
var trees = Directory.GetFiles(project, "*.cs", SearchOption.AllDirectories)
42+
.Where(p => !Path.GetRelativePath(project, p).Split(Path.DirectorySeparatorChar).Any(s => s is "bin" or "obj" or "Templates"))
43+
.Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p), new CSharpParseOptions(documentationMode: DocumentationMode.Diagnose), p)).ToArray();
44+
var compilation = CSharpCompilation.Create(name, trees, references.Where(r => r != assembly),
45+
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
46+
var path = Path.Combine(assemblies, name + ".xml");
47+
var document = File.Exists(path) ? XDocument.Load(path) : new XDocument(new XElement("doc", new XElement("assembly", new XElement("name", name)), new XElement("members")));
48+
var members = document.Root!.Element("members")!;
49+
var existing = members.Elements("member").Select(m => (string?)m.Attribute("name")).ToHashSet();
50+
foreach (var tree in trees)
51+
{
52+
AddTreeComments(compilation.GetSemanticModel(tree), tree, members, existing);
53+
}
54+
55+
document.Save(path);
56+
}
57+
58+
private static void AddTreeComments(SemanticModel model, SyntaxTree tree, XElement members, HashSet<string?> existing)
59+
{
60+
foreach (var node in tree.GetRoot().DescendantNodes().Where(n => n is MemberDeclarationSyntax or VariableDeclaratorSyntax))
61+
{
62+
var declared = model.GetDeclaredSymbol(node);
63+
var id = declared?.GetDocumentationCommentId();
64+
if (id is null || existing.Contains(id))
65+
{
66+
continue;
67+
}
68+
69+
var xml = declared!.GetDocumentationCommentXml();
70+
if (string.IsNullOrWhiteSpace(xml))
71+
{
72+
continue;
73+
}
74+
75+
try { members.Add(XElement.Parse(xml)); existing.Add(id); }
76+
catch (System.Xml.XmlException e) { throw new InvalidOperationException($"Invalid API comment for {id} in {tree.FilePath}", e); }
77+
}
78+
79+
}
80+
81+
private static IEnumerable<INamedTypeSymbol> Types(INamespaceOrTypeSymbol parent)
82+
{
83+
foreach (var member in parent.GetMembers())
84+
{
85+
if (member is INamedTypeSymbol type)
86+
{
87+
yield return type;
88+
}
89+
90+
if (member is INamespaceOrTypeSymbol container)
91+
{
92+
foreach (var child in Types(container))
93+
{
94+
yield return child;
95+
}
96+
}
97+
}
98+
}
99+
100+
private static bool Public(INamedTypeSymbol type) => type.DeclaredAccessibility == Accessibility.Public
101+
&& !type.Name.Contains('<') && type.ContainingNamespace.ToDisplayString().StartsWith("GitVersion", StringComparison.Ordinal)
102+
&& (type.ContainingType is null || Public(type.ContainingType));
103+
}

0 commit comments

Comments
 (0)