From af02fbe776fe5194a7fa93bcb28c73e9d775ea12 Mon Sep 17 00:00:00 2001 From: winches <329487092@qq.com> Date: Mon, 13 Jan 2025 20:03:42 +0800 Subject: [PATCH 1/9] fix: migrate nextui provider change to use regex (#133) --- .../src/helpers/actions/migrate/migrate-common.ts | 14 ++++++++++++++ .../actions/migrate/migrate-nextui-provider.ts | 14 ++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-common.ts b/packages/codemod/src/helpers/actions/migrate/migrate-common.ts index 9a69548..58d50b7 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-common.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-common.ts @@ -100,6 +100,20 @@ export function migrateJSXElementName( return dirtyFlag; } +export function migrateByRegex(rawContent: string, match: string, replace: string) { + const regex = new RegExp(match, 'g'); + const dirtyFlag = regex.test(rawContent); + + if (dirtyFlag) { + rawContent = rawContent.replace(regex, replace); + } + + return { + dirtyFlag, + rawContent + }; +} + /** * Migrate the name of the CallExpression * @example diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts b/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts index c99be60..0a39f23 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts @@ -1,7 +1,7 @@ import {HEROUI_PROVIDER, NEXTUI_PROVIDER} from '../../../constants/prefix'; import {getStore, writeFileAndUpdateStore} from '../../store'; -import {migrateImportName, migrateJSXElementName} from './migrate-common'; +import {migrateByRegex} from './migrate-common'; /** * Migrate the NextUIProvider to HeroUIProvider will directly write the file @@ -12,22 +12,20 @@ import {migrateImportName, migrateJSXElementName} from './migrate-common'; export function migrateNextuiProvider(paths: string[]) { for (const path of paths) { try { - const parsedContent = getStore(path, 'parsedContent'); + let rawContent = getStore(path, 'rawContent'); let dirtyFlag = false; - if (!parsedContent) { + if (!rawContent) { continue; } // Replace JSX element NextUIProvider with HeroUIProvider - dirtyFlag = migrateJSXElementName(parsedContent, NEXTUI_PROVIDER, HEROUI_PROVIDER); - // Replace NextUIProvider with HeroUIProvider in import statements - if (dirtyFlag) { - migrateImportName(parsedContent, NEXTUI_PROVIDER, HEROUI_PROVIDER); + ({dirtyFlag, rawContent} = migrateByRegex(rawContent, NEXTUI_PROVIDER, HEROUI_PROVIDER)); + if (dirtyFlag) { // Write the modified content back to the file - writeFileAndUpdateStore(path, 'parsedContent', parsedContent); + writeFileAndUpdateStore(path, 'rawContent', rawContent); } // eslint-disable-next-line no-empty } catch {} From a27371b55b365cd9591fa87324fee0b9e1f306bf Mon Sep 17 00:00:00 2001 From: winches <329487092@qq.com> Date: Tue, 14 Jan 2025 21:06:55 +0800 Subject: [PATCH 2/9] feat: add format effected files (#134) * feat: add format effected files * fix: typo * feat: change to use eslint * feat: add format to use prettier through option --- packages/codemod/README.md | 7 ++- .../codemod/src/actions/migrate-action.ts | 26 ++++++++- .../helpers/actions/lint-affected-files.ts | 10 ++++ .../actions/migrate/migrate-css-variables.ts | 3 +- .../helpers/actions/migrate/migrate-import.ts | 8 ++- .../helpers/actions/migrate/migrate-json.ts | 3 +- .../migrate/migrate-nextui-provider.ts | 3 +- .../actions/migrate/migrate-tailwindcss.ts | 3 +- packages/codemod/src/helpers/lint.ts | 58 +++++++++++++++++++ packages/codemod/src/helpers/options.ts | 17 ++++++ packages/codemod/src/helpers/store.ts | 6 ++ packages/codemod/src/index.ts | 5 ++ 12 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 packages/codemod/src/helpers/actions/lint-affected-files.ts create mode 100644 packages/codemod/src/helpers/lint.ts create mode 100644 packages/codemod/src/helpers/options.ts diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 73589da..cab565e 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -19,6 +19,8 @@ The CLI provides a comprehensive suite of tools to migrate your codebase from Ne ## Quick Start > **Note**: The heroui CLI requires [Node.js](https://nodejs.org/en) _18.17.x_ or later +> +> **Note**: If running in monorepo, you need to run the command in the root of your monorepo You can start using @heroui/codemod in one of the following ways: @@ -49,9 +51,10 @@ Options: -v, --version Output the current version -d, --debug Enable debug mode -h, --help Display help for command + -f, --format Format the affected files with Prettier Commands: - migrate [projectPath] Migrate your codebase to use heroui + migrate [projectPath] Migrate your codebase to use heroui ``` ## Codemod Arguments @@ -141,7 +144,7 @@ Example: Migrate your entire codebase from NextUI to heroui. You can choose which codemods to run during the migration process. ```bash -heroui-codemod migrate [projectPath] +heroui-codemod migrate [projectPath] [--format] ``` Example: diff --git a/packages/codemod/src/actions/migrate-action.ts b/packages/codemod/src/actions/migrate-action.ts index ae70fdd..496213d 100644 --- a/packages/codemod/src/actions/migrate-action.ts +++ b/packages/codemod/src/actions/migrate-action.ts @@ -6,6 +6,7 @@ import chalk from 'chalk'; import {confirmClack} from 'src/prompts/clack'; import {NEXTUI_PREFIX} from '../constants/prefix'; +import {lintAffectedFiles} from '../helpers/actions/lint-affected-files'; import {migrateCssVariables} from '../helpers/actions/migrate/migrate-css-variables'; import {migrateImportPackageWithPaths} from '../helpers/actions/migrate/migrate-import'; import {migrateJson} from '../helpers/actions/migrate/migrate-json'; @@ -13,7 +14,8 @@ import {migrateNextuiProvider} from '../helpers/actions/migrate/migrate-nextui-p import {migrateNpmrc} from '../helpers/actions/migrate/migrate-npmrc'; import {migrateTailwindcss} from '../helpers/actions/migrate/migrate-tailwindcss'; import {findFiles} from '../helpers/find-files'; -import {getStore, storeParsedContent, storePathsRawContent} from '../helpers/store'; +import {getOptionsValue} from '../helpers/options'; +import {affectedFiles, getStore, storeParsedContent, storePathsRawContent} from '../helpers/store'; import {transformPaths} from '../helpers/transform'; import {getCanRunCodemod} from '../helpers/utils'; @@ -144,5 +146,27 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig step++; } + const format = getOptionsValue('format'); + /** ======================== 7. Formatting affected files (Optional) ======================== */ + const runFormatAffectedFiles = affectedFiles.size > 0; + + // If user using format option, we don't need to use eslint + if (runFormatAffectedFiles && !format) { + p.log.step(`${step}. Formatting affected files (Optional)`); + const selectMigrateNpmrc = await confirmClack({ + message: `Do you want to format affected files? (${affectedFiles.size})` + }); + + if (selectMigrateNpmrc) { + await lintAffectedFiles(); + } + step++; + } + + // Directly linting affected files don't need to ask user + if (format) { + await lintAffectedFiles(); + } + p.outro(chalk.green('✅ Migration completed!')); } diff --git a/packages/codemod/src/helpers/actions/lint-affected-files.ts b/packages/codemod/src/helpers/actions/lint-affected-files.ts new file mode 100644 index 0000000..e331d78 --- /dev/null +++ b/packages/codemod/src/helpers/actions/lint-affected-files.ts @@ -0,0 +1,10 @@ +import {tryLintFile} from '../lint'; +import {affectedFiles} from '../store'; + +export async function lintAffectedFiles() { + try { + await tryLintFile(Array.from(affectedFiles)); + } catch (error) { + return; + } +} diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-css-variables.ts b/packages/codemod/src/helpers/actions/migrate/migrate-css-variables.ts index 3446e87..1bd92a8 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-css-variables.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-css-variables.ts @@ -1,5 +1,5 @@ import {HEROUI_CSS_VARIABLES_PREFIX, NEXTUI_CSS_VARIABLES_PREFIX} from '../../../constants/prefix'; -import {getStore, writeFileAndUpdateStore} from '../../store'; +import {getStore, updateAffectedFiles, writeFileAndUpdateStore} from '../../store'; export function migrateCssVariables(files: string[]) { for (const file of files) { @@ -13,6 +13,7 @@ export function migrateCssVariables(files: string[]) { ); writeFileAndUpdateStore(file, 'rawContent', content); + updateAffectedFiles(file); } } } diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-import.ts b/packages/codemod/src/helpers/actions/migrate/migrate-import.ts index 1a30010..bfedcbb 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-import.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-import.ts @@ -1,7 +1,12 @@ import jscodeshift from 'jscodeshift'; import {HEROUI_PREFIX, NEXTUI_PREFIX} from '../../../constants/prefix'; -import {type StoreObject, getStore, writeFileAndUpdateStore} from '../../store'; +import { + type StoreObject, + getStore, + updateAffectedFiles, + writeFileAndUpdateStore +} from '../../store'; /** * Migrate the import package will directly write the file @@ -24,6 +29,7 @@ export function migrateImportPackageWithPaths(paths: string[]) { if (dirtyFlag) { // Write the modified content back to the file writeFileAndUpdateStore(path, 'parsedContent', parsedContent); + updateAffectedFiles(path); } // eslint-disable-next-line no-empty } catch {} diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-json.ts b/packages/codemod/src/helpers/actions/migrate/migrate-json.ts index 9eb9469..8a230c4 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-json.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-json.ts @@ -5,7 +5,7 @@ import {Logger} from '@helpers/logger'; import {HEROUI_PREFIX, NEXTUI_PREFIX} from '../../../constants/prefix'; import {fetchPackageLatestVersion} from '../../https'; import {safeParseJson} from '../../parse'; -import {getStore, writeFileAndUpdateStore} from '../../store'; +import {getStore, updateAffectedFiles, writeFileAndUpdateStore} from '../../store'; const DEFAULT_INDENT = 2; @@ -51,6 +51,7 @@ export async function migrateJson(files: string[]) { const indent = detectIndent(content); writeFileAndUpdateStore(file, 'rawContent', JSON.stringify(json, null, indent)); + updateAffectedFiles(file); } }) ); diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts b/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts index 0a39f23..862a703 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-nextui-provider.ts @@ -1,5 +1,5 @@ import {HEROUI_PROVIDER, NEXTUI_PROVIDER} from '../../../constants/prefix'; -import {getStore, writeFileAndUpdateStore} from '../../store'; +import {getStore, updateAffectedFiles, writeFileAndUpdateStore} from '../../store'; import {migrateByRegex} from './migrate-common'; @@ -26,6 +26,7 @@ export function migrateNextuiProvider(paths: string[]) { if (dirtyFlag) { // Write the modified content back to the file writeFileAndUpdateStore(path, 'rawContent', rawContent); + updateAffectedFiles(path); } // eslint-disable-next-line no-empty } catch {} diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-tailwindcss.ts b/packages/codemod/src/helpers/actions/migrate/migrate-tailwindcss.ts index 76c596e..ca6a0b8 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-tailwindcss.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-tailwindcss.ts @@ -8,7 +8,7 @@ import { NEXTUI_PLUGIN, NEXTUI_PREFIX } from '../../../constants/prefix'; -import {getStore, writeFileAndUpdateStore} from '../../store'; +import {getStore, updateAffectedFiles, writeFileAndUpdateStore} from '../../store'; import {migrateCallExpressionName, migrateImportName} from './migrate-common'; import {migrateImportPackage} from './migrate-import'; @@ -56,6 +56,7 @@ export function migrateTailwindcss(paths: string[]) { if (dirtyFlag) { writeFileAndUpdateStore(path, 'parsedContent', parsedContent); + updateAffectedFiles(path); } } } diff --git a/packages/codemod/src/helpers/lint.ts b/packages/codemod/src/helpers/lint.ts new file mode 100644 index 0000000..a015d58 --- /dev/null +++ b/packages/codemod/src/helpers/lint.ts @@ -0,0 +1,58 @@ +import {getOptionsValue} from './options'; +import {getStore, writeFileAndUpdateStore} from './store'; + +async function tryImportPackage(packageName: string) { + try { + return await import(packageName); + } catch { + return null; + } +} + +export async function lintWithESLint(filePaths: string[]) { + const eslintPkg = await tryImportPackage('eslint'); + + if (eslintPkg) { + const ESLint = eslintPkg.ESLint; + const eslint = new ESLint({ + fix: true + }); + const result = await eslint.lintFiles(filePaths); + + await ESLint.outputFixes(result); + } +} + +export async function lintWithPrettier(filePaths: string[]) { + const prettier = await tryImportPackage('prettier'); + const options = await prettier.resolveConfig(process.cwd()); + + if (prettier) { + await Promise.all( + filePaths.map(async (filePath) => { + const rawContent = getStore(filePath, 'rawContent'); + const formattedContent = await prettier.format(rawContent, { + options, + parser: 'typescript' + }); + + writeFileAndUpdateStore(filePath, 'rawContent', formattedContent); + }) + ); + } +} + +/** + * Try linting a file with ESLint or Prettier + */ +export async function tryLintFile(filePaths: string[]) { + try { + if (getOptionsValue('format')) { + await lintWithPrettier(filePaths); + } else { + await lintWithESLint(filePaths); + } + } catch { + return; + } +} diff --git a/packages/codemod/src/helpers/options.ts b/packages/codemod/src/helpers/options.ts new file mode 100644 index 0000000..01ce731 --- /dev/null +++ b/packages/codemod/src/helpers/options.ts @@ -0,0 +1,17 @@ +export const CODEMOD_OPTIONS = { + format: false +}; + +export function initOptions(options: {format: boolean}) { + const {format} = options; + + setOptionsValue('format', format); +} + +export function setOptionsValue(key: keyof typeof CODEMOD_OPTIONS, value: boolean) { + CODEMOD_OPTIONS[key] = value; +} + +export function getOptionsValue(key: T) { + return CODEMOD_OPTIONS[key]; +} diff --git a/packages/codemod/src/helpers/store.ts b/packages/codemod/src/helpers/store.ts index e1685c2..9d9e50f 100644 --- a/packages/codemod/src/helpers/store.ts +++ b/packages/codemod/src/helpers/store.ts @@ -100,3 +100,9 @@ export function writeFileAndUpdateStore( updateStore(path, key, value); key === 'parsedContent' && updateStore(path, 'rawContent', data.rawContent); } + +export const affectedFiles = new Set(); + +export function updateAffectedFiles(path: string) { + affectedFiles.add(path); +} diff --git a/packages/codemod/src/index.ts b/packages/codemod/src/index.ts index bd925a0..90d8402 100644 --- a/packages/codemod/src/index.ts +++ b/packages/codemod/src/index.ts @@ -9,6 +9,7 @@ import pkg from '../package.json'; import {codemodAction} from './actions/codemod-action'; import {migrateAction} from './actions/migrate-action'; import {DEBUG} from './helpers/debug'; +import {initOptions} from './helpers/options'; import {codemods} from './types'; const nextui = new Command(); @@ -22,6 +23,7 @@ nextui .argument('[codemod]', `Specify which codemod to run\nCodemods: ${codemods.join(', ')}`) .allowUnknownOption() .option('-d, --debug', 'Enable debug mode') + .option('-f, --format', 'Format the affected files with Prettier') .action(codemodAction); nextui @@ -33,6 +35,9 @@ nextui nextui.hook('preAction', async (command) => { const options = (command as SAFE_ANY).rawArgs.slice(2); const debug = options.includes('--debug') || options.includes('-d'); + const format = options.includes('--format') || options.includes('-f'); + + initOptions({format}); DEBUG.enabled = debug; }); From ae9108bc32b678c47e5eb64675fb0dfd174e58f1 Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Tue, 14 Jan 2025 10:08:30 -0300 Subject: [PATCH 3/9] chore: readme upadted --- packages/codemod/README.md | 14 +++----------- packages/codemod/package.json | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/codemod/README.md b/packages/codemod/README.md index 73589da..1fda87c 100644 --- a/packages/codemod/README.md +++ b/packages/codemod/README.md @@ -1,20 +1,12 @@

- - nextui + + nextui

@heroui/codemod


-

- - License - - - npm downloads - -

-The CLI provides a comprehensive suite of tools to migrate your codebase from NextUI to heroui. +The CLI provides a comprehensive suite of tools to migrate your codebase from NextUI to HeroUI. ## Quick Start diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 5a50538..70acfbb 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -3,9 +3,9 @@ "private": false, "type": "module", "license": "MIT", - "version": "0.0.2", + "version": "1.0.0", "homepage": "https://github.com/frontio-ai/heroui-cli#readme", - "description": "A CLI tool that modifies your codebase to use the heroui", + "description": "HeroUI Codemod provides transformations to help migrate your codebase from NextUI to HeroUI", "keywords": [ "UI", "CLI", From d29c05a71ab781dfa88e9b64014f993516d50f28 Mon Sep 17 00:00:00 2001 From: winches <329487092@qq.com> Date: Wed, 15 Jan 2025 05:41:00 +0800 Subject: [PATCH 4/9] feat: add remaining files replace prompt and dot files detected and end note (#135) * fix: find dot files * fix: choose to replae remaining files * fix: dot files * fix: update stop and log * feat: add note * fix: update remaining files logic * Update packages/codemod/src/actions/migrate-action.ts --------- Co-authored-by: Junior Garcia --- .../codemod/src/actions/migrate-action.ts | 46 ++++++++++++++----- packages/codemod/src/constants/prefix.ts | 2 + .../actions/migrate/migrate-left-files.ts | 18 ++++++++ packages/codemod/src/helpers/utils.ts | 8 ++++ 4 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts diff --git a/packages/codemod/src/actions/migrate-action.ts b/packages/codemod/src/actions/migrate-action.ts index 496213d..7331c8f 100644 --- a/packages/codemod/src/actions/migrate-action.ts +++ b/packages/codemod/src/actions/migrate-action.ts @@ -5,19 +5,20 @@ import {Logger} from '@helpers/logger'; import chalk from 'chalk'; import {confirmClack} from 'src/prompts/clack'; -import {NEXTUI_PREFIX} from '../constants/prefix'; +import {EXTRA_FILES} from '../constants/prefix'; import {lintAffectedFiles} from '../helpers/actions/lint-affected-files'; import {migrateCssVariables} from '../helpers/actions/migrate/migrate-css-variables'; import {migrateImportPackageWithPaths} from '../helpers/actions/migrate/migrate-import'; import {migrateJson} from '../helpers/actions/migrate/migrate-json'; +import {migrateLeftFiles} from '../helpers/actions/migrate/migrate-left-files'; import {migrateNextuiProvider} from '../helpers/actions/migrate/migrate-nextui-provider'; import {migrateNpmrc} from '../helpers/actions/migrate/migrate-npmrc'; import {migrateTailwindcss} from '../helpers/actions/migrate/migrate-tailwindcss'; import {findFiles} from '../helpers/find-files'; import {getOptionsValue} from '../helpers/options'; -import {affectedFiles, getStore, storeParsedContent, storePathsRawContent} from '../helpers/store'; +import {affectedFiles, storeParsedContent, storePathsRawContent} from '../helpers/store'; import {transformPaths} from '../helpers/transform'; -import {getCanRunCodemod} from '../helpers/utils'; +import {filterNextuiFiles, getCanRunCodemod} from '../helpers/utils'; process.on('SIGINT', () => { Logger.newLine(); @@ -32,7 +33,10 @@ interface MigrateActionOptions { export async function migrateAction(projectPaths?: string[], options = {} as MigrateActionOptions) { const {codemod} = options; const transformedPaths = transformPaths(projectPaths); - const files = await findFiles(transformedPaths, {ext: '{js,jsx,ts,tsx,json}'}); + const baseFiles = await findFiles(transformedPaths, {ext: '{js,jsx,ts,tsx,json,mjs,cjs}'}); + const dotFiles = await findFiles(transformedPaths, {dot: true}); + const extraFiles = dotFiles.filter((file) => EXTRA_FILES.some((extra) => file.includes(extra))); + const files = [...baseFiles, ...extraFiles]; // Store the raw content of the files storePathsRawContent(files); @@ -40,9 +44,7 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig // All package.json const packagesJson = files.filter((file) => file.includes('package.json')); // All included nextui - const nextuiFiles = files.filter((file) => - new RegExp(NEXTUI_PREFIX, 'g').test(getStore(file, 'rawContent')) - ); + const nextuiFiles = filterNextuiFiles(files); let step = 1; p.intro(chalk.inverse(' Starting to migrate nextui to heroui ')); @@ -131,9 +133,7 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig const runMigrateNpmrc = getCanRunCodemod(codemod, 'npmrc'); if (runMigrateNpmrc) { - const npmrcFiles = (await findFiles(transformedPaths, {dot: true})).filter((path) => - path.includes('.npmrc') - ); + const npmrcFiles = dotFiles.filter((path) => path.includes('.npmrc')); p.log.step(`${step}. Migrating "npmrc" (Pnpm only)`); const selectMigrateNpmrc = await confirmClack({ @@ -146,8 +146,30 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig step++; } + /** ======================== 7. Whether need to change left files with @nextui-org ======================== */ + const remainingNextuiFiles = filterNextuiFiles([...affectedFiles]); + const remainingFiles = [ + ...nextuiFiles.filter((file) => !affectedFiles.has(file)), + ...remainingNextuiFiles + ]; + const runCheckLeftFiles = remainingFiles.length > 0; + + // If user not using individual codemod, we need to ask user to replace left files + if (runCheckLeftFiles && !codemod) { + p.log.step(`${step}. Remaining files with "@nextui-org" (${remainingFiles.length})`); + p.log.info(remainingFiles.join('\n')); + const selectMigrateLeftFiles = await confirmClack({ + message: 'Do you want to replace all remaining instances of ‘@nextui-org’ with ‘@heroui’?' + }); + + if (selectMigrateLeftFiles) { + migrateLeftFiles(remainingFiles); + } + step++; + } + const format = getOptionsValue('format'); - /** ======================== 7. Formatting affected files (Optional) ======================== */ + /** ======================== 8. Formatting affected files (Optional) ======================== */ const runFormatAffectedFiles = affectedFiles.size > 0; // If user using format option, we don't need to use eslint @@ -168,5 +190,7 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig await lintAffectedFiles(); } + p.note(`Reinstall the dependencies e.g. "pnpm install"`, 'Next steps'); + p.outro(chalk.green('✅ Migration completed!')); } diff --git a/packages/codemod/src/constants/prefix.ts b/packages/codemod/src/constants/prefix.ts index d2ce36c..46475e0 100644 --- a/packages/codemod/src/constants/prefix.ts +++ b/packages/codemod/src/constants/prefix.ts @@ -9,3 +9,5 @@ export const HEROUI_PLUGIN = 'heroui'; export const NEXTUI_CSS_VARIABLES_PREFIX = '--nextui-'; export const HEROUI_CSS_VARIABLES_PREFIX = '--heroui-'; + +export const EXTRA_FILES = ['.storybook']; diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts b/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts new file mode 100644 index 0000000..bc0c9ad --- /dev/null +++ b/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts @@ -0,0 +1,18 @@ +import { + HEROUI_PLUGIN, + HEROUI_PREFIX, + NEXTUI_PLUGIN, + NEXTUI_PREFIX +} from '../../../constants/prefix'; +import {getStore, writeFileAndUpdateStore} from '../../store'; + +export function migrateLeftFiles(files: string[]) { + for (const file of files) { + const rawContent = getStore(file, 'rawContent'); + const replaceContent = rawContent + .replaceAll(NEXTUI_PREFIX, HEROUI_PREFIX) + .replaceAll(NEXTUI_PLUGIN, HEROUI_PLUGIN); + + writeFileAndUpdateStore(file, 'rawContent', replaceContent); + } +} diff --git a/packages/codemod/src/helpers/utils.ts b/packages/codemod/src/helpers/utils.ts index 4d400cd..104666c 100644 --- a/packages/codemod/src/helpers/utils.ts +++ b/packages/codemod/src/helpers/utils.ts @@ -1,5 +1,13 @@ import type {Codemods} from '../types'; +import {NEXTUI_PREFIX} from '../constants/prefix'; + +import {getStore} from './store'; + export function getCanRunCodemod(codemod: Codemods, targetName: Codemods) { return codemod === undefined || codemod === targetName; } + +export function filterNextuiFiles(files: string[]) { + return files.filter((file) => new RegExp(NEXTUI_PREFIX, 'g').test(getStore(file, 'rawContent'))); +} From 271f38b9f86d5725fda988e193c2c1f3df388d3b Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Tue, 14 Jan 2025 18:42:23 -0300 Subject: [PATCH 5/9] chore: new version published --- packages/codemod/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 70acfbb..2760372 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -3,7 +3,7 @@ "private": false, "type": "module", "license": "MIT", - "version": "1.0.0", + "version": "1.0.2", "homepage": "https://github.com/frontio-ai/heroui-cli#readme", "description": "HeroUI Codemod provides transformations to help migrate your codebase from NextUI to HeroUI", "keywords": [ From f36839ed559b28b502b10639009f60c0622b7e4c Mon Sep 17 00:00:00 2001 From: winches <329487092@qq.com> Date: Thu, 16 Jan 2025 20:57:11 +0800 Subject: [PATCH 6/9] feat: add reinstall prompt and retry fetch version (#137) * feat: add reinstall prompt * fix: add retry fetch * feat: add update files --- .../codemod/src/actions/migrate-action.ts | 30 ++++++++++++++- .../helpers/actions/migrate/migrate-json.ts | 17 +++++++-- .../actions/migrate/migrate-left-files.ts | 3 +- packages/codemod/src/helpers/https.ts | 37 +++++++++---------- packages/codemod/src/helpers/utils.ts | 11 ++++++ 5 files changed, 72 insertions(+), 26 deletions(-) diff --git a/packages/codemod/src/actions/migrate-action.ts b/packages/codemod/src/actions/migrate-action.ts index 7331c8f..41038b1 100644 --- a/packages/codemod/src/actions/migrate-action.ts +++ b/packages/codemod/src/actions/migrate-action.ts @@ -1,6 +1,7 @@ import type {Codemods} from '../types'; import * as p from '@clack/prompts'; +import {exec} from '@helpers/exec'; import {Logger} from '@helpers/logger'; import chalk from 'chalk'; import {confirmClack} from 'src/prompts/clack'; @@ -18,7 +19,7 @@ import {findFiles} from '../helpers/find-files'; import {getOptionsValue} from '../helpers/options'; import {affectedFiles, storeParsedContent, storePathsRawContent} from '../helpers/store'; import {transformPaths} from '../helpers/transform'; -import {filterNextuiFiles, getCanRunCodemod} from '../helpers/utils'; +import {filterNextuiFiles, getCanRunCodemod, getInstallCommand} from '../helpers/utils'; process.on('SIGINT', () => { Logger.newLine(); @@ -190,7 +191,32 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig await lintAffectedFiles(); } - p.note(`Reinstall the dependencies e.g. "pnpm install"`, 'Next steps'); + /** ======================== 9. Reinstall the dependencies ======================== */ + // if package.json is affected, we need to ask user to reinstall the dependencies + const runReinstallDependencies = [...affectedFiles.keys()].some((file) => + file.includes('package.json') + ); + + if (runReinstallDependencies) { + p.log.step(`${step}. Reinstalling the dependencies`); + const selectReinstallDependencies = await confirmClack({ + message: 'Do you want to reinstall the dependencies?' + }); + + if (selectReinstallDependencies) { + const {cmd} = await getInstallCommand(); + + try { + await exec(cmd); + } catch { + p.log.error(`Reinstall dependencies error, you need to run it manually e.g. "${cmd}"`); + } + } else { + // If user not want to reinstall the dependencies automatically, we need to tell user to run it manually + p.note(`Reinstall the dependencies e.g. "pnpm install"`, 'Next steps'); + } + step++; + } p.outro(chalk.green('✅ Migration completed!')); } diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-json.ts b/packages/codemod/src/helpers/actions/migrate/migrate-json.ts index 8a230c4..6b2e616 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-json.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-json.ts @@ -8,6 +8,7 @@ import {safeParseJson} from '../../parse'; import {getStore, updateAffectedFiles, writeFileAndUpdateStore} from '../../store'; const DEFAULT_INDENT = 2; +const LATEST_VERSION = 'latest'; export function detectIndent(content: string): number { const match = content.match(/^(\s+)/m); @@ -33,14 +34,22 @@ export async function migrateJson(files: string[]) { try { await Promise.all([ ...filterHeroUiPkgs(Object.keys(json.dependencies)).map(async (key) => { - const version = await fetchPackageLatestVersion(key); + try { + const version = await fetchPackageLatestVersion(key); - json.dependencies[key] = version; + json.dependencies[key] = version; + } catch (error) { + json.dependencies[key] = LATEST_VERSION; + } }), ...filterHeroUiPkgs(Object.keys(json.devDependencies)).map(async (key) => { - const version = await fetchPackageLatestVersion(key); + try { + const version = await fetchPackageLatestVersion(key); - json.devDependencies[key] = version; + json.devDependencies[key] = version; + } catch (error) { + json.devDependencies[key] = LATEST_VERSION; + } }) ]); } catch (error) { diff --git a/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts b/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts index bc0c9ad..a00f89c 100644 --- a/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts +++ b/packages/codemod/src/helpers/actions/migrate/migrate-left-files.ts @@ -4,7 +4,7 @@ import { NEXTUI_PLUGIN, NEXTUI_PREFIX } from '../../../constants/prefix'; -import {getStore, writeFileAndUpdateStore} from '../../store'; +import {getStore, updateAffectedFiles, writeFileAndUpdateStore} from '../../store'; export function migrateLeftFiles(files: string[]) { for (const file of files) { @@ -14,5 +14,6 @@ export function migrateLeftFiles(files: string[]) { .replaceAll(NEXTUI_PLUGIN, HEROUI_PLUGIN); writeFileAndUpdateStore(file, 'rawContent', replaceContent); + updateAffectedFiles(file); } } diff --git a/packages/codemod/src/helpers/https.ts b/packages/codemod/src/helpers/https.ts index f77bbbd..6947b00 100644 --- a/packages/codemod/src/helpers/https.ts +++ b/packages/codemod/src/helpers/https.ts @@ -1,3 +1,4 @@ +import retry from 'async-retry'; import chalk from 'chalk'; import ora from 'ora'; @@ -25,30 +26,28 @@ export async function fetchPackageLatestVersion(packageName: string): Promise controller.abort(), 15000); // 15 seconds timeout + return await retry( + async () => { + const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, { + headers: { + Accept: 'application/json' + } + }); - const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, { - headers: { - Accept: 'application/json' - }, - signal: controller.signal - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - throw new Error(`Request failed with status ${response.status}`); - } + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } - const data = await response.json(); + const data = await response.json(); - return (data as {version: string}).version; + return (data as {version: string}).version; + }, + { + retries: 2 + } + ); } catch (error) { if (error instanceof Error) { - if (error.name === 'AbortError') { - throw new Error('Request timeout'); - } if (error.message.includes('fetch failed')) { throw new Error('Connection failed. Please check your network connection.'); } diff --git a/packages/codemod/src/helpers/utils.ts b/packages/codemod/src/helpers/utils.ts index 104666c..8e93cb6 100644 --- a/packages/codemod/src/helpers/utils.ts +++ b/packages/codemod/src/helpers/utils.ts @@ -1,5 +1,7 @@ import type {Codemods} from '../types'; +import {detect} from '@helpers/detect'; + import {NEXTUI_PREFIX} from '../constants/prefix'; import {getStore} from './store'; @@ -11,3 +13,12 @@ export function getCanRunCodemod(codemod: Codemods, targetName: Codemods) { export function filterNextuiFiles(files: string[]) { return files.filter((file) => new RegExp(NEXTUI_PREFIX, 'g').test(getStore(file, 'rawContent'))); } + +export async function getInstallCommand() { + const packageManager = await detect(); + + return { + cmd: `${packageManager} install`, + packageManager + }; +} From b42d9f141fd8903527cfb2c5c648b059ec15a5f0 Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Thu, 16 Jan 2025 09:58:30 -0300 Subject: [PATCH 7/9] chore: new version --- packages/codemod/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 2760372..6c394fb 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -3,7 +3,7 @@ "private": false, "type": "module", "license": "MIT", - "version": "1.0.2", + "version": "1.0.3", "homepage": "https://github.com/frontio-ai/heroui-cli#readme", "description": "HeroUI Codemod provides transformations to help migrate your codebase from NextUI to HeroUI", "keywords": [ From 1de98147191ac6dbc33cb53a6f22258abeb974f3 Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Thu, 16 Jan 2025 10:13:46 -0300 Subject: [PATCH 8/9] v1.1.0 --- packages/codemod/package.json | 4 +++- packages/codemod/src/actions/codemod-action.ts | 4 ++-- packages/codemod/src/actions/migrate-action.ts | 12 ++++++------ pnpm-lock.yaml | 6 ++++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 6c394fb..67a2831 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -3,7 +3,7 @@ "private": false, "type": "module", "license": "MIT", - "version": "1.0.3", + "version": "1.1.0", "homepage": "https://github.com/frontio-ai/heroui-cli#readme", "description": "HeroUI Codemod provides transformations to help migrate your codebase from NextUI to HeroUI", "keywords": [ @@ -58,8 +58,10 @@ "@clack/prompts": "0.7.0", "async-retry": "1.3.3", "chalk": "5.3.0", + "@winches/prompts": "0.0.6", "cli-progress": "3.12.0", "commander": "11.0.0", + "find-up": "7.0.0", "compare-versions": "6.1.1", "fast-glob": "3.3.2", "gradient-string": "2.0.2", diff --git a/packages/codemod/src/actions/codemod-action.ts b/packages/codemod/src/actions/codemod-action.ts index 825bce7..86989cc 100644 --- a/packages/codemod/src/actions/codemod-action.ts +++ b/packages/codemod/src/actions/codemod-action.ts @@ -6,8 +6,8 @@ import {migrateAction} from './migrate-action'; function printUsage() { Logger.grey('Usage: '); - Logger.log(`nextui [codemod]`); - Logger.log(`nextui migrate [projectPath]`); + Logger.log(`heroui [codemod]`); + Logger.log(`heroui migrate [projectPath]`); Logger.newLine(); Logger.grey('Codemods:'); Logger.log(`- ${codemods.join('\n- ')}`); diff --git a/packages/codemod/src/actions/migrate-action.ts b/packages/codemod/src/actions/migrate-action.ts index 41038b1..7b75d7b 100644 --- a/packages/codemod/src/actions/migrate-action.ts +++ b/packages/codemod/src/actions/migrate-action.ts @@ -48,7 +48,7 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig const nextuiFiles = filterNextuiFiles(files); let step = 1; - p.intro(chalk.inverse(' Starting to migrate nextui to heroui ')); + p.intro(chalk.inverse('Starting to migrate NextUI to HeroUI')); /** ======================== 1. Migrate package.json ======================== */ const runMigratePackageJson = getCanRunCodemod(codemod, 'package-json-package-name'); @@ -69,7 +69,7 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig const runMigrateImportNextui = getCanRunCodemod(codemod, 'import-heroui'); if (runMigrateImportNextui) { - p.log.step(`${step}. Migrating import "nextui" to "heorui"`); + p.log.step(`${step}. Migrating import "nextui" to "heroui"`); const selectMigrateNextui = await confirmClack({ message: 'Do you want to migrate import nextui to heroui?' }); @@ -160,7 +160,7 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig p.log.step(`${step}. Remaining files with "@nextui-org" (${remainingFiles.length})`); p.log.info(remainingFiles.join('\n')); const selectMigrateLeftFiles = await confirmClack({ - message: 'Do you want to replace all remaining instances of ‘@nextui-org’ with ‘@heroui’?' + message: 'Do you want to replace all remaining instances of "@nextui-org" with "@heroui"?' }); if (selectMigrateLeftFiles) { @@ -209,11 +209,11 @@ export async function migrateAction(projectPaths?: string[], options = {} as Mig try { await exec(cmd); } catch { - p.log.error(`Reinstall dependencies error, you need to run it manually e.g. "${cmd}"`); + p.log.error(`Failed to reinstall dependencies. Please run "${cmd}" manually.`); } } else { - // If user not want to reinstall the dependencies automatically, we need to tell user to run it manually - p.note(`Reinstall the dependencies e.g. "pnpm install"`, 'Next steps'); + // If user doesn't want to reinstall the dependencies automatically, tell them to run it manually + p.note(`Please reinstall the dependencies (e.g., "pnpm install")`, 'Next steps'); } step++; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36fffc5..04d07f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,9 @@ importers: '@clack/prompts': specifier: 0.7.0 version: 0.7.0 + '@winches/prompts': + specifier: 0.0.6 + version: 0.0.6 async-retry: specifier: 1.3.3 version: 1.3.3 @@ -144,6 +147,9 @@ importers: fast-glob: specifier: 3.3.2 version: 3.3.2 + find-up: + specifier: 7.0.0 + version: 7.0.0 gradient-string: specifier: 2.0.2 version: 2.0.2 From 33fa8e6e0740deffc786e7de02a5f7873736114b Mon Sep 17 00:00:00 2001 From: winches <329487092@qq.com> Date: Thu, 16 Jan 2025 21:14:58 +0800 Subject: [PATCH 9/9] fix: codemod release (#136) --- packages/codemod/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 67a2831..3fb8e15 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -50,8 +50,8 @@ "lint:fix": "eslint . --max-warnings=0 --fix", "check:prettier": "prettier --check .", "check:types": "tsc --noEmit", - "changelog": "npx conventional-changelog -p angular -i CHANGELOG.md -s --commit-path packages/codemod", - "release": "bumpp --execute='pnpm run changelog' --all --tag='@heroui/codemodv%s'", + "changelog": "npx conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .", + "release": "bumpp --execute='pnpm run changelog' --all --tag '@heroui/codemodv%s'", "prepublishOnly": "pnpm run build" }, "dependencies": {