mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
- Introduced `menu.json` for menu component structure with bindings for trigger and content. - Created `password-input.json` for password input handling visibility and value changes. - Added `popover.json` for popover component with trigger and content bindings. feat: Implement custom hooks for UI interactions - Added `useAccordion` for managing accordion state with single/multiple item support. - Created `useBindingEditor` for managing bindings in a dynamic editor. - Implemented `useCopyState` for clipboard copy functionality with feedback. - Developed `useFileUpload` for handling file uploads with drag-and-drop support. - Introduced `useFocusState` for managing focus state in components. - Created `useImageState` for handling image loading and error states. - Added `useMenuState` for managing menu interactions and item clicks. - Implemented `usePasswordVisibility` for toggling password visibility. - Developed `usePopoverState` for managing popover visibility and interactions. feat: Add constants and interfaces for JSON UI components - Introduced constants for sizes, placements, styles, and object-fit handling. - Created interfaces for various components including Accordion, Binding Editor, Copy Button, Data Source Editor, File Upload, and more. - Added type definitions for menu items, popover props, and other UI elements to enhance type safety and maintainability.
191 lines
6.1 KiB
TypeScript
191 lines
6.1 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* Analyze duplicate TSX files before deletion
|
|
* Check JSON contents to ensure they're complete
|
|
*/
|
|
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { globSync } from 'fs'
|
|
|
|
const ROOT_DIR = path.resolve(process.cwd())
|
|
const CONFIG_PAGES_DIR = path.join(ROOT_DIR, 'src/config/pages')
|
|
const COMPONENTS_DIR = path.join(ROOT_DIR, 'src/components')
|
|
const JSON_DEFS_DIR = path.join(ROOT_DIR, 'src/components/json-definitions')
|
|
|
|
function toKebabCase(str: string): string {
|
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')
|
|
}
|
|
|
|
interface AnalysisResult {
|
|
tsx: string
|
|
json: string
|
|
tsxSize: number
|
|
jsonSize: number
|
|
tsxHasHooks: boolean
|
|
tsxHasState: boolean
|
|
tsxHasEffects: boolean
|
|
jsonHasBindings: boolean
|
|
jsonHasChildren: boolean
|
|
recommendation: 'safe-to-delete' | 'needs-review' | 'keep-tsx'
|
|
reason: string
|
|
}
|
|
|
|
async function analyzeTsxFile(filePath: string): Promise<{
|
|
hasHooks: boolean
|
|
hasState: boolean
|
|
hasEffects: boolean
|
|
}> {
|
|
const content = fs.readFileSync(filePath, 'utf-8')
|
|
|
|
return {
|
|
hasHooks: /use[A-Z]/.test(content),
|
|
hasState: /useState|useReducer/.test(content),
|
|
hasEffects: /useEffect|useLayoutEffect/.test(content)
|
|
}
|
|
}
|
|
|
|
async function analyzeJsonFile(filePath: string): Promise<{
|
|
hasBindings: boolean
|
|
hasChildren: boolean
|
|
size: number
|
|
}> {
|
|
const content = fs.readFileSync(filePath, 'utf-8')
|
|
const json = JSON.parse(content)
|
|
|
|
return {
|
|
hasBindings: !!json.bindings || hasNestedBindings(json),
|
|
hasChildren: !!json.children,
|
|
size: content.length
|
|
}
|
|
}
|
|
|
|
function hasNestedBindings(obj: any): boolean {
|
|
if (!obj || typeof obj !== 'object') return false
|
|
if (obj.bindings) return true
|
|
|
|
for (const key in obj) {
|
|
if (hasNestedBindings(obj[key])) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
async function analyzeDuplicates() {
|
|
console.log('🔍 Analyzing duplicate TSX files...\n')
|
|
|
|
const results: AnalysisResult[] = []
|
|
|
|
// Find all TSX files in atoms, molecules, organisms
|
|
const categories = ['atoms', 'molecules', 'organisms']
|
|
|
|
for (const category of categories) {
|
|
const tsxFiles = globSync(path.join(COMPONENTS_DIR, category, '*.tsx'))
|
|
|
|
for (const tsxFile of tsxFiles) {
|
|
const basename = path.basename(tsxFile, '.tsx')
|
|
const kebab = toKebabCase(basename)
|
|
|
|
// Check for JSON equivalent in config/pages
|
|
const jsonPath = path.join(CONFIG_PAGES_DIR, category, `${kebab}.json`)
|
|
|
|
if (!fs.existsSync(jsonPath)) continue
|
|
|
|
// Check for JSON definition
|
|
const jsonDefPath = path.join(JSON_DEFS_DIR, `${kebab}.json`)
|
|
|
|
// Analyze both files
|
|
const tsxAnalysis = await analyzeTsxFile(tsxFile)
|
|
const tsxSize = fs.statSync(tsxFile).size
|
|
|
|
let jsonAnalysis = { hasBindings: false, hasChildren: false, size: 0 }
|
|
let actualJsonPath = jsonPath
|
|
|
|
if (fs.existsSync(jsonDefPath)) {
|
|
jsonAnalysis = await analyzeJsonFile(jsonDefPath)
|
|
actualJsonPath = jsonDefPath
|
|
} else if (fs.existsSync(jsonPath)) {
|
|
jsonAnalysis = await analyzeJsonFile(jsonPath)
|
|
}
|
|
|
|
// Determine recommendation
|
|
let recommendation: AnalysisResult['recommendation'] = 'safe-to-delete'
|
|
let reason = 'JSON definition exists'
|
|
|
|
if (tsxAnalysis.hasState || tsxAnalysis.hasEffects) {
|
|
if (!jsonAnalysis.hasBindings && jsonAnalysis.size < 500) {
|
|
recommendation = 'needs-review'
|
|
reason = 'TSX has state/effects but JSON seems incomplete'
|
|
} else {
|
|
recommendation = 'safe-to-delete'
|
|
reason = 'TSX has hooks but JSON should handle via createJsonComponentWithHooks'
|
|
}
|
|
}
|
|
|
|
if (tsxSize > 5000 && jsonAnalysis.size < 1000) {
|
|
recommendation = 'needs-review'
|
|
reason = 'TSX is large but JSON is small - might be missing content'
|
|
}
|
|
|
|
results.push({
|
|
tsx: path.relative(ROOT_DIR, tsxFile),
|
|
json: path.relative(ROOT_DIR, actualJsonPath),
|
|
tsxSize,
|
|
jsonSize: jsonAnalysis.size,
|
|
tsxHasHooks: tsxAnalysis.hasHooks,
|
|
tsxHasState: tsxAnalysis.hasState,
|
|
tsxHasEffects: tsxAnalysis.hasEffects,
|
|
jsonHasBindings: jsonAnalysis.hasBindings,
|
|
jsonHasChildren: jsonAnalysis.hasChildren,
|
|
recommendation,
|
|
reason
|
|
})
|
|
}
|
|
}
|
|
|
|
// Print results
|
|
console.log(`📊 Found ${results.length} duplicate components\n`)
|
|
|
|
const safeToDelete = results.filter(r => r.recommendation === 'safe-to-delete')
|
|
const needsReview = results.filter(r => r.recommendation === 'needs-review')
|
|
const keepTsx = results.filter(r => r.recommendation === 'keep-tsx')
|
|
|
|
console.log(`✅ Safe to delete: ${safeToDelete.length}`)
|
|
console.log(`⚠️ Needs review: ${needsReview.length}`)
|
|
console.log(`🔴 Keep TSX: ${keepTsx.length}\n`)
|
|
|
|
if (needsReview.length > 0) {
|
|
console.log('⚠️ NEEDS REVIEW:')
|
|
console.log('='.repeat(80))
|
|
for (const result of needsReview.slice(0, 10)) {
|
|
console.log(`\n${result.tsx}`)
|
|
console.log(` → ${result.json}`)
|
|
console.log(` TSX: ${result.tsxSize} bytes | JSON: ${result.jsonSize} bytes`)
|
|
console.log(` TSX hooks: ${result.tsxHasHooks} | state: ${result.tsxHasState} | effects: ${result.tsxHasEffects}`)
|
|
console.log(` JSON bindings: ${result.jsonHasBindings} | children: ${result.jsonHasChildren}`)
|
|
console.log(` Reason: ${result.reason}`)
|
|
}
|
|
if (needsReview.length > 10) {
|
|
console.log(`\n... and ${needsReview.length - 10} more`)
|
|
}
|
|
}
|
|
|
|
// Write full report
|
|
const reportPath = path.join(ROOT_DIR, 'duplicate-analysis.json')
|
|
fs.writeFileSync(reportPath, JSON.stringify(results, null, 2))
|
|
console.log(`\n📄 Full report written to: ${reportPath}`)
|
|
|
|
// Generate deletion script for safe components
|
|
if (safeToDelete.length > 0) {
|
|
const deletionScript = safeToDelete.map(r => `rm "${r.tsx}"`).join('\n')
|
|
const scriptPath = path.join(ROOT_DIR, 'delete-duplicates.sh')
|
|
fs.writeFileSync(scriptPath, deletionScript)
|
|
console.log(`📝 Deletion script written to: ${scriptPath}`)
|
|
console.log(` Run: bash delete-duplicates.sh`)
|
|
}
|
|
}
|
|
|
|
analyzeDuplicates().catch(error => {
|
|
console.error('❌ Analysis failed:', error)
|
|
process.exit(1)
|
|
})
|