mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
code: storybook,src,nextjs (4 files)
This commit is contained in:
@@ -8,6 +8,6 @@
|
||||
* providing a migration path to the full DBAL when ready.
|
||||
*/
|
||||
|
||||
export { closeAdapter } from '../dbal-client/adapter/close-adapter'
|
||||
export { getAdapter } from '../dbal-client/adapter/get-adapter'
|
||||
export type { DBALAdapter, ListOptions, ListResult } from '../dbal-client/types'
|
||||
export { closeAdapter } from '../../dbal-client/adapter/close-adapter'
|
||||
export { getAdapter } from '../../dbal-client/adapter/get-adapter'
|
||||
export type { DBALAdapter, ListOptions, ListResult } from '../../dbal-client/types'
|
||||
|
||||
@@ -18,6 +18,8 @@ const config: StorybookConfig = {
|
||||
staticDirs: [
|
||||
// Serve Lua packages from root
|
||||
{ from: '../../packages', to: '/packages' },
|
||||
// Serve storybook config
|
||||
{ from: '..', to: '/' },
|
||||
],
|
||||
async viteFinal(config) {
|
||||
return mergeConfig(config, {
|
||||
|
||||
274
storybook/src/lua/executor.ts
Normal file
274
storybook/src/lua/executor.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Lua Executor
|
||||
*
|
||||
* Executes Lua scripts from packages using fengari-web.
|
||||
* This allows rendering actual Lua packages in Storybook.
|
||||
*/
|
||||
|
||||
import * as fengari from 'fengari-web'
|
||||
import type { LuaRenderContext, LuaUIComponent } from '../types/lua-types'
|
||||
|
||||
const lua = fengari.lua
|
||||
const lauxlib = fengari.lauxlib
|
||||
const lualib = fengari.lualib
|
||||
|
||||
export interface LuaExecutionResult {
|
||||
success: boolean
|
||||
result?: LuaUIComponent
|
||||
error?: string
|
||||
logs: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JavaScript value to Lua and push onto stack
|
||||
*/
|
||||
function pushToLua(L: unknown, value: unknown): void {
|
||||
const state = L as fengari.lua_State
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
lua.lua_pushnil(state)
|
||||
} else if (typeof value === 'boolean') {
|
||||
lua.lua_pushboolean(state, value ? 1 : 0)
|
||||
} else if (typeof value === 'number') {
|
||||
lua.lua_pushnumber(state, value)
|
||||
} else if (typeof value === 'string') {
|
||||
lua.lua_pushstring(state, fengari.to_luastring(value))
|
||||
} else if (Array.isArray(value)) {
|
||||
lua.lua_createtable(state, value.length, 0)
|
||||
value.forEach((item, index) => {
|
||||
pushToLua(state, item)
|
||||
lua.lua_rawseti(state, -2, index + 1)
|
||||
})
|
||||
} else if (typeof value === 'object') {
|
||||
lua.lua_createtable(state, 0, Object.keys(value as object).length)
|
||||
for (const [key, val] of Object.entries(value as object)) {
|
||||
lua.lua_pushstring(state, fengari.to_luastring(key))
|
||||
pushToLua(state, val)
|
||||
lua.lua_rawset(state, -3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Lua value from stack to JavaScript
|
||||
*/
|
||||
function fromLua(L: unknown, index: number): unknown {
|
||||
const state = L as fengari.lua_State
|
||||
const type = lua.lua_type(state, index)
|
||||
|
||||
switch (type) {
|
||||
case lua.LUA_TNIL:
|
||||
return null
|
||||
case lua.LUA_TBOOLEAN:
|
||||
return lua.lua_toboolean(state, index) !== 0
|
||||
case lua.LUA_TNUMBER:
|
||||
return lua.lua_tonumber(state, index)
|
||||
case lua.LUA_TSTRING:
|
||||
return fengari.to_jsstring(lua.lua_tostring(state, index))
|
||||
case lua.LUA_TTABLE: {
|
||||
// Check if it's an array (has consecutive integer keys starting at 1)
|
||||
const result: Record<string, unknown> = {}
|
||||
const arrayPart: unknown[] = []
|
||||
let isArray = true
|
||||
let maxIndex = 0
|
||||
|
||||
lua.lua_pushnil(state)
|
||||
while (lua.lua_next(state, index < 0 ? index - 1 : index) !== 0) {
|
||||
const keyType = lua.lua_type(state, -2)
|
||||
|
||||
if (keyType === lua.LUA_TNUMBER) {
|
||||
const idx = lua.lua_tonumber(state, -2)
|
||||
if (Number.isInteger(idx) && idx > 0) {
|
||||
arrayPart[idx - 1] = fromLua(state, -1)
|
||||
maxIndex = Math.max(maxIndex, idx)
|
||||
} else {
|
||||
isArray = false
|
||||
}
|
||||
} else if (keyType === lua.LUA_TSTRING) {
|
||||
isArray = false
|
||||
const key = fengari.to_jsstring(lua.lua_tostring(state, -2))
|
||||
result[key] = fromLua(state, -1)
|
||||
}
|
||||
|
||||
lua.lua_pop(state, 1)
|
||||
}
|
||||
|
||||
// If all keys are consecutive integers, return as array
|
||||
if (isArray && maxIndex === arrayPart.length && maxIndex > 0) {
|
||||
return arrayPart
|
||||
}
|
||||
|
||||
// Mix array part into result
|
||||
arrayPart.forEach((val, idx) => {
|
||||
result[String(idx + 1)] = val
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize Lua output to proper component structure
|
||||
*/
|
||||
function normalizeComponent(raw: unknown): LuaUIComponent | null {
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
|
||||
const obj = raw as Record<string, unknown>
|
||||
|
||||
if (!obj.type || typeof obj.type !== 'string') return null
|
||||
|
||||
const component: LuaUIComponent = {
|
||||
type: obj.type,
|
||||
}
|
||||
|
||||
if (obj.props && typeof obj.props === 'object') {
|
||||
component.props = obj.props as Record<string, unknown>
|
||||
}
|
||||
|
||||
if (obj.children) {
|
||||
if (Array.isArray(obj.children)) {
|
||||
component.children = obj.children
|
||||
.map(c => normalizeComponent(c))
|
||||
.filter((c): c is LuaUIComponent => c !== null)
|
||||
}
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Lua state with standard libraries
|
||||
*/
|
||||
function createLuaState(): fengari.lua_State {
|
||||
const L = lauxlib.luaL_newstate()
|
||||
lualib.luaL_openlibs(L)
|
||||
return L
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Lua script and call its render function with context
|
||||
*/
|
||||
export async function executeLuaRender(
|
||||
luaCode: string,
|
||||
context: LuaRenderContext,
|
||||
functionName = 'render'
|
||||
): Promise<LuaExecutionResult> {
|
||||
const logs: string[] = []
|
||||
let L: fengari.lua_State | null = null
|
||||
|
||||
try {
|
||||
L = createLuaState()
|
||||
|
||||
// Override print to capture logs
|
||||
lua.lua_pushcfunction(L, (state: fengari.lua_State) => {
|
||||
const nargs = lua.lua_gettop(state)
|
||||
const parts: string[] = []
|
||||
for (let i = 1; i <= nargs; i++) {
|
||||
parts.push(String(fromLua(state, i)))
|
||||
}
|
||||
logs.push(parts.join('\t'))
|
||||
return 0
|
||||
})
|
||||
lua.lua_setglobal(L, fengari.to_luastring('print'))
|
||||
|
||||
// Load and execute the Lua code
|
||||
const loadResult = lauxlib.luaL_loadstring(L, fengari.to_luastring(luaCode))
|
||||
if (loadResult !== lua.LUA_OK) {
|
||||
const error = fengari.to_jsstring(lua.lua_tostring(L, -1))
|
||||
return { success: false, error: `Load error: ${error}`, logs }
|
||||
}
|
||||
|
||||
const execResult = lua.lua_pcall(L, 0, 1, 0)
|
||||
if (execResult !== lua.LUA_OK) {
|
||||
const error = fengari.to_jsstring(lua.lua_tostring(L, -1))
|
||||
return { success: false, error: `Execution error: ${error}`, logs }
|
||||
}
|
||||
|
||||
// Check what we got back
|
||||
const returnType = lua.lua_type(L, -1)
|
||||
|
||||
if (returnType === lua.LUA_TFUNCTION) {
|
||||
// Script returned a function directly (render function)
|
||||
pushToLua(L, context)
|
||||
const callResult = lua.lua_pcall(L, 1, 1, 0)
|
||||
if (callResult !== lua.LUA_OK) {
|
||||
const error = fengari.to_jsstring(lua.lua_tostring(L, -1))
|
||||
return { success: false, error: `Call error: ${error}`, logs }
|
||||
}
|
||||
} else if (returnType === lua.LUA_TTABLE) {
|
||||
// Script returned a module table, look for render function
|
||||
lua.lua_getfield(L, -1, fengari.to_luastring(functionName))
|
||||
|
||||
if (lua.lua_isfunction(L, -1)) {
|
||||
pushToLua(L, context)
|
||||
const callResult = lua.lua_pcall(L, 1, 1, 0)
|
||||
if (callResult !== lua.LUA_OK) {
|
||||
const error = fengari.to_jsstring(lua.lua_tostring(L, -1))
|
||||
return { success: false, error: `Render error: ${error}`, logs }
|
||||
}
|
||||
} else {
|
||||
// The table itself might be the component tree
|
||||
lua.lua_pop(L, 1) // Remove the nil/non-function
|
||||
// Keep the table on stack
|
||||
}
|
||||
}
|
||||
|
||||
// Convert result to JavaScript
|
||||
const rawResult = fromLua(L, -1)
|
||||
const result = normalizeComponent(rawResult)
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid component structure returned`,
|
||||
logs
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, result, logs }
|
||||
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
logs
|
||||
}
|
||||
} finally {
|
||||
if (L) {
|
||||
lua.lua_close(L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and execute a Lua file from a package
|
||||
*/
|
||||
export async function loadAndExecuteLuaFile(
|
||||
packageId: string,
|
||||
scriptPath: string,
|
||||
context: LuaRenderContext
|
||||
): Promise<LuaExecutionResult> {
|
||||
try {
|
||||
const response = await fetch(`/packages/${packageId}/seed/scripts/${scriptPath}`)
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to load script: ${response.statusText}`,
|
||||
logs: []
|
||||
}
|
||||
}
|
||||
|
||||
const luaCode = await response.text()
|
||||
return await executeLuaRender(luaCode, context)
|
||||
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
logs: []
|
||||
}
|
||||
}
|
||||
}
|
||||
51
storybook/src/types/fengari.d.ts
vendored
Normal file
51
storybook/src/types/fengari.d.ts
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// Type declarations for fengari-web
|
||||
|
||||
declare module 'fengari-web' {
|
||||
export interface lua_State {}
|
||||
|
||||
export const lua: {
|
||||
LUA_OK: number
|
||||
LUA_TNIL: number
|
||||
LUA_TBOOLEAN: number
|
||||
LUA_TNUMBER: number
|
||||
LUA_TSTRING: number
|
||||
LUA_TTABLE: number
|
||||
LUA_TFUNCTION: number
|
||||
|
||||
lua_pushnil(L: lua_State): void
|
||||
lua_pushboolean(L: lua_State, b: number): void
|
||||
lua_pushnumber(L: lua_State, n: number): void
|
||||
lua_pushstring(L: lua_State, s: Uint8Array): void
|
||||
lua_pushcfunction(L: lua_State, fn: (L: lua_State) => number): void
|
||||
|
||||
lua_createtable(L: lua_State, narr: number, nrec: number): void
|
||||
lua_rawseti(L: lua_State, index: number, i: number): void
|
||||
lua_rawset(L: lua_State, index: number): void
|
||||
lua_setglobal(L: lua_State, name: Uint8Array): void
|
||||
lua_getfield(L: lua_State, index: number, name: Uint8Array): void
|
||||
|
||||
lua_type(L: lua_State, index: number): number
|
||||
lua_gettop(L: lua_State): number
|
||||
lua_toboolean(L: lua_State, index: number): number
|
||||
lua_tonumber(L: lua_State, index: number): number
|
||||
lua_tostring(L: lua_State, index: number): Uint8Array
|
||||
lua_isfunction(L: lua_State, index: number): boolean
|
||||
|
||||
lua_pop(L: lua_State, n: number): void
|
||||
lua_pcall(L: lua_State, nargs: number, nresults: number, msgh: number): number
|
||||
lua_next(L: lua_State, index: number): number
|
||||
lua_close(L: lua_State): void
|
||||
}
|
||||
|
||||
export const lauxlib: {
|
||||
luaL_newstate(): lua_State
|
||||
luaL_loadstring(L: lua_State, s: Uint8Array): number
|
||||
}
|
||||
|
||||
export const lualib: {
|
||||
luaL_openlibs(L: lua_State): void
|
||||
}
|
||||
|
||||
export function to_luastring(s: string): Uint8Array
|
||||
export function to_jsstring(s: Uint8Array): string
|
||||
}
|
||||
Reference in New Issue
Block a user