Skip to content

Commit ae3b997

Browse files
committed
cli documentation
Add class to CLI output container and remove pre vertical padding t
1 parent d0f4bf8 commit ae3b997

File tree

101 files changed

+13092
-1188
lines changed

Some content is hidden

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

101 files changed

+13092
-1188
lines changed

CLI-DOCS.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# CLI Output Management System
2+
3+
This system automatically captures and caches CLI command outputs in MDX files, converting them to SVG format for consistent rendering.
4+
5+
## Setup
6+
7+
1. Configure `ansi-run.json` with your example project path and starting commit hash:
8+
9+
```json
10+
{
11+
"exampleProjectPath": "/path/to/example/project",
12+
"startingHash": "initial-commit-hash",
13+
"config": {
14+
"cacheDir": "content/cache",
15+
"outputFormat": "svg"
16+
}
17+
}
18+
```
19+
20+
2. Install dependencies:
21+
```bash
22+
pnpm install
23+
```
24+
25+
## Usage
26+
27+
### Adding CLI Commands to MDX
28+
29+
Use special `cli` code blocks in your MDX files:
30+
31+
```mdx
32+
Here's how to check status:
33+
34+
```cli
35+
but status
36+
```
37+
38+
The output will be automatically captured and cached.
39+
```
40+
41+
### Restore Commands
42+
43+
To restore to a specific state before running commands, add a restore comment:
44+
45+
```mdx
46+
{/* restore [commit-hash] */}
47+
48+
```cli
49+
but status
50+
```
51+
52+
This will run `but restore [commit-hash]` before executing the cli command.
53+
```
54+
55+
### Updating CLI Outputs
56+
57+
Run the update script to process all MDX files and update CLI outputs:
58+
59+
```bash
60+
pnpm update-cli
61+
```
62+
63+
This will:
64+
- Read your `ansi-run.json` configuration
65+
- Change to your example project directory
66+
- Restore to the starting hash
67+
- Process all MDX files in `content/docs/`
68+
- Execute CLI commands and capture outputs
69+
- Convert outputs to SVG using ansi2html
70+
- Cache outputs in `content/cache/[hash].svg`
71+
- Update MDX files with hash references: ```cli [hash]
72+
- Report any changes detected
73+
74+
### How It Works
75+
76+
1. **Processing**: The script finds all ````cli` blocks in MDX files
77+
2. **Execution**: Commands are run in your configured example project
78+
3. **Caching**: Output is converted to SVG and stored with a content hash
79+
4. **Updates**: MDX blocks are updated with hash references
80+
5. **Rendering**: The CliBlock component renders cached SVGs or shows placeholders
81+
82+
### File Structure
83+
84+
```
85+
├── ansi-run.json # Configuration
86+
├── content/
87+
│ ├── cache/ # Cached SVG outputs
88+
│ │ ├── abc123def456.svg
89+
│ │ └── def789ghi012.svg
90+
│ └── docs/ # MDX documentation files
91+
│ └── commands/
92+
│ └── status.mdx
93+
├── scripts/
94+
│ └── update-cli-outputs.js # Main processing script
95+
└── app/
96+
└── components/
97+
├── CliBlock.tsx # Rendering component
98+
└── remark-cli.ts # MDX transformer
99+
```
100+
101+
### Example Workflow
102+
103+
1. Create a new MDX file with CLI commands:
104+
```mdx
105+
# Status Command
106+
107+
Check your workspace status:
108+
109+
```cli
110+
but status
111+
```
112+
```
113+
114+
2. Run the update script:
115+
```bash
116+
pnpm update-cli
117+
```
118+
119+
3. The script will show output like:
120+
```
121+
Processing: content/docs/commands/status.mdx
122+
Found CLI command: but status
123+
New CLI block found: but status
124+
Updated: content/docs/commands/status.mdx
125+
```
126+
127+
4. Your MDX file is now updated:
128+
```mdx
129+
# Status Command
130+
131+
Check your workspace status:
132+
133+
```cli [abc123def456]
134+
but status
135+
```
136+
```
137+
138+
5. When rendered, users see the actual command output in SVG format.
139+
140+
## Troubleshooting
141+
142+
- **Missing outputs**: Run `pnpm update-cli` to generate missing cache files
143+
- **Outdated outputs**: The script will detect hash changes and notify you
144+
- **Command failures**: Failed commands will still be cached to show error output
145+
- **Path issues**: Ensure your `ansi-run.json` paths are absolute and correct
146+
147+
### Updating CLI Outputs
148+
149+
Run the update script to process all MDX files and update CLI outputs:
150+
151+
```bash
152+
export CLICOLOR_FORCE=1
153+
export GIT_AUTHOR_DATE="2020-09-09 09:06:03 +0800"
154+
export GIT_COMMITTER_DATE="2020-10-09 09:06:03 +0800"
155+
pnpm update-cli
156+
```
157+
158+
## Commands
159+
160+
The command "man pages" are copied from `../gitbutler/cli-docs` so that changes to the commands docs can be included in changes with the code.
161+
162+
To update the command man-pages, you can run ./scripts/sync-commands.sh
163+
164+

app/(docs)/[[...slug]]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Callout } from "fumadocs-ui/components/callout"
88
import { TypeTable } from "fumadocs-ui/components/type-table"
99
import { Accordion, Accordions } from "fumadocs-ui/components/accordion"
1010
import ImageSection from "@/app/components/ImageSection"
11+
import CliBlock from "@/app/components/CliBlock"
1112
import type { ComponentProps, FC } from "react"
1213

1314
interface Param {
@@ -103,6 +104,7 @@ export default async function Page(props: { params: Promise<Param> }): Promise<R
103104
Accordion,
104105
Accordions,
105106
ImageSection,
107+
CliBlock,
106108
blockquote: Callout as unknown as FC<ComponentProps<"blockquote">>,
107109
APIPage: openapi.APIPage
108110
}}

app/components/CliBlock.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import fs from 'fs/promises';
2+
import path from 'path';
3+
4+
interface CliBlockProps {
5+
hash?: string;
6+
height?: string;
7+
lang?: 'cli' | 'ansi';
8+
children: React.ReactNode;
9+
}
10+
11+
export default async function CliBlock({ hash, height, lang = 'cli', children }: CliBlockProps) {
12+
// If no hash, render as regular code block
13+
if (!hash) {
14+
return (
15+
<div className="rounded-lg border bg-muted p-4 mb-4">
16+
<div className="font-mono text-base text-muted-foreground mb-2">$ {children}</div>
17+
<div className="text-sm text-muted-foreground">
18+
Run <code>pnpm update-cli</code> to generate output
19+
</div>
20+
</div>
21+
);
22+
}
23+
24+
// Determine which directory to use based on lang
25+
const dir = lang === 'ansi'
26+
? path.join(process.cwd(), 'public/cli-examples')
27+
: path.join(process.cwd(), 'public/cache/cli-output');
28+
const htmlPath = path.join(dir, `${hash}.html`);
29+
30+
try {
31+
// Read the HTML file content
32+
const fullHtmlContent = await fs.readFile(htmlPath, 'utf8');
33+
34+
// Extract content from body tag if present (for full HTML documents)
35+
const bodyMatch = fullHtmlContent.match(/<body[^>]*>([\s\S]*)<\/body>/i);
36+
const htmlContent = bodyMatch ? bodyMatch[1] : fullHtmlContent;
37+
38+
return (
39+
<div className="rounded-lg border bg-muted overflow-hidden mb-4">
40+
<div className="bg-gray-50 px-4 py-2 border-b">
41+
<div className="font-mono text-base text-muted-foreground">$ {children}</div>
42+
</div>
43+
<div className="p-4 cli-output-container">
44+
<div dangerouslySetInnerHTML={{ __html: htmlContent }} />
45+
</div>
46+
</div>
47+
);
48+
} catch (error) {
49+
// HTML file not found, show placeholder
50+
const errorMessage = lang === 'ansi'
51+
? 'Output not found. Run ./scripts/sync-commands.sh to sync examples.'
52+
: 'Output cache not found. Run pnpm update-cli to generate.';
53+
54+
return (
55+
<div className="rounded-lg border bg-muted p-4 mb-4">
56+
<div className="font-mono text-base text-muted-foreground mb-2">$ {children}</div>
57+
<div className="text-sm text-muted-foreground">
58+
{errorMessage} (hash: {hash}{height && `, height: ${height}`})
59+
</div>
60+
</div>
61+
);
62+
}
63+
}

app/components/ResizableIframe.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
interface ResizableIframeProps {
2+
src: string
3+
title: string
4+
sandbox: string
5+
className?: string
6+
fixedHeight?: string
7+
}
8+
9+
export default function ResizableIframe({ src, title, sandbox, className, fixedHeight }: ResizableIframeProps) {
10+
const height = fixedHeight || '200px'
11+
const minHeight = fixedHeight ? undefined : '200px'
12+
13+
return (
14+
<iframe
15+
src={src}
16+
className={className}
17+
style={{ height, minHeight }}
18+
title={title}
19+
sandbox={sandbox}
20+
/>
21+
)
22+
}

app/components/remark-cli.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { visit } from 'unist-util-visit';
2+
import type { Code } from 'mdast';
3+
import type { Plugin } from 'unified';
4+
5+
export const remarkCli: Plugin = () => {
6+
return (tree) => {
7+
visit(tree, 'code', (node: Code) => {
8+
// Handle both 'cli' and 'ansi' code blocks
9+
if (node.lang === 'cli' || node.lang === 'ansi') {
10+
const meta = node.meta || '';
11+
let hash: string | undefined;
12+
let height: string | undefined;
13+
14+
if (node.lang === 'ansi') {
15+
// For ansi blocks, the meta is the hash directly
16+
hash = meta.trim() || undefined;
17+
} else {
18+
// For cli blocks, parse the old format [hash, height]
19+
const paramsMatch = meta.match(/\[([^\]]+)\]/);
20+
if (paramsMatch) {
21+
const params = paramsMatch[1].split(',').map(p => p.trim());
22+
hash = params[0] || undefined;
23+
height = params[1] || undefined;
24+
}
25+
}
26+
27+
// Build attributes array
28+
const attributes = [];
29+
30+
// Always pass the lang attribute
31+
attributes.push({
32+
type: 'mdxJsxAttribute',
33+
name: 'lang',
34+
value: node.lang
35+
});
36+
37+
if (hash) {
38+
attributes.push({
39+
type: 'mdxJsxAttribute',
40+
name: 'hash',
41+
value: hash
42+
});
43+
}
44+
if (height) {
45+
attributes.push({
46+
type: 'mdxJsxAttribute',
47+
name: 'height',
48+
value: height
49+
});
50+
}
51+
52+
// Transform to JSX component
53+
const componentNode = {
54+
type: 'mdxJsxFlowElement',
55+
name: 'CliBlock',
56+
attributes,
57+
children: [
58+
{
59+
type: 'text',
60+
value: node.value
61+
}
62+
]
63+
};
64+
65+
Object.assign(node, componentNode);
66+
}
67+
});
68+
};
69+
};

app/global.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
@tailwind components;
33
@tailwind utilities;
44

5+
/* Import JetBrains Mono Nerd Font */
6+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
7+
58
@import "open-props/easings";
69
@import "open-props/animations";
710

@@ -62,3 +65,17 @@ iframe[src*="youtube"] {
6265
.font-accent {
6366
font-family: var(--font-accent);
6467
}
68+
69+
/* Override fumadocs description paragraph margin */
70+
p.mb-8.text-lg.text-fd-muted-foreground,
71+
p.mb-8[class*="text-fd-muted-foreground"],
72+
p[class*="mb-8"][class*="text-lg"][class*="text-fd-muted-foreground"] {
73+
@apply mb-4 !important;
74+
}
75+
76+
/* CLI output styling - remove vertical padding from pre tags */
77+
.cli-output-container pre {
78+
margin: 0;
79+
padding: 0;
80+
line-height: 22px;
81+
}

cli-examples-run.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"exampleProjectPath": "/Users/schacon/projects/why",
3+
"startingHash": "f0f437258043",
4+
"ansi_senor_path": "/Users/schacon/.cargo/bin/ansi-senor",
5+
"but_path": "/usr/local/bin/but",
6+
"config": {
7+
"cacheDir": "public/cache/cli-output",
8+
"outputFormat": "html"
9+
}
10+
}

0 commit comments

Comments
 (0)