Skip to content

Commit 89eb553

Browse files
committed
Attempting to fix typescript reference generator
1 parent e7c4756 commit 89eb553

3 files changed

Lines changed: 67 additions & 14 deletions

File tree

.github/workflows/generate.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Set up .NET
2323
uses: actions/setup-dotnet@v5
2424
with:
25-
dotnet-version: '9.x'
25+
dotnet-version: '10.x'
2626
- name: Generate CLI Reference
2727
working-directory: generator
2828
run: dotnet run -- cli refout
@@ -39,10 +39,10 @@ jobs:
3939
- name: Set up .NET
4040
uses: actions/setup-dotnet@v5
4141
with:
42-
dotnet-version: '9.x'
42+
dotnet-version: '10.x'
4343
- name: Generate
4444
working-directory: generator
45-
run: dotnet run
45+
run: dotnet run -- --strict
4646
- uses: caesay/wait-artifact-action@master
4747
with:
4848
token: ${{ secrets.GITHUB_TOKEN }}

generator/Languages/TypeScript/TypeScriptExtractor.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,31 @@ internal static class TypeScriptExtractor
5454
// Install typedoc + typescript locally in the extracted package.
5555
await Util.RunProcessAsync("npm", new[] { "install", "--no-audit", "--no-fund", "typedoc", "typescript" }, packageDir);
5656

57+
// TypeScript discovers a tsconfig.json by walking up the directory tree from the entry
58+
// point. The work dir lives inside this docs repo, whose root tsconfig.json extends
59+
// "@docusaurus/tsconfig" — not installed here — so the walk-up resolves to it and fails
60+
// with TS6053, killing the run. Write a self-contained tsconfig next to the entry point
61+
// and point typedoc at it so the search never escapes the package.
62+
var tsconfig = Path.Combine(packageDir, "tsconfig.typedoc.json");
63+
await File.WriteAllTextAsync(tsconfig, """
64+
{
65+
"compilerOptions": {
66+
"target": "ESNext",
67+
"module": "ESNext",
68+
"moduleResolution": "node",
69+
"skipLibCheck": true,
70+
"noEmit": true
71+
},
72+
"include": ["**/*.d.ts"]
73+
}
74+
""");
75+
5776
var jsonOut = Path.Combine(workDir, "typedoc.json");
5877
var result = await Util.RunCaptureAsync("npx", new[]
5978
{
6079
"typedoc",
6180
"--json", jsonOut,
81+
"--tsconfig", tsconfig,
6282
"--entryPoints", entry,
6383
"--entryPointStrategy", "expand",
6484
"--skipErrorChecking",

generator/Program.cs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ static async Task Main(string[] args)
2121
string docRootDir = Path.GetFullPath(Path.Combine(scriptsDir, ".."));
2222
string ReferenceDir(string lang) => Path.Combine(docRootDir, "docs", "reference", lang);
2323

24+
// Strict mode: a language that fails to generate is a HARD ERROR (non-zero exit) instead
25+
// of silently keeping the previously-committed docs. Auto-on in CI (GitHub sets CI=true)
26+
// so a broken extractor fails the workflow rather than shipping stale reference pages;
27+
// off locally so a dev missing Docker/npm/python can still regenerate the subset they can.
28+
// Pass --strict to force it on anywhere.
29+
bool strict = args.Contains("--strict")
30+
|| !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"));
31+
args = args.Where(a => a != "--strict").ToArray();
32+
var failures = new List<string>();
33+
2434
// CLI-only mode (used by the per-OS CI matrix to capture native help text).
2535
if (args.Length == 2 && args[0] == "cli") {
2636
Console.WriteLine("Generating CLI Reference Only...");
@@ -51,23 +61,23 @@ static async Task Main(string[] args)
5161

5262
// Unified pipeline: extractor → adapter → ApiModel → MarkdownWriter, one per language.
5363
if (Enabled("cs"))
54-
await RunLanguageAsync("C#", ReferenceDir("cs"),
64+
await RunLanguageAsync("C#", ReferenceDir("cs"), failures, strict,
5565
async () => { var e = await CSharpExtractor.ExtractAsync(); return e == null ? null : CSharpAdapter.Build(e); });
5666

5767
if (Enabled("js"))
58-
await RunLanguageAsync("JavaScript", ReferenceDir("js"),
68+
await RunLanguageAsync("JavaScript", ReferenceDir("js"), failures, strict,
5969
async () => { var e = await TypeScriptExtractor.ExtractAsync(); return e == null ? null : TypeScriptAdapter.Build(e); });
6070

6171
if (Enabled("cpp"))
6272
{
6373
CppExtractResult? cppExtract = null;
64-
await RunLanguageAsync("C++", ReferenceDir("cpp"),
74+
await RunLanguageAsync("C++", ReferenceDir("cpp"), failures, strict,
6575
async () => { cppExtract = await CppExtractor.ExtractAsync(); return cppExtract == null ? null : CppAdapter.Build(cppExtract); },
6676
afterWrite: dir => CppAdapter.WriteCApiPage(cppExtract!, dir));
6777
}
6878

6979
if (Enabled("py"))
70-
await RunLanguageAsync("Python", ReferenceDir("py"),
80+
await RunLanguageAsync("Python", ReferenceDir("py"), failures, strict,
7181
async () => { var e = await PythonExtractor.ExtractAsync(); return e == null ? null : PythonAdapter.Build(e); });
7282

7383
Console.WriteLine("Updating CLI Reference...");
@@ -77,34 +87,57 @@ await RunLanguageAsync("Python", ReferenceDir("py"),
7787
Console.WriteLine($" CLI reference failed: {ex.Message}");
7888
}
7989

90+
// In strict mode (CI), surface every failed language at once and exit non-zero so the
91+
// workflow fails loudly instead of committing stale docs for the broken language(s).
92+
if (failures.Count > 0)
93+
{
94+
Console.Error.WriteLine($"\n{failures.Count} reference(s) failed (strict mode):");
95+
foreach (var f in failures)
96+
Console.Error.WriteLine($" - {f}");
97+
Environment.Exit(1);
98+
}
99+
80100
Console.WriteLine("Done!");
81101
}
82102

83103
/// <summary>
84-
/// Run one language pipeline. On any failure (tool missing, network down, empty model)
85-
/// the existing committed docs are left untouched so the site keeps building.
104+
/// Run one language pipeline. On any failure (tool missing, network down, empty model) the
105+
/// existing committed docs are left untouched so the site keeps building — UNLESS
106+
/// <paramref name="strict"/> is set, in which case the failure is recorded in
107+
/// <paramref name="failures"/> and turned into a non-zero exit by the caller (CI behaviour).
86108
/// </summary>
87-
static async Task RunLanguageAsync(string display, string outputDir, Func<Task<ApiModel?>> build, Action<string>? afterWrite = null)
109+
static async Task RunLanguageAsync(string display, string outputDir, List<string> failures, bool strict, Func<Task<ApiModel?>> build, Action<string>? afterWrite = null)
88110
{
111+
// In strict mode, "keep existing docs" is itself the failure we want to catch.
112+
string Skip(string reason)
113+
{
114+
if (strict) {
115+
Console.WriteLine($" {display}: {reason} — FAILING (strict mode).");
116+
failures.Add($"{display}: {reason}");
117+
} else {
118+
Console.WriteLine($" {display}: {reason} — keeping existing docs.");
119+
}
120+
return reason;
121+
}
122+
89123
Console.WriteLine($"Updating {display} Reference...");
90124
try {
91125
var model = await build();
92126
if (model == null) {
93-
Console.WriteLine($" {display}: extractor unavailable — keeping existing docs.");
127+
Skip("extractor produced no model");
94128
return;
95129
}
96130
var typeCount = model.Namespaces.Sum(n => n.Types.Count);
97131
if (typeCount == 0) {
98-
Console.WriteLine($" {display}: model has no types — keeping existing docs.");
132+
Skip("model has no types");
99133
return;
100134
}
101135
Util.EnsureEmptyDirectory(outputDir);
102136
MarkdownWriter.Write(model, outputDir);
103137
afterWrite?.Invoke(outputDir);
104138
Console.WriteLine($" {display}: wrote {typeCount} type page(s) to {outputDir}.");
105139
} catch (Exception ex) {
106-
Console.WriteLine($" {display} reference failed: {ex.Message}");
107-
Console.WriteLine($" Keeping existing {display} docs.");
140+
Skip($"reference failed: {ex.Message}");
108141
}
109142
}
110143

0 commit comments

Comments
 (0)