Skip to content

Commit 4ca8338

Browse files
committed
workflow test
1 parent 923bb56 commit 4ca8338

File tree

3 files changed

+66
-30
lines changed

3 files changed

+66
-30
lines changed

src/ReportGenerator.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,15 @@ export class ReportGenerator {
239239
private static renderMKDOCSReport(tmo: ThreatModel, ctx: any): string {
240240
resetHeadingNumbers();
241241
ctx.useMarkDown_attr_list_ext = ctx.useMarkDown_attr_list_ext ?? true;
242-
ctx.process_toc = false;
242+
const includeToc = Boolean(ctx.process_toc ?? false);
243+
ctx.process_toc = includeToc;
243244
ctx.process_prepost_md = false;
244245

245246
const lines: string[] = [];
246247
const rootHeaderLevel = ctx.rootHeaderLevel || 1;
247248

248-
// Root part: no TOC, with summary
249-
lines.push(this.renderTmReportPart(tmo, true, false, true, rootHeaderLevel, ctx));
249+
// Root part: TOC is optional for MKDOCS, driven by process_toc
250+
lines.push(this.renderTmReportPart(tmo, true, includeToc, true, rootHeaderLevel, ctx));
250251

251252
// Descendants
252253
for (const descendant of tmo.getDescendantsTM()) {

src/scripts/build-mkdocs-site.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,18 +190,24 @@ function stageTM(
190190
tmStagingDir: string,
191191
tm: TMEntry,
192192
docsDir: string,
193+
markdownOverride: string | null = null,
193194
options: { expectPdf?: boolean } = {}
194195
): StagedTMEntry | null {
195-
const mdSource = resolveBuiltMarkdown(tmStagingDir, tm);
196-
if (!mdSource) {
197-
console.warn(`No markdown output found for ${tm.name}; skipping`);
198-
return null;
196+
let mdContent: string;
197+
if (markdownOverride !== null) {
198+
mdContent = markdownOverride;
199+
} else {
200+
const mdSource = resolveBuiltMarkdown(tmStagingDir, tm);
201+
if (!mdSource) {
202+
console.warn(`No markdown output found for ${tm.name}; skipping`);
203+
return null;
204+
}
205+
mdContent = fs.readFileSync(mdSource, 'utf-8');
199206
}
200207

201208
const tmDocsDir = path.join(docsDir, tm.id);
202209
fs.mkdirSync(tmDocsDir, { recursive: true });
203210

204-
const mdContent = fs.readFileSync(mdSource, 'utf-8');
205211
fs.writeFileSync(path.join(tmDocsDir, 'index.md'), mdContent, 'utf-8');
206212

207213
const htmlSource = path.join(tmStagingDir, `${tm.id}.html`);
@@ -265,7 +271,7 @@ export function buildMkdocsSite(
265271
templateSiteFolderDST,
266272
template = 'MKdocs',
267273
pdfArtifactLink,
268-
headerNumbering = false,
274+
headerNumbering = true,
269275
...tmOptions
270276
} = options;
271277

@@ -318,26 +324,52 @@ export function buildMkdocsSite(
318324
fs.mkdirSync(stagingDir, { recursive: true });
319325

320326
const staged: StagedTMEntry[] = [];
327+
const mkdocsMarkdownByTm = new Map<string, string>();
321328
const built: Array<{ tm: TMEntry; tmStagingDir: string }> = [];
322329
for (const tm of tmList) {
323330
console.log(`\n--- Building ${tm.name} ---`);
324331
const tmStagingDir = path.join(stagingDir, tm.name);
325332
fs.mkdirSync(tmStagingDir, { recursive: true });
326333

327334
try {
328-
buildSingleTM(tm.yamlPath, tmStagingDir, { ...tmOptions, template, headerNumbering });
335+
// Pass 1: MkDocs markdown source (no TOC, no numbering)
336+
buildSingleTM(tm.yamlPath, tmStagingDir, {
337+
...tmOptions,
338+
template,
339+
headerNumbering: false,
340+
forceToc: false,
341+
generatePDF: false,
342+
});
343+
344+
const mkdocsMdSource = resolveBuiltMarkdown(tmStagingDir, tm);
345+
if (!mkdocsMdSource) {
346+
console.warn(`No markdown output found for ${tm.name}; skipping`);
347+
continue;
348+
}
349+
mkdocsMarkdownByTm.set(tm.id, fs.readFileSync(mkdocsMdSource, 'utf-8'));
350+
351+
// Pass 2: HTML/PDF artifacts (with TOC + numbering)
352+
buildSingleTM(tm.yamlPath, tmStagingDir, {
353+
...tmOptions,
354+
template,
355+
headerNumbering,
356+
forceToc: true,
357+
generatePDF: Boolean(tmOptions.generatePDF),
358+
});
359+
329360
built.push({ tm, tmStagingDir });
330361
} catch (err) {
331362
console.error(`ERROR building ${tm.name}: ${err}`);
332363
}
333364
}
334365

335366
for (const entry of built) {
336-
const stagedTm = stageTM(entry.tmStagingDir, entry.tm, absDocsDir, {
367+
const mkdocsMarkdown = mkdocsMarkdownByTm.get(entry.tm.id) ?? null;
368+
const stagedTmWithMarkdown = stageTM(entry.tmStagingDir, entry.tm, absDocsDir, mkdocsMarkdown, {
337369
expectPdf: Boolean(tmOptions.generatePDF),
338370
});
339-
if (stagedTm) {
340-
staged.push(stagedTm);
371+
if (stagedTmWithMarkdown) {
372+
staged.push(stagedTmWithMarkdown);
341373
}
342374
}
343375

@@ -390,7 +422,7 @@ Options:
390422
--siteUrl <url> Optional canonical site URL in mkdocs.yml
391423
--templateSiteFolderSRC <path> Extra site overlay source folder
392424
--templateSiteFolderDST <path> Overlay destination (default: <MKDocsDir>)
393-
--headerNumbering Enable auto heading numbers (default: OFF for MkDocs)
425+
--headerNumbering Enable auto heading numbers (default: ON)
394426
--no-headerNumbering Force-disable auto heading numbers
395427
--generatePDF Generate PDF per TM
396428
--pdfHeaderNote <text> PDF page header text
@@ -411,8 +443,8 @@ const siteName = parseOption(cliArgs, 'siteName') ?? 'Threat Models';
411443
const siteUrl = parseOption(cliArgs, 'siteUrl');
412444
const templateSiteFolderSRC = parseOption(cliArgs, 'templateSiteFolderSRC');
413445
const templateSiteFolderDST = parseOption(cliArgs, 'templateSiteFolderDST');
414-
// MkDocs default: no heading numbering (site TOC/navigation already provides structure).
415-
const headerNumbering = parseFlag(cliArgs, 'headerNumbering') && !parseFlag(cliArgs, 'no-headerNumbering');
446+
// MkDocs default: heading numbering is enabled unless explicitly disabled.
447+
const headerNumbering = !parseFlag(cliArgs, 'no-headerNumbering');
416448
const generatePDF = parseFlag(cliArgs, 'generatePDF');
417449
const pdfHeaderNote = parseOption(cliArgs, 'pdfHeaderNote') ?? 'Private and confidential';
418450
const pdfArtifactLink = parseOption(cliArgs, 'pdfArtifactLink');

src/scripts/build-threat-model.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export interface BuildTMOptions {
156156
mainTitle?: string;
157157
generatePDF?: boolean;
158158
headerNumbering?: boolean;
159+
forceToc?: boolean;
159160
fileName?: string;
160161
pdfHeaderNote?: string;
161162
pdfArtifactLink?: string;
@@ -169,6 +170,7 @@ export function buildSingleTM(yamlFile: string, outputDir: string, options: Buil
169170
mainTitle = '',
170171
generatePDF = false,
171172
headerNumbering = true,
173+
forceToc,
172174
fileName,
173175
pdfHeaderNote = 'Private and confidential',
174176
assetFolders = [DEFAULT_ASSET_FOLDER]
@@ -188,27 +190,14 @@ export function buildSingleTM(yamlFile: string, outputDir: string, options: Buil
188190
fileName,
189191
// Canonical ReportGenerator switch for heading numbering.
190192
process_heading_numbering: headerNumbering,
193+
process_toc: forceToc,
191194
// Keep alias for backward compatibility with existing callers.
192195
headerNumbering,
193196
});
194197

195198
copyAssetFolders(assetFolders, absOutputDir);
196199

197200
const modelId = fileName || (tmo as any)._id || (tmo as any).id;
198-
generateHtmlFromMarkdown(absOutputDir, modelId);
199-
200-
// Generate PDF if requested
201-
if (generatePDF) {
202-
console.log('Generating PDF...');
203-
const pdfRenderer = new PDFRenderer(tmo);
204-
const pdfPath = path.join(absOutputDir, `${modelId}.pdf`);
205-
try {
206-
pdfRenderer.renderToPDF(pdfPath, { headerNote: pdfHeaderNote });
207-
} catch (err: any) {
208-
console.warn(`PDF generation failed: ${err.message}`);
209-
}
210-
}
211-
212201
const imgDir = path.join(absOutputDir, 'img');
213202
console.log('Generating PlantUML diagrams...');
214203

@@ -238,6 +227,20 @@ export function buildSingleTM(yamlFile: string, outputDir: string, options: Buil
238227
console.warn(error.message);
239228
}
240229
}
230+
231+
generateHtmlFromMarkdown(absOutputDir, modelId);
232+
233+
// Generate PDF if requested
234+
if (generatePDF) {
235+
console.log('Generating PDF...');
236+
const pdfRenderer = new PDFRenderer(tmo);
237+
const pdfPath = path.join(absOutputDir, `${modelId}.pdf`);
238+
try {
239+
pdfRenderer.renderToPDF(pdfPath, { headerNote: pdfHeaderNote });
240+
} catch (err: any) {
241+
console.warn(`PDF generation failed: ${err.message}`);
242+
}
243+
}
241244
}
242245

243246
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('build-threat-model.ts')) {

0 commit comments

Comments
 (0)