Skip to content

Commit 868e485

Browse files
feat(ui~cli): add migrate command (#1543)
* feat(cli): add migrate command for code transformations and enhance help documentation - Introduced a new `migrate` command to perform code transformations using defined transformers. - Updated the help command to include the new `migrate` command and its description. - Added a new transformer for compound components to simplify component imports. * chore: add changeset * also cater for "flowbite-react/components/" imports * docs(cli): add documentation for the `flowbite-react migrate` command * fix coderabbit nitpicks: - add progress indicator
1 parent 0aa6f21 commit 868e485

File tree

6 files changed

+393
-25
lines changed

6 files changed

+393
-25
lines changed

.changeset/slimy-wombats-poke.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"flowbite-react": patch
3+
---
4+
5+
add new `migrate` CLI command
6+
7+
- add new transformer from compound components to singular imports
8+
9+
```tsx
10+
import { Button } from "flowbite-react";
11+
12+
// from
13+
<Button.Group>
14+
<Button>tab 1</Button>
15+
<Button>tab 2</Button>
16+
<Button>tab 3</Button>
17+
</Button.Group>
18+
19+
// to
20+
import { Button, ButtonGroup } from "flowbite-react";
21+
22+
<ButtonGroup>
23+
<Button>tab 1</Button>
24+
<Button>tab 2</Button>
25+
<Button>tab 3</Button>
26+
</ButtonGroup>
27+
```

apps/web/content/docs/getting-started/cli.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,27 @@ Installs Flowbite React in your project:
128128
- Installs the latest version of flowbite-react
129129
- Skips installation if already installed
130130

131+
### `flowbite-react migrate`
132+
133+
Runs code transformations to help migrate your codebase:
134+
135+
- Transforms compound components to their simple form (e.g., `Accordion.Panel` to `AccordionPanel`)
136+
- Automatically updates import statements and component usage
137+
138+
Example transformations:
139+
140+
```tsx
141+
// Before
142+
import { Accordion } from "flowbite-react";
143+
144+
<Accordion.Panel>...</Accordion.Panel>
145+
146+
// After
147+
import { AccordionPanel } from "flowbite-react";
148+
149+
<AccordionPanel>...</AccordionPanel>
150+
```
151+
131152
### `flowbite-react patch`
132153

133154
Patches Tailwind CSS to expose its version number for compatibility detection:

packages/ui/src/cli/commands/help.ts

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,36 @@
1-
export async function help() {
2-
const commands = [
3-
{ name: "build", description: "Generates the class list for your components" },
4-
{ name: "create", description: "Creates a new component with the proper structure and theming setup" },
5-
{ name: "dev", description: "Starts the development mode which watches for component changes" },
6-
{ name: "help", description: "Show available commands" },
7-
{ name: "init", description: "Initializes Flowbite React in your project" },
8-
{ name: "install", description: "Installs Flowbite React in your project" },
9-
{ name: "patch", description: "Patch Tailwind CSS for compatibility" },
10-
{ name: "register", description: "Registers the Flowbite React process for development" },
11-
{ name: "setup", description: "Setup specific features (plugin, tailwindcss, vscode)" },
12-
];
1+
export async function help(): Promise<void> {
2+
console.log(`
3+
Usage: flowbite-react [command] [options]
134
14-
console.log("\nFlowbite React CLI\n");
15-
console.log("Available commands:\n");
5+
Commands:
6+
build Generate class list for components and write to .flowbite-react/class-list.json
7+
create <name> Create a new component with proper structure and theming setup
8+
dev Start development server with real-time class generation
9+
help Show this help message and command information
10+
init Initialize Flowbite React with config files and necessary setup
11+
install Install Flowbite React using your detected package manager
12+
migrate Run code transformations to update to latest patterns and APIs
13+
patch Patch Tailwind CSS to expose version number for compatibility
14+
register Register Flowbite React process for development with automatic class generation
15+
setup <subcommand> Setup additional features and configurations
1616
17-
const longestName = Math.max(...commands.map((cmd) => cmd.name.length));
17+
Setup subcommands:
18+
plugin Setup bundler-specific Flowbite React plugin
19+
tailwindcss Configure Tailwind CSS with necessary plugins and settings
20+
vscode Setup VS Code with recommended extensions and settings
1821
19-
commands.forEach((command) => {
20-
const padding = " ".repeat(longestName - command.name.length + 2);
21-
console.log(` ${command.name}${padding}${command.description}`);
22-
});
22+
Options:
23+
--help Show this help message and command information
24+
`);
2325
}
2426

25-
export async function helpSetup() {
26-
console.log("\nAvailable setup subcommands:\n");
27-
console.log(" plugin Sets up the appropriate bundler plugin for your project");
28-
console.log(" tailwindcss Configures Tailwind CSS for use with Flowbite React");
29-
console.log(" vscode Sets up VSCode for optimal development");
27+
export async function helpSetup(): Promise<void> {
28+
console.log(`
29+
Usage: flowbite-react setup [subcommand]
30+
31+
Subcommands:
32+
plugin Setup bundler-specific Flowbite React plugin
33+
tailwindcss Configure Tailwind CSS with necessary plugins and settings
34+
vscode Setup VS Code with recommended extensions and settings
35+
`);
3036
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import * as fs from "fs/promises";
2+
import * as path from "path";
3+
import { compoundComponentsTransformer } from "../transformers/compound-components";
4+
import { findFiles } from "../utils/find-files";
5+
6+
export interface TransformResult {
7+
content: string;
8+
changed: boolean;
9+
}
10+
11+
export interface Transformer {
12+
name: string;
13+
transform: (content: string) => TransformResult;
14+
}
15+
16+
const transformers: Transformer[] = [
17+
compoundComponentsTransformer,
18+
// Add more transformers as needed
19+
];
20+
21+
export async function migrate(): Promise<void> {
22+
console.log("🔄 Running code transformations...");
23+
24+
try {
25+
const files = await findFiles({
26+
patterns: ["**/*.tsx", "**/*.jsx", "**/*.ts", "**/*.js"],
27+
excludeDirs: ["node_modules", "dist"],
28+
});
29+
30+
let totalUpdatedFiles = 0;
31+
let totalSkippedFiles = 0;
32+
33+
for (const transformer of transformers) {
34+
console.log(`\n📝 Running ${transformer.name} transformer...`);
35+
let updatedFiles = 0;
36+
let skippedFiles = 0;
37+
let processedFiles = 0;
38+
const totalFiles = files.length;
39+
40+
for (const file of files) {
41+
processedFiles++;
42+
if (processedFiles % 50 === 0 || processedFiles === totalFiles) {
43+
process.stdout.write(
44+
`\rProcessing: ${processedFiles}/${totalFiles} files (${Math.round((processedFiles / totalFiles) * 100)}%)`,
45+
);
46+
}
47+
try {
48+
const content = await fs.readFile(file, "utf-8");
49+
const result = transformer.transform(content);
50+
51+
if (result.changed) {
52+
await fs.writeFile(file, result.content, "utf-8");
53+
console.log(`✓ Updated ${path.relative(process.cwd(), file)}`);
54+
updatedFiles++;
55+
}
56+
} catch (_error) {
57+
skippedFiles++;
58+
console.error(`Could not process ${path.relative(process.cwd(), file)}`);
59+
}
60+
}
61+
62+
if (skippedFiles > 0) {
63+
console.log(`\nℹ️ Skipped ${skippedFiles} file${skippedFiles === 1 ? "" : "s"} due to parsing errors`);
64+
}
65+
66+
if (updatedFiles > 0) {
67+
console.log(
68+
`\n✨ Successfully transformed ${updatedFiles} file${updatedFiles === 1 ? "" : "s"} with ${transformer.name}`,
69+
);
70+
} else if (skippedFiles === 0) {
71+
console.log(`\n✨ No files needed transformation with ${transformer.name}`);
72+
}
73+
74+
totalUpdatedFiles += updatedFiles;
75+
totalSkippedFiles += skippedFiles;
76+
}
77+
78+
console.log("\n📊 Migration Summary:");
79+
console.log(`Total files updated: ${totalUpdatedFiles}`);
80+
console.log(`Total files skipped: ${totalSkippedFiles}`);
81+
} catch (error) {
82+
console.error("Error during migration:", error);
83+
process.exit(1);
84+
}
85+
}

packages/ui/src/cli/main.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export async function main(argv: string[]) {
2626
const { installFlowbiteReact } = await import("./commands/install");
2727
await installFlowbiteReact();
2828
}
29+
if (command === "migrate") {
30+
const { migrate } = await import("./commands/migrate");
31+
await migrate();
32+
}
2933
if (command === "patch") {
3034
const { patchTailwind } = await import("./commands/patch");
3135
await patchTailwind();
@@ -50,7 +54,19 @@ export async function main(argv: string[]) {
5054
}
5155

5256
if (
53-
!["build", "create", "dev", "help", "--help", "init", "install", "patch", "register", "setup"].includes(command)
57+
![
58+
"build",
59+
"create",
60+
"dev",
61+
"help",
62+
"--help",
63+
"init",
64+
"install",
65+
"migrate",
66+
"patch",
67+
"register",
68+
"setup",
69+
].includes(command)
5470
) {
5571
console.error(`Unknown command: ${command}`);
5672
const { help } = await import("./commands/help");

0 commit comments

Comments
 (0)