@ankhorage/paradox 0.1.26 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +53 -61
- package/dist/analyze/analyze.d.ts +2 -2
- package/dist/analyze/analyze.js +51 -35
- package/dist/analyze/badges.d.ts +2 -1
- package/dist/analyze/badges.js +14 -4
- package/dist/analyze/components.js +12 -11
- package/dist/analyze/documentation/collectDocumentationCommentsAsync.d.ts +11 -0
- package/dist/analyze/documentation/collectDocumentationCommentsAsync.js +72 -0
- package/dist/analyze/documentation/findings.d.ts +5 -0
- package/dist/analyze/documentation/findings.js +17 -0
- package/dist/analyze/documentation/validateDocumentationPolicyAsync.d.ts +13 -0
- package/dist/analyze/documentation/validateDocumentationPolicyAsync.js +150 -0
- package/dist/analyze/documentation/validateReferencesAsync.d.ts +11 -0
- package/dist/analyze/documentation/validateReferencesAsync.js +148 -0
- package/dist/analyze/exports.d.ts +4 -0
- package/dist/analyze/exports.js +14 -7
- package/dist/analyze/readmeConfig.d.ts +1 -3
- package/dist/analyze/readmeConfig.js +8 -19
- package/dist/analyze/readmeUsage.d.ts +7 -10
- package/dist/analyze/readmeUsage.js +124 -48
- package/dist/analyze/semantic/docBlocks.js +18 -48
- package/dist/analyze/semantic/exports.js +1 -3
- package/dist/analyze/semantic/model.d.ts +0 -2
- package/dist/analyze/semantic/paradoxComment.d.ts +1 -11
- package/dist/analyze/semantic/paradoxComment.js +1 -43
- package/dist/analyze/semantic/tagRegistry.js +2 -1
- package/dist/analyze/sourceFunctions.js +4 -4
- package/dist/analyze/types.d.ts +31 -24
- package/dist/analyze/usage.d.ts +2 -2
- package/dist/analyze/usage.js +3 -33
- package/dist/analyze/utils/getExportMetadata.js +11 -40
- package/dist/analyze/utils/parseParadoxComment.d.ts +12 -9
- package/dist/analyze/utils/parseParadoxComment.js +66 -78
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +3 -2
- package/dist/cli/standalone.js +11 -0
- package/dist/config/defineParadoxConfig.d.ts +1 -1
- package/dist/doc-tags/registry.d.ts +28 -32
- package/dist/doc-tags/registry.js +35 -39
- package/dist/index.d.ts +1 -1
- package/dist/model/buildModel.d.ts +26 -19
- package/dist/model/buildModel.js +33 -99
- package/dist/model/types.d.ts +27 -20
- package/dist/paths/policy.d.ts +1 -1
- package/dist/render/renderers/diagrams.js +1 -7
- package/dist/render/renderers/html.js +93 -70
- package/dist/render/renderers/markdown.js +139 -86
- package/dist/render/toFileStem.d.ts +2 -0
- package/dist/render/toFileStem.js +8 -0
- package/dist/{config/types.d.ts → types/config.d.ts} +3 -5
- package/dist/write/write.d.ts +1 -1
- package/package.json +9 -5
- package/dist/analyze/readmeCli.d.ts +0 -9
- package/dist/analyze/readmeCli.js +0 -33
- package/dist/analyze/utils/getLeadingParadoxComment.d.ts +0 -10
- package/dist/analyze/utils/getLeadingParadoxComment.js +0 -16
- /package/dist/{config/types.js → types/config.js} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { slugifyAscii } from '@ankhorage/utility/string';
|
|
1
2
|
/***
|
|
2
3
|
* Renders a deterministic static HTML documentation app.
|
|
3
4
|
*/
|
|
4
5
|
export function renderHtml({ diagrams, model, }) {
|
|
5
6
|
const sourceAreas = getSourceAreas(model);
|
|
6
|
-
const cliScenarios = getReadmeCliScenarios(model);
|
|
7
7
|
const exportsByModule = groupBy(model.exports, (item) => item.modulePath);
|
|
8
8
|
return {
|
|
9
9
|
indexHtml: `<!doctype html>
|
|
@@ -140,7 +140,7 @@ export function renderHtml({ diagrams, model, }) {
|
|
|
140
140
|
</aside>
|
|
141
141
|
<main class="content">
|
|
142
142
|
<section id="view-home" class="view" data-view="home">
|
|
143
|
-
${renderHomeView(model, diagrams,
|
|
143
|
+
${renderHomeView(model, diagrams, exportsByModule)}
|
|
144
144
|
</section>
|
|
145
145
|
${sourceAreas.map(renderSourceAreaView).join('')}
|
|
146
146
|
</main>
|
|
@@ -196,7 +196,7 @@ export function renderHtml({ diagrams, model, }) {
|
|
|
196
196
|
/***
|
|
197
197
|
* Renders the Home view that keeps public API and package-level information together.
|
|
198
198
|
*/
|
|
199
|
-
function renderHomeView(model, diagrams,
|
|
199
|
+
function renderHomeView(model, diagrams, exportsByModule) {
|
|
200
200
|
return `
|
|
201
201
|
<section class="panel">
|
|
202
202
|
<h2>Package overview</h2>
|
|
@@ -211,7 +211,8 @@ function renderHomeView(model, diagrams, cliScenarios, exportsByModule) {
|
|
|
211
211
|
${model.entrypoints.map((entrypoint) => `<li><code>${escapeHtml(entrypoint)}</code></li>`).join('')}
|
|
212
212
|
</ul>
|
|
213
213
|
</section>
|
|
214
|
-
${
|
|
214
|
+
${renderUsagePanel(model)}
|
|
215
|
+
${renderFindingsPanel(model)}
|
|
215
216
|
<section class="panel">
|
|
216
217
|
<h2>Modules</h2>
|
|
217
218
|
${model.modules.map(renderModuleCard).join('')}
|
|
@@ -236,36 +237,62 @@ function renderHomeView(model, diagrams, cliScenarios, exportsByModule) {
|
|
|
236
237
|
</section>`;
|
|
237
238
|
}
|
|
238
239
|
/***
|
|
239
|
-
* Renders
|
|
240
|
+
* Renders complete CLI and programmatic usage documentation.
|
|
240
241
|
*/
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
model.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return '';
|
|
258
|
-
return `<article class="item" data-search="${escapeAttribute([scenario.name, scenario.description ?? ''].join(' '))}">
|
|
259
|
-
<h3>${escapeHtml(scenario.name)}</h3>
|
|
260
|
-
${scenario.description === null ? '' : `<p>${escapeHtml(scenario.description)}</p>`}
|
|
261
|
-
${diagram === undefined ? '' : renderDiagramCard(diagram)}
|
|
262
|
-
</article>`;
|
|
263
|
-
})
|
|
264
|
-
.join('')}
|
|
242
|
+
function renderUsagePanel(model) {
|
|
243
|
+
return `<section class="panel" data-search="${escapeAttribute([
|
|
244
|
+
'usage',
|
|
245
|
+
model.usage.command,
|
|
246
|
+
...model.usageEntries.flatMap((entry) => [
|
|
247
|
+
entry.title ?? '',
|
|
248
|
+
entry.description ?? '',
|
|
249
|
+
entry.sourcePath,
|
|
250
|
+
]),
|
|
251
|
+
].join(' '))}">
|
|
252
|
+
<h2>Usage</h2>
|
|
253
|
+
<article class="item">
|
|
254
|
+
<h3>CLI</h3>
|
|
255
|
+
<pre>${escapeHtml(model.usage.command)}</pre>
|
|
256
|
+
</article>
|
|
257
|
+
${model.usageEntries.map(renderUsageEntry).join('')}
|
|
265
258
|
</section>`;
|
|
266
259
|
}
|
|
267
260
|
/***
|
|
268
|
-
* Renders one source
|
|
261
|
+
* Renders one source-backed usage entry in the complete documentation app.
|
|
262
|
+
*/
|
|
263
|
+
function renderUsageEntry(entry) {
|
|
264
|
+
return `<article class="item" data-search="${escapeAttribute([
|
|
265
|
+
entry.title ?? '',
|
|
266
|
+
entry.description ?? '',
|
|
267
|
+
entry.sourcePath,
|
|
268
|
+
...entry.see,
|
|
269
|
+
...entry.security,
|
|
270
|
+
].join(' '))}">
|
|
271
|
+
<h3>${escapeHtml(entry.title ?? 'Usage')}</h3>
|
|
272
|
+
<p class="muted"><code>${escapeHtml(entry.sourcePath)}</code></p>
|
|
273
|
+
${entry.description === null ? '' : `<p>${escapeHtml(entry.description)}</p>`}
|
|
274
|
+
${renderReferenceMetadata(entry)}
|
|
275
|
+
<pre>${escapeHtml(entry.code)}</pre>
|
|
276
|
+
</article>`;
|
|
277
|
+
}
|
|
278
|
+
/***
|
|
279
|
+
* Renders policy findings so complete docs expose the same evidence consumed by Doctor.
|
|
280
|
+
*/
|
|
281
|
+
function renderFindingsPanel(model) {
|
|
282
|
+
if (model.findings.length === 0) {
|
|
283
|
+
return '<section class="panel"><h2>Policy</h2><p>No documentation findings.</p></section>';
|
|
284
|
+
}
|
|
285
|
+
return `<section class="panel">
|
|
286
|
+
<h2>Policy findings</h2>
|
|
287
|
+
${model.findings
|
|
288
|
+
.map((finding) => `<article class="item" data-search="${escapeAttribute([finding.ruleId, finding.severity, finding.message, finding.sourcePath ?? ''].join(' '))}">
|
|
289
|
+
<h3>${escapeHtml(finding.ruleId)}</h3>
|
|
290
|
+
<p><strong>${escapeHtml(finding.severity)}</strong> — ${escapeHtml(finding.message)}</p>
|
|
291
|
+
${finding.sourcePath === null ? '' : `<p class="muted"><code>${escapeHtml(finding.sourcePath)}${finding.line === null ? '' : `:${finding.line}`}</code></p>`}
|
|
292
|
+
</article>`)
|
|
293
|
+
.join('')}
|
|
294
|
+
</section>`;
|
|
295
|
+
}
|
|
269
296
|
/***
|
|
270
297
|
* Renders one source file entry in the left navigation.
|
|
271
298
|
*/
|
|
@@ -281,7 +308,7 @@ function renderSourceNavItem(area) {
|
|
|
281
308
|
* Renders the right-hand source area view for a selected file.
|
|
282
309
|
*/
|
|
283
310
|
function renderSourceAreaView(area) {
|
|
284
|
-
return `<section id="view-${
|
|
311
|
+
return `<section id="view-${slugifyAscii(area.path)}" class="view" data-view="${escapeAttribute(area.path)}" hidden>
|
|
285
312
|
<section class="panel">
|
|
286
313
|
<h2>${escapeHtml(area.path)}</h2>
|
|
287
314
|
${area.functions.map(renderSourceFunctionCard).join('')}
|
|
@@ -292,10 +319,17 @@ function renderSourceAreaView(area) {
|
|
|
292
319
|
* Renders one source function card in a source-area view.
|
|
293
320
|
*/
|
|
294
321
|
function renderSourceFunctionCard(item) {
|
|
295
|
-
return `<article class="item" data-search="${escapeAttribute([
|
|
322
|
+
return `<article class="item" data-search="${escapeAttribute([
|
|
323
|
+
item.name,
|
|
324
|
+
item.sourceLocation.filePath,
|
|
325
|
+
item.description ?? '',
|
|
326
|
+
...item.see,
|
|
327
|
+
...item.security,
|
|
328
|
+
].join(' '))}">
|
|
296
329
|
<h3>${escapeHtml(item.name)}</h3>
|
|
297
330
|
<p class="muted"><code>${escapeHtml(item.sourceLocation.filePath)}:${item.sourceLocation.line}:${item.sourceLocation.column}</code></p>
|
|
298
331
|
${item.description === null ? '<p class="empty">No description available.</p>' : `<p>${escapeHtml(item.description)}</p>`}
|
|
332
|
+
${renderReferenceMetadata(item)}
|
|
299
333
|
</article>`;
|
|
300
334
|
}
|
|
301
335
|
/***
|
|
@@ -306,22 +340,6 @@ function getSourceAreas(model) {
|
|
|
306
340
|
.map(([path, functions]) => ({ path, functions }))
|
|
307
341
|
.sort((left, right) => left.path.localeCompare(right.path));
|
|
308
342
|
}
|
|
309
|
-
/***
|
|
310
|
-
* Selects bin scenarios that should be shown on the Home page.
|
|
311
|
-
*/
|
|
312
|
-
function getReadmeCliScenarios(model) {
|
|
313
|
-
if (model.readmeCli === null)
|
|
314
|
-
return [];
|
|
315
|
-
return model.sequenceScenarios.filter((scenario) => scenario.kind === 'bin');
|
|
316
|
-
}
|
|
317
|
-
/***
|
|
318
|
-
* Finds the generated Mermaid artifact for a sequence scenario.
|
|
319
|
-
/***
|
|
320
|
-
* Finds the generated Mermaid artifact for a sequence scenario.
|
|
321
|
-
*/
|
|
322
|
-
function findScenarioDiagram(diagrams, scenario) {
|
|
323
|
-
return diagrams.find((diagram) => diagram.path === `diagrams/sequences/${toFileStem(scenario.name)}.mmd`);
|
|
324
|
-
}
|
|
325
343
|
/***
|
|
326
344
|
* Renders module metadata on the Home page.
|
|
327
345
|
*/
|
|
@@ -337,17 +355,22 @@ function renderModuleCard(module) {
|
|
|
337
355
|
* Renders one public API export card on the Home page.
|
|
338
356
|
*/
|
|
339
357
|
function renderExportCard(item) {
|
|
340
|
-
return `<article class="item" id="symbol-${
|
|
358
|
+
return `<article class="item" id="symbol-${slugifyAscii(item.name)}" data-search="${escapeAttribute([
|
|
341
359
|
item.name,
|
|
360
|
+
item.title ?? '',
|
|
342
361
|
item.kind,
|
|
343
362
|
item.modulePath,
|
|
344
363
|
item.description ?? '',
|
|
364
|
+
...item.see,
|
|
365
|
+
...item.security,
|
|
345
366
|
...item.relatedSymbols,
|
|
346
367
|
...item.signatures.map((signature) => signature.label),
|
|
347
368
|
].join(' '))}">
|
|
348
|
-
<h4>${escapeHtml(item.name)}</h4>
|
|
369
|
+
<h4>${escapeHtml(item.title ?? item.name)}</h4>
|
|
370
|
+
${item.title === null || item.title === item.name ? '' : `<p class="muted">Symbol: <code>${escapeHtml(item.name)}</code></p>`}
|
|
349
371
|
<p class="muted">${escapeHtml(item.kind)} • <code>${escapeHtml(item.sourceLocation.filePath)}:${item.sourceLocation.line}:${item.sourceLocation.column}</code></p>
|
|
350
372
|
${item.description ? `<p>${escapeHtml(item.description)}</p>` : ''}
|
|
373
|
+
${renderReferenceMetadata(item)}
|
|
351
374
|
<p><strong>Export paths:</strong> ${renderInlineCodeList(item.exportPaths)}</p>
|
|
352
375
|
<div><strong>Related symbols:</strong> ${item.relatedSymbols.length > 0 ? renderChipList(item.relatedSymbols) : '<span class="empty">None</span>'}</div>
|
|
353
376
|
${item.signatures.length > 0 ? renderSignatureBlock(item) : ''}
|
|
@@ -404,15 +427,18 @@ function renderMemberTable(item) {
|
|
|
404
427
|
* Renders one detected component card on the Home page.
|
|
405
428
|
*/
|
|
406
429
|
function renderComponentCard(component) {
|
|
407
|
-
return `<article class="item" id="component-${
|
|
430
|
+
return `<article class="item" id="component-${slugifyAscii(component.name)}" data-search="${escapeAttribute([
|
|
408
431
|
component.name,
|
|
409
432
|
component.modulePath,
|
|
410
433
|
component.description ?? '',
|
|
434
|
+
...component.see,
|
|
435
|
+
...component.security,
|
|
411
436
|
...component.props.map((prop) => `${prop.name} ${prop.type}`),
|
|
412
437
|
].join(' '))}">
|
|
413
438
|
<h3>${escapeHtml(component.name)}</h3>
|
|
414
439
|
<p class="muted"><code>${escapeHtml(component.sourceLocation.filePath)}:${component.sourceLocation.line}:${component.sourceLocation.column}</code></p>
|
|
415
440
|
${component.description ? `<p>${escapeHtml(component.description)}</p>` : ''}
|
|
441
|
+
${renderReferenceMetadata(component)}
|
|
416
442
|
<p><strong>Export paths:</strong> ${renderInlineCodeList(component.exportPaths)}</p>
|
|
417
443
|
<table>
|
|
418
444
|
<thead><tr><th>Prop</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
|
|
@@ -443,6 +469,22 @@ function renderDiagramCard(diagram) {
|
|
|
443
469
|
</details>
|
|
444
470
|
</article>`;
|
|
445
471
|
}
|
|
472
|
+
/***
|
|
473
|
+
* Renders validated external references and security-test evidence.
|
|
474
|
+
*/
|
|
475
|
+
function renderReferenceMetadata(metadata) {
|
|
476
|
+
const see = metadata.see.length === 0
|
|
477
|
+
? ''
|
|
478
|
+
: `<p><strong>See also:</strong> ${metadata.see
|
|
479
|
+
.map((url) => `<a href="${escapeAttribute(url)}" rel="noreferrer">${escapeHtml(url)}</a>`)
|
|
480
|
+
.join(', ')}</p>`;
|
|
481
|
+
const security = metadata.security.length === 0
|
|
482
|
+
? ''
|
|
483
|
+
: `<p><strong>Security tests:</strong> ${metadata.security
|
|
484
|
+
.map((reference) => `<code>${escapeHtml(reference)}</code>`)
|
|
485
|
+
.join(', ')}</p>`;
|
|
486
|
+
return `${see}${security}`;
|
|
487
|
+
}
|
|
446
488
|
/***
|
|
447
489
|
* Renders an inline comma-separated list of code values.
|
|
448
490
|
*/
|
|
@@ -477,25 +519,6 @@ function groupBy(items, key) {
|
|
|
477
519
|
}
|
|
478
520
|
return new Map([...groups.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
479
521
|
}
|
|
480
|
-
/***
|
|
481
|
-
* Converts a label to a stable HTML anchor id fragment.
|
|
482
|
-
*/
|
|
483
|
-
function toAnchorId(value) {
|
|
484
|
-
return value
|
|
485
|
-
.toLowerCase()
|
|
486
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
487
|
-
.replace(/^-|-$/g, '');
|
|
488
|
-
}
|
|
489
|
-
/***
|
|
490
|
-
* Converts a scenario name to the generated Mermaid file stem.
|
|
491
|
-
*/
|
|
492
|
-
function toFileStem(value) {
|
|
493
|
-
return value
|
|
494
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
495
|
-
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
496
|
-
.replace(/^-+|-+$/g, '')
|
|
497
|
-
.toLowerCase();
|
|
498
|
-
}
|
|
499
522
|
/***
|
|
500
523
|
* Escapes user-controlled text for safe HTML rendering.
|
|
501
524
|
*/
|