mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Align JSON UI registry with canonical components
This commit is contained in:
82
scripts/validate-json-ui-registry.cjs
Normal file
82
scripts/validate-json-ui-registry.cjs
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const registryPath = path.join(process.cwd(), 'json-components-registry.json')
|
||||
const schemaPath = path.join(process.cwd(), 'src', 'schemas', 'registry-validation.json')
|
||||
|
||||
if (!fs.existsSync(registryPath)) {
|
||||
console.error('❌ Could not find json-components-registry.json')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(schemaPath)) {
|
||||
console.error('❌ Could not find src/schemas/registry-validation.json')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'))
|
||||
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
|
||||
|
||||
const primitiveTypes = new Set([
|
||||
'div',
|
||||
'span',
|
||||
'p',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'section',
|
||||
'article',
|
||||
'header',
|
||||
'footer',
|
||||
'main',
|
||||
'aside',
|
||||
'nav',
|
||||
])
|
||||
|
||||
const registryTypes = new Set()
|
||||
|
||||
for (const entry of registry.components || []) {
|
||||
if (entry.source === 'atoms' || entry.source === 'molecules') {
|
||||
const name = entry.export || entry.name || entry.type
|
||||
if (name) {
|
||||
registryTypes.add(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const schemaTypes = new Set()
|
||||
|
||||
const collectTypes = (components) => {
|
||||
if (!components) return
|
||||
if (Array.isArray(components)) {
|
||||
components.forEach(collectTypes)
|
||||
return
|
||||
}
|
||||
if (components.type) {
|
||||
schemaTypes.add(components.type)
|
||||
}
|
||||
if (components.children) {
|
||||
collectTypes(components.children)
|
||||
}
|
||||
}
|
||||
|
||||
collectTypes(schema.components || [])
|
||||
|
||||
const missing = []
|
||||
for (const type of schemaTypes) {
|
||||
if (!primitiveTypes.has(type) && !registryTypes.has(type)) {
|
||||
missing.push(type)
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length) {
|
||||
console.error(`❌ Missing registry entries for: ${missing.join(', ')}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('✅ JSON UI registry validation passed for primitives and atom/molecule components.')
|
||||
@@ -17,40 +17,9 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
||||
import { Skeleton as ShadcnSkeleton } from '@/components/ui/skeleton'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Heading } from '@/components/atoms/Heading'
|
||||
import { Text } from '@/components/atoms/Text'
|
||||
import { TextArea } from '@/components/atoms/TextArea'
|
||||
import { List as ListComponent } from '@/components/atoms/List'
|
||||
import { Grid } from '@/components/atoms/Grid'
|
||||
import { Stack } from '@/components/atoms/Stack'
|
||||
import { Flex } from '@/components/atoms/Flex'
|
||||
import { Container } from '@/components/atoms/Container'
|
||||
import { Link } from '@/components/atoms/Link'
|
||||
import { Image } from '@/components/atoms/Image'
|
||||
import { Avatar as AtomAvatar } from '@/components/atoms/Avatar'
|
||||
import { Code } from '@/components/atoms/Code'
|
||||
import { Tag } from '@/components/atoms/Tag'
|
||||
import { Spinner } from '@/components/atoms/Spinner'
|
||||
import { Skeleton as AtomSkeleton } from '@/components/atoms/Skeleton'
|
||||
import { Slider } from '@/components/atoms/Slider'
|
||||
import { NumberInput } from '@/components/atoms/NumberInput'
|
||||
import { Radio } from '@/components/atoms/Radio'
|
||||
import { Alert as AtomAlert } from '@/components/atoms/Alert'
|
||||
import { InfoBox } from '@/components/atoms/InfoBox'
|
||||
import { EmptyState } from '@/components/atoms/EmptyState'
|
||||
import { Table as AtomTable } from '@/components/atoms/Table'
|
||||
import { KeyValue } from '@/components/atoms/KeyValue'
|
||||
import { StatCard } from '@/components/atoms/StatCard'
|
||||
import { StatusBadge } from '@/components/atoms/StatusBadge'
|
||||
import { DataCard } from '@/components/molecules/DataCard'
|
||||
import { SearchInput } from '@/components/molecules/SearchInput'
|
||||
import { ActionBar } from '@/components/molecules/ActionBar'
|
||||
import { AppBranding } from '@/components/molecules/AppBranding'
|
||||
import { LabelWithBadge } from '@/components/molecules/LabelWithBadge'
|
||||
import { EmptyEditorState } from '@/components/molecules/EmptyEditorState'
|
||||
import { LoadingFallback } from '@/components/molecules/LoadingFallback'
|
||||
import { LoadingState } from '@/components/molecules/LoadingState'
|
||||
import { NavigationGroupHeader } from '@/components/molecules/NavigationGroupHeader'
|
||||
import * as AtomComponents from '@/components/atoms'
|
||||
import * as MoleculeComponents from '@/components/molecules'
|
||||
import jsonComponentsRegistry from '../../../json-components-registry.json'
|
||||
import {
|
||||
ArrowLeft, ArrowRight, Check, X, Plus, Minus, MagnifyingGlass,
|
||||
Funnel, Download, Upload, PencilSimple, Trash, Eye, EyeClosed,
|
||||
@@ -64,6 +33,42 @@ export interface UIComponentRegistry {
|
||||
[key: string]: ComponentType<any>
|
||||
}
|
||||
|
||||
interface JsonRegistryEntry {
|
||||
name?: string
|
||||
type?: string
|
||||
export?: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
interface JsonComponentRegistry {
|
||||
components?: JsonRegistryEntry[]
|
||||
}
|
||||
|
||||
const jsonRegistry = jsonComponentsRegistry as JsonComponentRegistry
|
||||
|
||||
const buildRegistryFromNames = (
|
||||
names: string[],
|
||||
components: Record<string, ComponentType<any>>
|
||||
): UIComponentRegistry => {
|
||||
return names.reduce<UIComponentRegistry>((registry, name) => {
|
||||
const component = components[name]
|
||||
if (component) {
|
||||
registry[name] = component
|
||||
}
|
||||
return registry
|
||||
}, {})
|
||||
}
|
||||
|
||||
const jsonRegistryEntries = jsonRegistry.components ?? []
|
||||
const atomRegistryNames = jsonRegistryEntries
|
||||
.filter((entry) => entry.source === 'atoms')
|
||||
.map((entry) => entry.export ?? entry.name ?? entry.type)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
const moleculeRegistryNames = jsonRegistryEntries
|
||||
.filter((entry) => entry.source === 'molecules')
|
||||
.map((entry) => entry.export ?? entry.name ?? entry.type)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
|
||||
export const primitiveComponents: UIComponentRegistry = {
|
||||
div: 'div' as any,
|
||||
span: 'span' as any,
|
||||
@@ -131,45 +136,15 @@ export const shadcnComponents: UIComponentRegistry = {
|
||||
AvatarImage,
|
||||
}
|
||||
|
||||
export const atomComponents: UIComponentRegistry = {
|
||||
Heading,
|
||||
Text,
|
||||
TextArea,
|
||||
List: ListComponent,
|
||||
Grid,
|
||||
Stack,
|
||||
Flex,
|
||||
Container,
|
||||
Link,
|
||||
Image,
|
||||
Avatar: AtomAvatar,
|
||||
Code,
|
||||
Tag,
|
||||
Spinner,
|
||||
Skeleton: AtomSkeleton,
|
||||
Slider,
|
||||
NumberInput,
|
||||
Radio,
|
||||
Alert: AtomAlert,
|
||||
InfoBox,
|
||||
EmptyState,
|
||||
Table: AtomTable,
|
||||
KeyValue,
|
||||
StatCard,
|
||||
StatusBadge,
|
||||
}
|
||||
export const atomComponents: UIComponentRegistry = buildRegistryFromNames(
|
||||
atomRegistryNames,
|
||||
AtomComponents as Record<string, ComponentType<any>>
|
||||
)
|
||||
|
||||
export const moleculeComponents: UIComponentRegistry = {
|
||||
DataCard,
|
||||
SearchInput,
|
||||
ActionBar,
|
||||
AppBranding,
|
||||
LabelWithBadge,
|
||||
EmptyEditorState,
|
||||
LoadingFallback,
|
||||
LoadingState,
|
||||
NavigationGroupHeader,
|
||||
}
|
||||
export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames(
|
||||
moleculeRegistryNames,
|
||||
MoleculeComponents as Record<string, ComponentType<any>>
|
||||
)
|
||||
|
||||
export const iconComponents: UIComponentRegistry = {
|
||||
ArrowLeft,
|
||||
|
||||
74
src/schemas/registry-validation.json
Normal file
74
src/schemas/registry-validation.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"id": "registry-validation",
|
||||
"name": "Registry Validation",
|
||||
"layout": {
|
||||
"type": "single"
|
||||
},
|
||||
"dataSources": [],
|
||||
"components": [
|
||||
{
|
||||
"id": "root",
|
||||
"type": "div",
|
||||
"props": {
|
||||
"className": "p-6 space-y-6"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "branding",
|
||||
"type": "AppBranding",
|
||||
"props": {
|
||||
"title": "Registry Validation",
|
||||
"subtitle": "Atoms, molecules, and primitives"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stack",
|
||||
"type": "Stack",
|
||||
"props": {
|
||||
"direction": "horizontal",
|
||||
"spacing": "lg",
|
||||
"className": "items-stretch"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "stat-card",
|
||||
"type": "StatCard",
|
||||
"props": {
|
||||
"title": "Active Users",
|
||||
"value": "128",
|
||||
"description": "Past 24 hours"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flex",
|
||||
"type": "Flex",
|
||||
"props": {
|
||||
"direction": "col",
|
||||
"gap": "sm",
|
||||
"className": "rounded-md border p-4"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "flex-title",
|
||||
"type": "Heading",
|
||||
"props": {
|
||||
"level": 4,
|
||||
"children": "Flex Container"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flex-text",
|
||||
"type": "Text",
|
||||
"props": {
|
||||
"children": "Ensures primitives and atom components resolve."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalActions": []
|
||||
}
|
||||
Reference in New Issue
Block a user