code: storybook,src,types (3 files)

This commit is contained in:
Richard Ward
2025-12-30 21:16:54 +00:00
parent 7277d5b17e
commit 3ea1fe79bf
3 changed files with 275 additions and 10 deletions

View 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: [],
}
}

View File

@@ -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'

View File

@@ -47,6 +47,10 @@ export interface LuaRenderContext {
level?: number
email?: string
}
tenant?: {
id?: string
name?: string
}
nerdMode?: boolean
theme?: 'light' | 'dark'
[key: string]: unknown