Skip to content

Commit 8d2cfa1

Browse files
authored
feat(agents-md): support for migration docs (#175)
* feat: agents-md for migration docs * fix(agents-md): parse fumadocs include tags
1 parent 6feae4e commit 8d2cfa1

File tree

6 files changed

+298
-133
lines changed

6 files changed

+298
-133
lines changed

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -473,14 +473,15 @@ heroui agents-md [options]
473473
#### Features
474474
475475
> 1. Downloads latest HeroUI documentation from the `v3` branch
476-
> 2. Supports both React and Native documentation
477-
> 3. Generates separate sections for React or Native in the markdown file
476+
> 2. Supports React, Native, and Migration (v2→v3) documentation
477+
> 3. Generates a section for the selected library (React, Native, or Migration) in the markdown file
478478
> 4. Automatically adds `.heroui-docs/` to `.gitignore`
479479
480480
#### Agents-md Options
481481
482-
- `--react` [boolean] Include only React docs
483-
- `--native` [boolean] Include only Native docs
482+
- `--react` [boolean] Include React docs only (one library at a time)
483+
- `--native` [boolean] Include Native docs only
484+
- `--migration` [boolean] Include HeroUI v2 to v3 migration docs only
484485
- `--output <file>` [string] Target file path (e.g., `AGENTS.md`, `CLAUDE.md`)
485486
- `--ssh` [boolean] Use SSH instead of HTTPS for git clone
486487
@@ -504,10 +505,10 @@ Download Native docs:
504505
heroui agents-md --native --output CLAUDE.md
505506
```
506507
507-
Download both React and Native docs:
508+
Download migration docs (v2→v3):
508509
509510
```bash
510-
heroui agents-md --react --native --output AGENTS.md
511+
heroui agents-md --migration --output AGENTS.md
511512
```
512513
513514
Use SSH for cloning (useful if HTTPS fails):
@@ -523,7 +524,8 @@ heroui agents-md --react --ssh --output AGENTS.md
523524
3. **Injects into Markdown**: Injects the index into your specified markdown file (e.g., `AGENTS.md`) with special markers:
524525
- `<!-- HEROUI-REACT-AGENTS-MD-START -->` / `<!-- HEROUI-REACT-AGENTS-MD-END -->` for React docs
525526
- `<!-- HEROUI-NATIVE-AGENTS-MD-START -->` / `<!-- HEROUI-NATIVE-AGENTS-MD-END -->` for Native docs
526-
4. **Preserves Sections**: When updating one library, the other library's section is preserved
527+
- `<!-- HEROUI-MIGRATION-AGENTS-MD-START -->` / `<!-- HEROUI-MIGRATION-AGENTS-MD-END -->` for Migration docs
528+
4. **Single library**: Only one of React, Native, or Migration can be selected at a time
527529
528530
#### File Structure
529531
@@ -532,8 +534,9 @@ After running the command, you'll have:
532534
```
533535
your-project/
534536
├── .heroui-docs/ # Downloaded documentation (gitignored)
535-
│ ├── react/ # React documentation files
536-
│ └── native/ # Native documentation files (if selected)
537+
│ ├── react/ # React documentation files (if selected)
538+
│ ├── native/ # Native documentation files (if selected)
539+
│ └── migration/ # Migration docs (v2→v3, if selected)
537540
├── AGENTS.md # Your markdown file with injected index
538541
└── .gitignore # Updated to include .heroui-docs/
539542
```

src/actions/docs-action.ts

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
buildDocTree,
1111
collectDemoFiles,
1212
collectDocFiles,
13+
collectMigrationDocFiles,
1314
ensureGitignoreEntry,
1415
generateHerouiMdIndex,
1516
getHerouiVersions,
@@ -25,6 +26,13 @@ import {compareVersions} from 'src/scripts/helpers';
2526

2627
const DOCS_DIR_NAME = '.heroui-docs';
2728

29+
function formatSelectionText(selection: DocSelection): string {
30+
if (selection === 'react') return 'HeroUI React v3';
31+
if (selection === 'native') return 'HeroUI Native';
32+
33+
return 'HeroUI Migration (v2→v3)';
34+
}
35+
2836
function formatSize(bytes: number): string {
2937
if (bytes < 1024) return `${bytes} B`;
3038
const kb = bytes / 1024;
@@ -84,7 +92,7 @@ function validateRequirements(cwd: string, selection: DocSelection): ValidationR
8492
}
8593

8694
// For React docs: Check React >= 19.0.0
87-
if (selection === 'react' || selection === 'both') {
95+
if (selection === 'react') {
8896
const reactVersion = allDeps.react;
8997

9098
if (reactVersion) {
@@ -172,19 +180,23 @@ export async function docsAction(options: DocsOptions) {
172180
try {
173181
// Mode logic:
174182
// 1. No flags → autodetect package, prompt only if neither found (or if both found)
175-
// 2. Library flags (--react, --native) → use that selection, prompt for output file if --output not provided
183+
// 2. Library flags (--react, --native, --migration) → use that selection, prompt for output file if --output not provided
176184
// 3. --output alone → autodetect package, use provided output file
177185

178186
let selection: DocSelection;
179187
let outputFiles: string[] | undefined;
180188

181-
// Determine selection from flags
182-
if (options.react && options.native) {
183-
selection = 'both';
184-
} else if (options.react) {
185-
selection = 'react';
186-
} else if (options.native) {
187-
selection = 'native';
189+
// Determine selection from flags (only one library at a time)
190+
const flagCount = [options.react, options.native, options.migration].filter(Boolean).length;
191+
192+
if (flagCount > 1) {
193+
throw new ValidationError(
194+
'Only one library option is supported at a time. Use --react, --native, or --migration (not in combination).'
195+
);
196+
}
197+
198+
if (options.react || options.native || options.migration) {
199+
selection = options.migration ? 'migration' : options.native ? 'native' : 'react';
188200
} else {
189201
// Autodetect installed packages
190202
const {hasNative, hasReact} = detectInstalledPackages(cwd);
@@ -194,18 +206,21 @@ export async function docsAction(options: DocsOptions) {
194206
if (options.output) {
195207
selection = await promptForLibrarySelection(false);
196208
} else {
197-
const promptedOptions = await promptForOptions();
209+
const promptedOptions = await promptForOptions(false);
198210

199211
selection = promptedOptions.selection;
200212
outputFiles = promptedOptions.targetFiles;
201213
}
202214
} else if (hasReact) {
215+
// Only React found - use it automatically
203216
selection = 'react';
204217
Logger.log(chalk.dim('Detected @heroui/react, using HeroUI React v3 docs'));
205218
} else if (hasNative) {
219+
// Only Native found - use it automatically
206220
selection = 'native';
207221
Logger.log(chalk.dim('Detected heroui-native, using HeroUI Native docs'));
208222
} else {
223+
// Neither found - prompt for selection with warning
209224
if (options.output) {
210225
selection = await promptForLibrarySelection(true);
211226
} else {
@@ -242,12 +257,7 @@ export async function docsAction(options: DocsOptions) {
242257

243258
const docsPath = path.join(cwd, DOCS_DIR_NAME);
244259

245-
const selectionText =
246-
selection === 'both'
247-
? 'HeroUI React v3 and HeroUI Native'
248-
: selection === 'react'
249-
? 'HeroUI React v3'
250-
: 'HeroUI Native';
260+
const selectionText = formatSelectionText(selection);
251261

252262
Logger.log(`\nDownloading ${selectionText} documentation to ${chalk.cyan(DOCS_DIR_NAME)}...`);
253263

@@ -265,9 +275,10 @@ export async function docsAction(options: DocsOptions) {
265275
// Collect and build trees for selected docs
266276
let reactSections: ReturnType<typeof buildDocTree> | undefined;
267277
let nativeSections: ReturnType<typeof buildDocTree> | undefined;
278+
let migrationSections: ReturnType<typeof buildDocTree> | undefined;
268279
let reactDemoFiles: {relativePath: string}[] | undefined;
269280

270-
if (selection === 'react' || selection === 'both') {
281+
if (selection === 'react') {
271282
const reactDocsPath = path.join(docsPath, 'react');
272283

273284
if (fs.existsSync(reactDocsPath)) {
@@ -276,15 +287,14 @@ export async function docsAction(options: DocsOptions) {
276287
reactSections = buildDocTree(reactDocFiles);
277288
}
278289

279-
// Collect demo files
280290
const reactDemosPath = path.join(docsPath, 'react', 'demos');
281291

282292
if (fs.existsSync(reactDemosPath)) {
283293
reactDemoFiles = collectDemoFiles(reactDemosPath);
284294
}
285295
}
286296

287-
if (selection === 'native' || selection === 'both') {
297+
if (selection === 'native') {
288298
const nativeDocsPath = path.join(docsPath, 'native');
289299

290300
if (fs.existsSync(nativeDocsPath)) {
@@ -294,10 +304,20 @@ export async function docsAction(options: DocsOptions) {
294304
}
295305
}
296306

297-
const reactDocsLinkPath =
298-
selection === 'react' || selection === 'both' ? `./${DOCS_DIR_NAME}/react` : undefined;
299-
const nativeDocsLinkPath =
300-
selection === 'native' || selection === 'both' ? `./${DOCS_DIR_NAME}/native` : undefined;
307+
if (selection === 'migration') {
308+
const migrationDocsPath = path.join(docsPath, 'migration');
309+
310+
if (fs.existsSync(migrationDocsPath)) {
311+
const migrationDocFiles = collectMigrationDocFiles(migrationDocsPath);
312+
313+
migrationSections = buildDocTree(migrationDocFiles);
314+
}
315+
}
316+
317+
const reactDocsLinkPath = selection === 'react' ? `./${DOCS_DIR_NAME}/react` : undefined;
318+
const nativeDocsLinkPath = selection === 'native' ? `./${DOCS_DIR_NAME}/native` : undefined;
319+
const migrationDocsLinkPath =
320+
selection === 'migration' ? `./${DOCS_DIR_NAME}/migration` : undefined;
301321

302322
// Generate index content once (reused for all output files)
303323
const indexData: Parameters<typeof generateHerouiMdIndex>[0] = {
@@ -307,19 +327,19 @@ export async function docsAction(options: DocsOptions) {
307327

308328
if (nativeDocsLinkPath) indexData.nativeDocsPath = nativeDocsLinkPath;
309329
if (nativeSections) indexData.nativeSections = nativeSections;
330+
if (migrationDocsLinkPath) indexData.migrationDocsPath = migrationDocsLinkPath;
331+
if (migrationSections) indexData.migrationSections = migrationSections;
310332
if (reactDocsLinkPath) indexData.reactDocsPath = reactDocsLinkPath;
311333
if (reactSections) indexData.reactSections = reactSections;
312334
if (reactDemoFiles) indexData.reactDemoFiles = reactDemoFiles;
313335

314-
// Generate separate index content for React and Native
336+
// Generate index content for the selected library only
315337
const reactIndexContent =
316-
selection === 'react' || selection === 'both'
317-
? generateHerouiMdIndex(indexData, 'react')
318-
: undefined;
338+
selection === 'react' ? generateHerouiMdIndex(indexData, 'react') : undefined;
319339
const nativeIndexContent =
320-
selection === 'native' || selection === 'both'
321-
? generateHerouiMdIndex(indexData, 'native')
322-
: undefined;
340+
selection === 'native' ? generateHerouiMdIndex(indexData, 'native') : undefined;
341+
const migrationIndexContent =
342+
selection === 'migration' ? generateHerouiMdIndex(indexData, 'migration') : undefined;
323343

324344
// Write to all output files
325345
const gitignoreResult = ensureGitignoreEntry(cwd);
@@ -336,7 +356,12 @@ export async function docsAction(options: DocsOptions) {
336356
isNewFile = false;
337357
}
338358

339-
const newContent = injectIntoClaudeMd(existingContent, reactIndexContent, nativeIndexContent);
359+
const newContent = injectIntoClaudeMd(
360+
existingContent,
361+
reactIndexContent,
362+
nativeIndexContent,
363+
migrationIndexContent
364+
);
340365

341366
fs.writeFileSync(filePath, newContent, 'utf-8');
342367

@@ -359,7 +384,7 @@ export async function docsAction(options: DocsOptions) {
359384
Logger.log(chalk.cyan('📚 What was installed:'));
360385
Logger.log(` • Documentation files downloaded to ${chalk.bold(`.${DOCS_DIR_NAME}/`)}`);
361386
Logger.log(` • Index generated in ${chalk.bold(outputFiles.join(', '))}`);
362-
if (selection === 'react' || selection === 'both') {
387+
if (selection === 'react') {
363388
Logger.log(` • Demo files included for React code examples`);
364389
}
365390
Logger.newLine();
@@ -405,7 +430,7 @@ async function promptForLibrarySelection(neitherFound: boolean = false): Promise
405430
const selection = await getSelect('Select docs to include', [
406431
{title: 'HeroUI React v3', value: 'react'},
407432
{title: 'HeroUI Native', value: 'native'},
408-
{title: 'Both', value: 'both'}
433+
{title: 'HeroUI Migration (v2→v3)', value: 'migration'}
409434
]);
410435

411436
if (selection === undefined) {

src/analytics/analytics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {getErrorMessage} from './utils';
55
export type AgentsMdEvent = 'AGENTS_MD_SUCCESS' | 'AGENTS_MD_ERROR';
66

77
export interface AgentsMdProperties {
8-
selection?: 'react' | 'native' | 'both';
8+
selection?: 'react' | 'native' | 'migration';
99
outputFiles?: string[];
1010
outputFileCount?: number;
1111
duration?: number;

src/commands/agents-md.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function registerAgentsMdCommand(cmd: Command) {
1010
)
1111
.option('--react', 'Include only React docs')
1212
.option('--native', 'Include only Native docs')
13+
.option('--migration', 'Include HeroUI v2 to v3 migration docs')
1314
.option(
1415
'--output <files...>',
1516
'Target file path(s) (e.g., AGENTS.md, or AGENTS.md CLAUDE.md for multiple)'

0 commit comments

Comments
 (0)