mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 14:25:02 +00:00
code: storybook,src,types (3 files)
This commit is contained in:
234
storybook/src/mocks/auto-loader.ts
Normal file
234
storybook/src/mocks/auto-loader.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Package Auto-Loader
|
||||
*
|
||||
* Loads component trees and metadata directly from the actual packages.
|
||||
* No manual mocking needed - reads from packages/{id}/seed/
|
||||
*/
|
||||
|
||||
import type { LuaUIComponent, LuaPackageMetadata, LuaRenderContext } from '../types/lua-types'
|
||||
import type { MockLuaResult, MockPackageDefinition } from './lua-engine'
|
||||
import { registerMockPackage } from './lua-engine'
|
||||
import type { TemplateVariables } from './schema'
|
||||
import { processTemplates } from './schema'
|
||||
|
||||
/**
|
||||
* Component definition from components.json
|
||||
*/
|
||||
interface PackageComponent {
|
||||
id: string
|
||||
type: string
|
||||
props?: Record<string, unknown>
|
||||
children?: PackageComponent[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a PackageComponent to LuaUIComponent
|
||||
*/
|
||||
function packageComponentToLua(component: PackageComponent): LuaUIComponent {
|
||||
return {
|
||||
type: component.type,
|
||||
props: component.props,
|
||||
children: component.children?.map(packageComponentToLua),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load package metadata from the actual package
|
||||
*/
|
||||
export async function loadPackageMetadata(packageId: string): Promise<LuaPackageMetadata | null> {
|
||||
try {
|
||||
const response = await fetch(`/packages/${packageId}/seed/metadata.json`)
|
||||
if (!response.ok) return null
|
||||
return await response.json()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load components.json from a package
|
||||
*/
|
||||
export async function loadPackageComponents(packageId: string): Promise<PackageComponent[]> {
|
||||
try {
|
||||
const response = await fetch(`/packages/${packageId}/seed/components.json`)
|
||||
if (!response.ok) return []
|
||||
const components = await response.json()
|
||||
return Array.isArray(components) ? components : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load script manifest from a package
|
||||
*/
|
||||
export async function loadPackageScripts(packageId: string): Promise<Array<{ file: string; name: string; description?: string }>> {
|
||||
try {
|
||||
const response = await fetch(`/packages/${packageId}/seed/scripts/manifest.json`)
|
||||
if (!response.ok) return []
|
||||
const manifest = await response.json()
|
||||
return manifest.scripts || []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create template variables from context
|
||||
*/
|
||||
function createTemplateVars(ctx: LuaRenderContext): TemplateVariables {
|
||||
return {
|
||||
'user.username': ctx.user?.username ?? 'Guest',
|
||||
'user.email': ctx.user?.email ?? 'guest@example.com',
|
||||
'user.level': ctx.user?.level ?? 1,
|
||||
'user.id': ctx.user?.id ?? 'guest',
|
||||
'tenant.name': ctx.tenant?.name ?? 'Default',
|
||||
'tenant.id': ctx.tenant?.id ?? 'default',
|
||||
'nerdMode': ctx.nerdMode ?? false,
|
||||
'theme': ctx.theme ?? 'light',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-generate renders from components.json
|
||||
* Each component becomes a render with its id as the key
|
||||
*/
|
||||
function componentsToRenders(
|
||||
components: PackageComponent[]
|
||||
): Record<string, (ctx: LuaRenderContext) => LuaUIComponent> {
|
||||
const renders: Record<string, (ctx: LuaRenderContext) => LuaUIComponent> = {}
|
||||
|
||||
for (const component of components) {
|
||||
const luaComponent = packageComponentToLua(component)
|
||||
|
||||
// Register by component id (e.g., "stat_card", "dashboard_grid")
|
||||
renders[component.id] = (ctx: LuaRenderContext) => {
|
||||
const vars = createTemplateVars(ctx)
|
||||
return processTemplates(luaComponent, vars)
|
||||
}
|
||||
|
||||
// Also register with .json suffix for discovery
|
||||
renders[`${component.id}.json`] = renders[component.id]
|
||||
}
|
||||
|
||||
// Add a special "all_components" render that shows all components
|
||||
if (components.length > 0) {
|
||||
renders['all_components'] = (ctx: LuaRenderContext) => {
|
||||
const vars = createTemplateVars(ctx)
|
||||
return processTemplates({
|
||||
type: 'Stack',
|
||||
props: { spacing: 4 },
|
||||
children: components.map(c => ({
|
||||
type: 'Box',
|
||||
children: [
|
||||
{
|
||||
type: 'Typography',
|
||||
props: { variant: 'overline', text: c.id },
|
||||
},
|
||||
packageComponentToLua(c),
|
||||
],
|
||||
})),
|
||||
}, vars)
|
||||
}
|
||||
}
|
||||
|
||||
return renders
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-load a package from the workspace
|
||||
* Reads metadata.json and components.json directly
|
||||
*/
|
||||
export async function autoLoadPackage(packageId: string): Promise<MockPackageDefinition | null> {
|
||||
const [metadata, components] = await Promise.all([
|
||||
loadPackageMetadata(packageId),
|
||||
loadPackageComponents(packageId),
|
||||
])
|
||||
|
||||
if (!metadata) {
|
||||
console.warn(`[AutoLoader] No metadata found for ${packageId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
// Generate renders from components
|
||||
const renders = componentsToRenders(components)
|
||||
|
||||
// Add an info render for packages with no components
|
||||
if (Object.keys(renders).length === 0) {
|
||||
renders['info'] = () => ({
|
||||
type: 'Alert',
|
||||
props: { severity: 'info' },
|
||||
children: [
|
||||
{
|
||||
type: 'Typography',
|
||||
props: {
|
||||
variant: 'body2',
|
||||
text: `Package "${metadata.name}" has no components.json definitions. Scripts: ${metadata.exports?.scripts?.join(', ') || 'none'}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
metadata,
|
||||
renders,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-load and register a package
|
||||
*/
|
||||
export async function autoRegisterPackage(packageId: string): Promise<boolean> {
|
||||
const pkg = await autoLoadPackage(packageId)
|
||||
if (pkg) {
|
||||
registerMockPackage(pkg)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-load multiple packages
|
||||
*/
|
||||
export async function autoRegisterPackages(packageIds: string[]): Promise<string[]> {
|
||||
const registered: string[] = []
|
||||
|
||||
await Promise.all(
|
||||
packageIds.map(async (id) => {
|
||||
const success = await autoRegisterPackage(id)
|
||||
if (success) registered.push(id)
|
||||
})
|
||||
)
|
||||
|
||||
return registered
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a render from an auto-loaded package
|
||||
*/
|
||||
export function executeAutoRender(
|
||||
packageId: string,
|
||||
componentId: string,
|
||||
context: LuaRenderContext,
|
||||
components: PackageComponent[]
|
||||
): MockLuaResult {
|
||||
const component = components.find(c => c.id === componentId)
|
||||
|
||||
if (!component) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Component "${componentId}" not found in ${packageId}`,
|
||||
logs: [`Available: ${components.map(c => c.id).join(', ')}`],
|
||||
}
|
||||
}
|
||||
|
||||
const luaComponent = packageComponentToLua(component)
|
||||
const vars = createTemplateVars(context)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: processTemplates(luaComponent, vars),
|
||||
logs: [],
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,44 @@
|
||||
/**
|
||||
* Package mocks index
|
||||
*
|
||||
* JSON-driven mocks are now preferred!
|
||||
* Add new mocks as JSON files in ../data/*.json
|
||||
*
|
||||
* Legacy TypeScript mocks are still imported for backwards compatibility.
|
||||
* Three sources of mock data (in priority order):
|
||||
* 1. Auto-loaded from packages/{id}/seed/components.json (real data!)
|
||||
* 2. JSON files in ../data/*.json (manual overrides)
|
||||
* 3. Legacy TypeScript mocks (deprecated)
|
||||
*/
|
||||
|
||||
// Register JSON-based mocks (preferred)
|
||||
// Register JSON-based mocks first (these can override auto-loaded)
|
||||
import { registerJsonMocks } from '../json-loader'
|
||||
registerJsonMocks()
|
||||
|
||||
// Legacy TypeScript mocks (kept for reference, but JSON takes precedence)
|
||||
// import './dashboard'
|
||||
// import './ui-level4'
|
||||
// import './user-manager'
|
||||
// Auto-load packages that have components.json
|
||||
import { autoRegisterPackages } from '../auto-loader'
|
||||
|
||||
// Packages known to have good components.json files
|
||||
const AUTO_LOAD_PACKAGES = [
|
||||
'dashboard',
|
||||
'ui_header',
|
||||
'ui_footer',
|
||||
'nav_menu',
|
||||
'data_table',
|
||||
'stats_grid',
|
||||
'form_builder',
|
||||
'notification_center',
|
||||
]
|
||||
|
||||
// Initialize on module load
|
||||
let initialized = false
|
||||
export async function initializeMocks(): Promise<void> {
|
||||
if (initialized) return
|
||||
initialized = true
|
||||
|
||||
// Auto-load from real packages first
|
||||
const autoLoaded = await autoRegisterPackages(AUTO_LOAD_PACKAGES)
|
||||
console.log(`[Mocks] Auto-loaded ${autoLoaded.length} packages from workspace:`, autoLoaded)
|
||||
|
||||
// Then register JSON overrides (these take precedence)
|
||||
registerJsonMocks()
|
||||
console.log('[Mocks] Registered JSON mock overrides')
|
||||
}
|
||||
|
||||
// Re-export utilities
|
||||
export {
|
||||
@@ -26,3 +50,6 @@ export {
|
||||
|
||||
// Export JSON utilities
|
||||
export { getRenderDescriptions, executeJsonMock } from '../json-loader'
|
||||
|
||||
// Export auto-loader utilities
|
||||
export { autoLoadPackage, loadPackageComponents, loadPackageMetadata } from '../auto-loader'
|
||||
|
||||
@@ -47,6 +47,10 @@ export interface LuaRenderContext {
|
||||
level?: number
|
||||
email?: string
|
||||
}
|
||||
tenant?: {
|
||||
id?: string
|
||||
name?: string
|
||||
}
|
||||
nerdMode?: boolean
|
||||
theme?: 'light' | 'dark'
|
||||
[key: string]: unknown
|
||||
|
||||
Reference in New Issue
Block a user