Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/docs-flatten-dlp-cli-nav.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cdot65/prisma-airs-cli": patch
---

The CLI Reference sidebar at https://cdot65.github.io/prisma-airs-cli/cli/ now lists `runtime dlp dictionaries`, `runtime dlp filtering-profiles`, `runtime dlp patterns`, and `runtime dlp profiles` as flat siblings under Runtime instead of collapsing them under a separate "Dlp" group. Achieved via a generated `docs/cli/SUMMARY.md` consumed by mkdocs `literate-nav`. No URLs or command structure changed.
40 changes: 40 additions & 0 deletions docs/cli/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
* [CLI Reference](index.md)
* Runtime
* [airs runtime api-keys](runtime/api-keys.md)
* [airs runtime bulk-scan](runtime/bulk-scan.md)
* [airs runtime customer-apps](runtime/customer-apps.md)
* [airs runtime deployment-profiles](runtime/deployment-profiles.md)
* [airs runtime dlp dictionaries](runtime/dlp/dictionaries.md)
* [airs runtime dlp filtering-profiles](runtime/dlp/filtering-profiles.md)
* [airs runtime dlp patterns](runtime/dlp/patterns.md)
* [airs runtime dlp profiles](runtime/dlp/profiles.md)
* [airs runtime dlp-gen](runtime/dlp-gen.md)
* [airs runtime dlp-profiles](runtime/dlp-profiles.md)
* [airs runtime profiles](runtime/profiles.md)
* [airs runtime resume-poll](runtime/resume-poll.md)
* [airs runtime scan](runtime/scan.md)
* [airs runtime scan-logs](runtime/scan-logs.md)
* [airs runtime topics](runtime/topics.md)
* Redteam
* [airs redteam abort](redteam/abort.md)
* [airs redteam categories](redteam/categories.md)
* [airs redteam devices](redteam/devices.md)
* [airs redteam eula](redteam/eula.md)
* [airs redteam instances](redteam/instances.md)
* [airs redteam list](redteam/list.md)
* [airs redteam prompt-sets](redteam/prompt-sets.md)
* [airs redteam prompts](redteam/prompts.md)
* [airs redteam properties](redteam/properties.md)
* [airs redteam registry-credentials](redteam/registry-credentials.md)
* [airs redteam report](redteam/report.md)
* [airs redteam scan](redteam/scan.md)
* [airs redteam status](redteam/status.md)
* [airs redteam targets](redteam/targets.md)
* Model-security
* [airs model-security groups](model-security/groups.md)
* [airs model-security install](model-security/install.md)
* [airs model-security labels](model-security/labels.md)
* [airs model-security pypi-auth](model-security/pypi-auth.md)
* [airs model-security rule-instances](model-security/rule-instances.md)
* [airs model-security rules](model-security/rules.md)
* [airs model-security scans](model-security/scans.md)
21 changes: 21 additions & 0 deletions scripts/cli-docs/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,24 @@ export function renderIndex(pages: PageNode[]): string {
'',
].join('\n');
}

export function renderSummary(pages: PageNode[]): string {
const groups = new Map<string, PageNode[]>();
for (const p of pages) {
const top = p.slug.split('/')[0];
const arr = groups.get(top) ?? [];
arr.push(p);
groups.set(top, arr);
}
const out: string[] = ['* [CLI Reference](index.md)'];
for (const top of groups.keys()) {
const label = top.charAt(0).toUpperCase() + top.slice(1);
out.push(`* ${label}`);
const sorted = (groups.get(top) ?? []).slice().sort((a, b) => a.title.localeCompare(b.title));
for (const p of sorted) {
out.push(` * [airs ${p.title}](${p.slug}.md)`);
}
}
out.push('');
return out.join('\n');
}
6 changes: 5 additions & 1 deletion scripts/gen-cli-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { buildProgram } from '../src/cli/program.js';
import { loadExamples } from './cli-docs/examples.js';
import { renderGroupPage, renderIndex } from './cli-docs/render.js';
import { renderGroupPage, renderIndex, renderSummary } from './cli-docs/render.js';
import { collectPages, walkProgram } from './cli-docs/walk.js';

const OUT_DIR = 'docs/cli';
Expand All @@ -26,6 +26,10 @@ export function generate(outDir = OUT_DIR, examplesDir = EXAMPLES_DIR): string[]
const indexFile = join(outDir, 'index.md');
writeFileSync(indexFile, renderIndex(pages));
written.push(indexFile);

const summaryFile = join(outDir, 'SUMMARY.md');
writeFileSync(summaryFile, renderSummary(pages));
written.push(summaryFile);
return written;
}

Expand Down
107 changes: 106 additions & 1 deletion tests/unit/scripts/render.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import type { CommandNode, ExampleMap, PageNode } from '../../../scripts/cli-docs/model.js';
import { renderGroupPage, renderIndex } from '../../../scripts/cli-docs/render.js';
import { renderGroupPage, renderIndex, renderSummary } from '../../../scripts/cli-docs/render.js';

const scan: CommandNode = {
path: 'runtime scan',
Expand Down Expand Up @@ -60,3 +60,108 @@ describe('renderIndex', () => {
expect(idx).toContain('runtime/scan.md');
});
});

describe('renderSummary', () => {
const pages: PageNode[] = [
{
slug: 'runtime/api-keys',
title: 'runtime api-keys',
groupPath: 'runtime api-keys',
commands: [scan],
},
{
slug: 'runtime/dlp/dictionaries',
title: 'runtime dlp dictionaries',
groupPath: 'runtime dlp dictionaries',
commands: [scan],
},
{
slug: 'runtime/dlp/filtering-profiles',
title: 'runtime dlp filtering-profiles',
groupPath: 'runtime dlp filtering-profiles',
commands: [scan],
},
{
slug: 'runtime/dlp/patterns',
title: 'runtime dlp patterns',
groupPath: 'runtime dlp patterns',
commands: [scan],
},
{
slug: 'runtime/dlp/profiles',
title: 'runtime dlp profiles',
groupPath: 'runtime dlp profiles',
commands: [scan],
},
{
slug: 'runtime/dlp-gen',
title: 'runtime dlp-gen',
groupPath: 'runtime dlp-gen',
commands: [scan],
},
{
slug: 'runtime/dlp-profiles',
title: 'runtime dlp-profiles',
groupPath: 'runtime dlp-profiles',
commands: [scan],
},
{ slug: 'runtime/scan', title: 'runtime scan', groupPath: 'runtime scan', commands: [scan] },
{ slug: 'redteam/scan', title: 'redteam scan', groupPath: 'redteam scan', commands: [scan] },
{
slug: 'model-security/groups',
title: 'model-security groups',
groupPath: 'model-security groups',
commands: [scan],
},
];

it('groups pages by top-level command with DLP entries flat under Runtime', () => {
const out = renderSummary(pages);
const lines = out.split('\n');
const runtimeIdx = lines.findIndex((l) => l === '* Runtime');
const redteamIdx = lines.findIndex((l) => l === '* Redteam');
expect(runtimeIdx).toBeGreaterThanOrEqual(0);
expect(redteamIdx).toBeGreaterThan(runtimeIdx);
const runtimeBlock = lines
.slice(runtimeIdx + 1, redteamIdx)
.filter((l) => l.startsWith(' *'));
// DLP entries listed as flat siblings — single-level indent, not nested under another group
expect(runtimeBlock).toContain(
' * [airs runtime dlp dictionaries](runtime/dlp/dictionaries.md)',
);
expect(runtimeBlock).toContain(
' * [airs runtime dlp filtering-profiles](runtime/dlp/filtering-profiles.md)',
);
expect(runtimeBlock).toContain(' * [airs runtime dlp patterns](runtime/dlp/patterns.md)');
expect(runtimeBlock).toContain(' * [airs runtime dlp profiles](runtime/dlp/profiles.md)');
// No nested "Dlp" group label
expect(out).not.toMatch(/^\s*\*\s+Dlp\s*$/m);
});

it('preserves existing flat pages (dlp-gen, dlp-profiles) without duplication', () => {
const out = renderSummary(pages);
expect(out.match(/runtime\/dlp-gen\.md/g)?.length).toBe(1);
expect(out.match(/runtime\/dlp-profiles\.md/g)?.length).toBe(1);
});

it('sorts entries within a group by title alphabetically', () => {
const out = renderSummary(pages);
const lines = out.split('\n');
const runtimeLinks = lines
.filter((l) => l.startsWith(' * [airs runtime'))
.map((l) => l.match(/\[(airs runtime[^\]]+)\]/)?.[1] ?? '');
const sorted = [...runtimeLinks].sort();
expect(runtimeLinks).toEqual(sorted);
});

it('emits index.md as the section landing', () => {
expect(renderSummary(pages)).toContain('* [CLI Reference](index.md)');
});

it('handles all three top-level groups', () => {
const out = renderSummary(pages);
expect(out).toContain('* Runtime');
expect(out).toContain('* Redteam');
expect(out).toContain('* Model-security');
});
});
Loading