mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
config: studio,packages,codegen (6 files)
This commit is contained in:
207
packages/codegen_studio/seed/scripts/package_template/cli.lua
Normal file
207
packages/codegen_studio/seed/scripts/package_template/cli.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
-- Package template CLI interface
|
||||
-- @module package_template.cli
|
||||
|
||||
local generator = require("package_template.generator")
|
||||
local templates = require("package_template.templates")
|
||||
|
||||
local M = {}
|
||||
|
||||
---Print usage help
|
||||
function M.print_help()
|
||||
print([[
|
||||
Package Template Generator
|
||||
==========================
|
||||
|
||||
Usage: lua cli.lua <command> [options]
|
||||
|
||||
Commands:
|
||||
new <package_id> Create a new package with interactive prompts
|
||||
quick <package_id> Create a new package with defaults
|
||||
list-categories List available package categories
|
||||
validate <config> Validate a package configuration
|
||||
|
||||
Options for 'new' and 'quick':
|
||||
--name <name> Display name (default: derived from package_id)
|
||||
--description <desc> Package description
|
||||
--category <cat> Package category (default: ui)
|
||||
--min-level <n> Minimum access level 0-6 (default: 2)
|
||||
--primary Package can own routes (default)
|
||||
--dependency Package is dependency-only
|
||||
--with-schema Include database schema scaffolding
|
||||
--entities <e1,e2> Entity names for schema (comma-separated)
|
||||
--with-components Include component scaffolding
|
||||
--components <c1,c2> Component names (comma-separated)
|
||||
--deps <d1,d2> Package dependencies (comma-separated)
|
||||
--output <path> Output directory (default: packages/)
|
||||
|
||||
Examples:
|
||||
lua cli.lua new my_package --category tools --min-level 3
|
||||
lua cli.lua quick my_widget --dependency --category ui
|
||||
lua cli.lua new forum_clone --with-schema --entities Thread,Post,Reply
|
||||
lua cli.lua new my_dashboard --with-components --components StatCard,Chart
|
||||
]])
|
||||
end
|
||||
|
||||
---Parse command line arguments
|
||||
---@param args string[]
|
||||
---@return table
|
||||
function M.parse_args(args)
|
||||
local parsed = {
|
||||
command = nil,
|
||||
packageId = nil,
|
||||
options = {
|
||||
name = nil,
|
||||
description = nil,
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = false,
|
||||
withTests = true,
|
||||
withComponents = false,
|
||||
entities = {},
|
||||
components = {},
|
||||
dependencies = {},
|
||||
output = "packages/"
|
||||
}
|
||||
}
|
||||
|
||||
local i = 1
|
||||
while i <= #args do
|
||||
local arg = args[i]
|
||||
|
||||
if not parsed.command then
|
||||
parsed.command = arg
|
||||
elseif not parsed.packageId and not string.match(arg, "^%-") then
|
||||
parsed.packageId = arg
|
||||
elseif arg == "--name" then
|
||||
i = i + 1
|
||||
parsed.options.name = args[i]
|
||||
elseif arg == "--description" then
|
||||
i = i + 1
|
||||
parsed.options.description = args[i]
|
||||
elseif arg == "--category" then
|
||||
i = i + 1
|
||||
parsed.options.category = args[i]
|
||||
elseif arg == "--min-level" then
|
||||
i = i + 1
|
||||
parsed.options.minLevel = tonumber(args[i])
|
||||
elseif arg == "--primary" then
|
||||
parsed.options.primary = true
|
||||
elseif arg == "--dependency" then
|
||||
parsed.options.primary = false
|
||||
elseif arg == "--with-schema" then
|
||||
parsed.options.withSchema = true
|
||||
elseif arg == "--entities" then
|
||||
i = i + 1
|
||||
for entity in string.gmatch(args[i], "[^,]+") do
|
||||
table.insert(parsed.options.entities, entity)
|
||||
end
|
||||
elseif arg == "--with-components" then
|
||||
parsed.options.withComponents = true
|
||||
elseif arg == "--components" then
|
||||
i = i + 1
|
||||
for comp in string.gmatch(args[i], "[^,]+") do
|
||||
table.insert(parsed.options.components, comp)
|
||||
end
|
||||
elseif arg == "--deps" then
|
||||
i = i + 1
|
||||
for dep in string.gmatch(args[i], "[^,]+") do
|
||||
table.insert(parsed.options.dependencies, dep)
|
||||
end
|
||||
elseif arg == "--output" then
|
||||
i = i + 1
|
||||
parsed.options.output = args[i]
|
||||
elseif arg == "--help" or arg == "-h" then
|
||||
parsed.command = "help"
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return parsed
|
||||
end
|
||||
|
||||
---Build config from parsed arguments
|
||||
---@param parsed table
|
||||
---@return PackageConfig
|
||||
function M.build_config(parsed)
|
||||
local defaults = generator.get_default_config(parsed.packageId)
|
||||
|
||||
return {
|
||||
packageId = parsed.packageId,
|
||||
name = parsed.options.name or defaults.name,
|
||||
description = parsed.options.description or defaults.description,
|
||||
author = "MetaBuilder",
|
||||
category = parsed.options.category,
|
||||
minLevel = parsed.options.minLevel,
|
||||
primary = parsed.options.primary,
|
||||
withSchema = parsed.options.withSchema,
|
||||
withTests = parsed.options.withTests,
|
||||
withComponents = parsed.options.withComponents,
|
||||
entities = parsed.options.entities,
|
||||
components = parsed.options.components,
|
||||
dependencies = parsed.options.dependencies
|
||||
}
|
||||
end
|
||||
|
||||
---Execute the CLI
|
||||
---@param args string[]
|
||||
---@return number Exit code
|
||||
function M.run(args)
|
||||
local parsed = M.parse_args(args)
|
||||
|
||||
if parsed.command == "help" or not parsed.command then
|
||||
M.print_help()
|
||||
return 0
|
||||
end
|
||||
|
||||
if parsed.command == "list-categories" then
|
||||
print("Available categories:")
|
||||
for _, cat in ipairs(templates.get_categories()) do
|
||||
print(" - " .. cat)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
if parsed.command == "new" or parsed.command == "quick" then
|
||||
if not parsed.packageId then
|
||||
print("Error: package_id is required")
|
||||
return 1
|
||||
end
|
||||
|
||||
local config = M.build_config(parsed)
|
||||
local valid, errors = generator.validate_config(config)
|
||||
|
||||
if not valid then
|
||||
print("Configuration errors:")
|
||||
for _, err in ipairs(errors) do
|
||||
print(" - " .. err)
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
local result = generator.generate(config)
|
||||
|
||||
if not result.success then
|
||||
print("Generation failed:")
|
||||
for _, err in ipairs(result.errors) do
|
||||
print(" - " .. err)
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
print("Generated package: " .. result.packagePath)
|
||||
print("Files:")
|
||||
for _, file in ipairs(result.files) do
|
||||
print(" - " .. file.path)
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
print("Unknown command: " .. (parsed.command or "none"))
|
||||
M.print_help()
|
||||
return 1
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,335 @@
|
||||
-- Package generator - creates all files for a new package
|
||||
-- @module package_template.generator
|
||||
|
||||
local templates = require("package_template.templates")
|
||||
|
||||
local M = {}
|
||||
|
||||
---Get default package configuration
|
||||
---@param packageId string
|
||||
---@return PackageConfig
|
||||
function M.get_default_config(packageId)
|
||||
-- Convert package_id to display name
|
||||
local name = packageId:gsub("_", " "):gsub("(%l)(%w*)", function(a, b)
|
||||
return string.upper(a) .. b
|
||||
end)
|
||||
|
||||
return {
|
||||
packageId = packageId,
|
||||
name = name,
|
||||
description = name .. " package for MetaBuilder",
|
||||
author = "MetaBuilder",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = false,
|
||||
withTests = true,
|
||||
withComponents = false,
|
||||
entities = {},
|
||||
components = {},
|
||||
dependencies = {}
|
||||
}
|
||||
end
|
||||
|
||||
---Validate package configuration
|
||||
---@param config PackageConfig
|
||||
---@return boolean valid
|
||||
---@return string[] errors
|
||||
function M.validate_config(config)
|
||||
local errors = {}
|
||||
|
||||
-- Required fields
|
||||
if not config.packageId then
|
||||
table.insert(errors, "packageId is required")
|
||||
elseif not string.match(config.packageId, "^[a-z][a-z0-9_]*$") then
|
||||
table.insert(errors, "packageId must be lowercase with underscores, starting with a letter")
|
||||
end
|
||||
|
||||
if not config.name then
|
||||
table.insert(errors, "name is required")
|
||||
end
|
||||
|
||||
if not config.description then
|
||||
table.insert(errors, "description is required")
|
||||
end
|
||||
|
||||
if not config.category then
|
||||
table.insert(errors, "category is required")
|
||||
else
|
||||
local validCategories = templates.get_categories()
|
||||
local found = false
|
||||
for _, cat in ipairs(validCategories) do
|
||||
if cat == config.category then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
table.insert(errors, "category must be one of: " .. table.concat(validCategories, ", "))
|
||||
end
|
||||
end
|
||||
|
||||
if config.minLevel == nil then
|
||||
table.insert(errors, "minLevel is required")
|
||||
elseif type(config.minLevel) ~= "number" or config.minLevel < 0 or config.minLevel > 6 then
|
||||
table.insert(errors, "minLevel must be a number between 0 and 6")
|
||||
end
|
||||
|
||||
if config.primary == nil then
|
||||
table.insert(errors, "primary is required (true or false)")
|
||||
end
|
||||
|
||||
-- Validate entities if schema is requested
|
||||
if config.withSchema then
|
||||
if not config.entities or #config.entities == 0 then
|
||||
table.insert(errors, "entities are required when withSchema is true")
|
||||
else
|
||||
for i, entity in ipairs(config.entities) do
|
||||
if not string.match(entity, "^[A-Z][a-zA-Z0-9]*$") then
|
||||
table.insert(errors, "entity[" .. i .. "] must be PascalCase (e.g., 'ForumPost')")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate component names
|
||||
if config.components then
|
||||
for i, comp in ipairs(config.components) do
|
||||
if not string.match(comp, "^[A-Z][a-zA-Z0-9]*$") then
|
||||
table.insert(errors, "component[" .. i .. "] must be PascalCase (e.g., 'MyComponent')")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return #errors == 0, errors
|
||||
end
|
||||
|
||||
---Generate all files for a new package
|
||||
---@param config PackageConfig
|
||||
---@return GenerateResult
|
||||
function M.generate(config)
|
||||
local valid, errors = M.validate_config(config)
|
||||
if not valid then
|
||||
return {
|
||||
success = false,
|
||||
files = {},
|
||||
errors = errors,
|
||||
packagePath = ""
|
||||
}
|
||||
end
|
||||
|
||||
local files = {}
|
||||
local packagePath = "packages/" .. config.packageId
|
||||
|
||||
-- Generate seed/metadata.json
|
||||
table.insert(files, {
|
||||
path = "seed/metadata.json",
|
||||
content = templates.generate_metadata(config)
|
||||
})
|
||||
|
||||
-- Generate seed/components.json
|
||||
table.insert(files, {
|
||||
path = "seed/components.json",
|
||||
content = templates.generate_components_json(config)
|
||||
})
|
||||
|
||||
-- Generate seed/layout.json
|
||||
table.insert(files, {
|
||||
path = "seed/layout.json",
|
||||
content = templates.generate_layout_json(config)
|
||||
})
|
||||
|
||||
-- Generate seed/scripts/init.lua
|
||||
table.insert(files, {
|
||||
path = "seed/scripts/init.lua",
|
||||
content = templates.generate_init_lua(config)
|
||||
})
|
||||
|
||||
-- Generate schema if requested
|
||||
if config.withSchema and config.entities and #config.entities > 0 then
|
||||
table.insert(files, {
|
||||
path = "seed/schema/entities.yaml",
|
||||
content = templates.generate_schema_yaml(config)
|
||||
})
|
||||
|
||||
-- Generate db operations stub
|
||||
table.insert(files, {
|
||||
path = "seed/scripts/db/operations.lua",
|
||||
content = M.generate_db_operations(config)
|
||||
})
|
||||
end
|
||||
|
||||
-- Generate tests if requested
|
||||
if config.withTests then
|
||||
table.insert(files, {
|
||||
path = "seed/scripts/tests/metadata.test.lua",
|
||||
content = templates.generate_test("metadata", config)
|
||||
})
|
||||
|
||||
table.insert(files, {
|
||||
path = "seed/scripts/tests/components.test.lua",
|
||||
content = templates.generate_test("components", config)
|
||||
})
|
||||
|
||||
table.insert(files, {
|
||||
path = "seed/scripts/tests/metadata.cases.json",
|
||||
content = templates.generate_test_cases("metadata")
|
||||
})
|
||||
|
||||
table.insert(files, {
|
||||
path = "seed/scripts/tests/components.cases.json",
|
||||
content = templates.generate_test_cases("components")
|
||||
})
|
||||
end
|
||||
|
||||
-- Generate static content
|
||||
table.insert(files, {
|
||||
path = "static_content/icon.svg",
|
||||
content = templates.generate_icon_svg(config)
|
||||
})
|
||||
|
||||
-- Generate README
|
||||
table.insert(files, {
|
||||
path = "README.md",
|
||||
content = templates.generate_readme(config)
|
||||
})
|
||||
|
||||
-- Generate index.ts for TypeScript exports
|
||||
table.insert(files, {
|
||||
path = "seed/index.ts",
|
||||
content = M.generate_index_ts(config)
|
||||
})
|
||||
|
||||
return {
|
||||
success = true,
|
||||
files = files,
|
||||
errors = {},
|
||||
packagePath = packagePath
|
||||
}
|
||||
end
|
||||
|
||||
---Generate db operations Lua file
|
||||
---@param config PackageConfig
|
||||
---@return string
|
||||
function M.generate_db_operations(config)
|
||||
local lines = {
|
||||
"-- Database operations for " .. config.name,
|
||||
"-- Auto-generated by package template generator",
|
||||
"",
|
||||
"local M = {}",
|
||||
""
|
||||
}
|
||||
|
||||
if config.entities then
|
||||
for _, entity in ipairs(config.entities) do
|
||||
local entityLower = string.lower(entity)
|
||||
|
||||
table.insert(lines, "-- " .. entity .. " operations")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- List
|
||||
table.insert(lines, "---List all " .. entity .. " records")
|
||||
table.insert(lines, "---@param ctx DBALContext")
|
||||
table.insert(lines, "---@return table[]")
|
||||
table.insert(lines, "function M.list_" .. entityLower .. "(ctx)")
|
||||
table.insert(lines, " -- TODO: Implement list operation")
|
||||
table.insert(lines, " return {}")
|
||||
table.insert(lines, "end")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Get
|
||||
table.insert(lines, "---Get a single " .. entity .. " by ID")
|
||||
table.insert(lines, "---@param ctx DBALContext")
|
||||
table.insert(lines, "---@param id string")
|
||||
table.insert(lines, "---@return table|nil")
|
||||
table.insert(lines, "function M.get_" .. entityLower .. "(ctx, id)")
|
||||
table.insert(lines, " -- TODO: Implement get operation")
|
||||
table.insert(lines, " return nil")
|
||||
table.insert(lines, "end")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Create
|
||||
table.insert(lines, "---Create a new " .. entity)
|
||||
table.insert(lines, "---@param ctx DBALContext")
|
||||
table.insert(lines, "---@param data table")
|
||||
table.insert(lines, "---@return table")
|
||||
table.insert(lines, "function M.create_" .. entityLower .. "(ctx, data)")
|
||||
table.insert(lines, " -- TODO: Implement create operation")
|
||||
table.insert(lines, " return data")
|
||||
table.insert(lines, "end")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Update
|
||||
table.insert(lines, "---Update an existing " .. entity)
|
||||
table.insert(lines, "---@param ctx DBALContext")
|
||||
table.insert(lines, "---@param id string")
|
||||
table.insert(lines, "---@param data table")
|
||||
table.insert(lines, "---@return table|nil")
|
||||
table.insert(lines, "function M.update_" .. entityLower .. "(ctx, id, data)")
|
||||
table.insert(lines, " -- TODO: Implement update operation")
|
||||
table.insert(lines, " return nil")
|
||||
table.insert(lines, "end")
|
||||
table.insert(lines, "")
|
||||
|
||||
-- Delete
|
||||
table.insert(lines, "---Delete a " .. entity)
|
||||
table.insert(lines, "---@param ctx DBALContext")
|
||||
table.insert(lines, "---@param id string")
|
||||
table.insert(lines, "---@return boolean")
|
||||
table.insert(lines, "function M.delete_" .. entityLower .. "(ctx, id)")
|
||||
table.insert(lines, " -- TODO: Implement delete operation")
|
||||
table.insert(lines, " return false")
|
||||
table.insert(lines, "end")
|
||||
table.insert(lines, "")
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(lines, "return M")
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---Generate TypeScript index file
|
||||
---@param config PackageConfig
|
||||
---@return string
|
||||
function M.generate_index_ts(config)
|
||||
local lines = {
|
||||
"// " .. config.name .. " package exports",
|
||||
"// Auto-generated by package template generator",
|
||||
"",
|
||||
"import metadata from './metadata.json'",
|
||||
"import components from './components.json'",
|
||||
"import layout from './layout.json'",
|
||||
"",
|
||||
"export const packageSeed = {",
|
||||
" metadata,",
|
||||
" components,",
|
||||
" layout,",
|
||||
"}",
|
||||
"",
|
||||
"export default packageSeed",
|
||||
""
|
||||
}
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---Generate package to file system (would need file I/O)
|
||||
---@param config PackageConfig
|
||||
---@param basePath string Base path for packages directory
|
||||
---@return GenerateResult
|
||||
function M.generate_to_disk(config, basePath)
|
||||
local result = M.generate(config)
|
||||
|
||||
if not result.success then
|
||||
return result
|
||||
end
|
||||
|
||||
-- In actual implementation, this would write files to disk
|
||||
-- For now, return the generated structure
|
||||
result.packagePath = basePath .. "/" .. config.packageId
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,49 @@
|
||||
-- Package Template Generator
|
||||
-- Generates new MetaBuilder packages with proper structure
|
||||
-- @module package_template
|
||||
|
||||
local M = {}
|
||||
|
||||
local templates = require("package_template.templates")
|
||||
local generator = require("package_template.generator")
|
||||
|
||||
---@class PackageConfig
|
||||
---@field packageId string Package identifier (lowercase with underscores)
|
||||
---@field name string Display name
|
||||
---@field description string Package description
|
||||
---@field author? string Author name (default: "MetaBuilder")
|
||||
---@field category string Package category
|
||||
---@field minLevel number Minimum access level (0-6)
|
||||
---@field primary boolean Whether package can own routes
|
||||
---@field withSchema boolean Include database schema
|
||||
---@field withTests boolean Include test scaffolding
|
||||
---@field withComponents boolean Include component scaffolding
|
||||
---@field entities? string[] Entity names for schema (if withSchema)
|
||||
---@field components? string[] Component names to scaffold
|
||||
---@field dependencies? string[] Package dependencies
|
||||
---@field permissions? table<string, PermissionConfig> Permission declarations
|
||||
|
||||
---@class PermissionConfig
|
||||
---@field minLevel number Minimum level required
|
||||
---@field description string Permission description
|
||||
|
||||
---@class GenerateResult
|
||||
---@field success boolean Whether generation succeeded
|
||||
---@field files GeneratedFile[] List of generated files
|
||||
---@field errors string[] Any errors encountered
|
||||
---@field packagePath string Root path of generated package
|
||||
|
||||
---@class GeneratedFile
|
||||
---@field path string Relative path within package
|
||||
---@field content string File content
|
||||
|
||||
-- Re-export main functions
|
||||
M.generate = generator.generate
|
||||
M.generate_metadata = templates.generate_metadata
|
||||
M.generate_component = templates.generate_component
|
||||
M.generate_test = templates.generate_test
|
||||
M.validate_config = generator.validate_config
|
||||
M.get_template_categories = templates.get_categories
|
||||
M.get_default_config = generator.get_default_config
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,464 @@
|
||||
-- Package template definitions
|
||||
-- @module package_template.templates
|
||||
|
||||
local M = {}
|
||||
|
||||
---@return string[] Available package categories
|
||||
function M.get_categories()
|
||||
return {
|
||||
"ui",
|
||||
"editors",
|
||||
"tools",
|
||||
"social",
|
||||
"media",
|
||||
"gaming",
|
||||
"admin",
|
||||
"config",
|
||||
"core",
|
||||
"demo",
|
||||
"development",
|
||||
"managers"
|
||||
}
|
||||
end
|
||||
|
||||
---Generate metadata.json content
|
||||
---@param config PackageConfig
|
||||
---@return string JSON content
|
||||
function M.generate_metadata(config)
|
||||
local metadata = {
|
||||
packageId = config.packageId,
|
||||
name = config.name,
|
||||
version = "1.0.0",
|
||||
description = config.description,
|
||||
icon = "static_content/icon.svg",
|
||||
author = config.author or "MetaBuilder",
|
||||
category = config.category,
|
||||
primary = config.primary,
|
||||
dependencies = config.dependencies or {},
|
||||
devDependencies = { "lua_test" },
|
||||
exports = {
|
||||
components = config.components or {},
|
||||
scripts = { "init" }
|
||||
},
|
||||
tests = {
|
||||
scripts = { "tests/metadata.test.lua", "tests/components.test.lua" },
|
||||
cases = { "tests/metadata.cases.json", "tests/components.cases.json" }
|
||||
},
|
||||
minLevel = config.minLevel
|
||||
}
|
||||
|
||||
-- Add schema if requested
|
||||
if config.withSchema and config.entities and #config.entities > 0 then
|
||||
metadata.schema = {
|
||||
entities = config.entities,
|
||||
path = "schema/entities.yaml"
|
||||
}
|
||||
end
|
||||
|
||||
-- Add permissions
|
||||
if config.permissions then
|
||||
metadata.permissions = {}
|
||||
for permKey, permConfig in pairs(config.permissions) do
|
||||
metadata.permissions[permKey] = {
|
||||
minLevel = permConfig.minLevel,
|
||||
description = permConfig.description
|
||||
}
|
||||
end
|
||||
else
|
||||
-- Generate default permissions based on package type
|
||||
metadata.permissions = M.generate_default_permissions(config)
|
||||
end
|
||||
|
||||
return M.to_json(metadata)
|
||||
end
|
||||
|
||||
---Generate default permissions based on package config
|
||||
---@param config PackageConfig
|
||||
---@return table<string, table>
|
||||
function M.generate_default_permissions(config)
|
||||
local prefix = string.gsub(config.packageId, "_", ".")
|
||||
local permissions = {}
|
||||
|
||||
-- Basic view permission
|
||||
permissions[prefix .. ".view"] = {
|
||||
minLevel = config.minLevel,
|
||||
description = "View " .. config.name
|
||||
}
|
||||
|
||||
-- If primary, add more permissions
|
||||
if config.primary then
|
||||
permissions[prefix .. ".edit"] = {
|
||||
minLevel = config.minLevel,
|
||||
description = "Edit " .. config.name .. " content"
|
||||
}
|
||||
end
|
||||
|
||||
-- If has schema, add CRUD permissions
|
||||
if config.withSchema and config.entities then
|
||||
for _, entity in ipairs(config.entities) do
|
||||
local entityLower = string.lower(entity)
|
||||
permissions[prefix .. "." .. entityLower .. ".create"] = {
|
||||
minLevel = config.minLevel,
|
||||
description = "Create " .. entity
|
||||
}
|
||||
permissions[prefix .. "." .. entityLower .. ".update"] = {
|
||||
minLevel = config.minLevel,
|
||||
description = "Update " .. entity
|
||||
}
|
||||
permissions[prefix .. "." .. entityLower .. ".delete"] = {
|
||||
minLevel = math.min(config.minLevel + 1, 6),
|
||||
description = "Delete " .. entity
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return permissions
|
||||
end
|
||||
|
||||
---Generate init.lua content
|
||||
---@param config PackageConfig
|
||||
---@return string Lua content
|
||||
function M.generate_init_lua(config)
|
||||
local lines = {
|
||||
"--- " .. config.name .. " initialization",
|
||||
"--- @module init",
|
||||
"",
|
||||
"local M = {}",
|
||||
"",
|
||||
"---@class InstallContext",
|
||||
"---@field version string",
|
||||
"",
|
||||
"---@class InstallResult",
|
||||
"---@field message string",
|
||||
"---@field version string",
|
||||
"",
|
||||
"---Called when package is installed",
|
||||
"---@param context InstallContext",
|
||||
"---@return InstallResult",
|
||||
"function M.on_install(context)",
|
||||
" return {",
|
||||
" message = \"" .. config.name .. " installed successfully\",",
|
||||
" version = context.version",
|
||||
" }",
|
||||
"end",
|
||||
"",
|
||||
"---Called when package is uninstalled",
|
||||
"---@return table",
|
||||
"function M.on_uninstall()",
|
||||
" return { message = \"" .. config.name .. " removed\" }",
|
||||
"end",
|
||||
"",
|
||||
"return M"
|
||||
}
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---Generate component.json entry
|
||||
---@param componentName string
|
||||
---@param config PackageConfig
|
||||
---@return table Component definition
|
||||
function M.generate_component(componentName, config)
|
||||
local componentId = string.lower(config.packageId .. "_" .. componentName)
|
||||
|
||||
return {
|
||||
id = componentId,
|
||||
type = "container",
|
||||
name = componentName,
|
||||
description = componentName .. " component for " .. config.name,
|
||||
props = {},
|
||||
layout = {
|
||||
type = "flex",
|
||||
props = {
|
||||
direction = "column",
|
||||
gap = 2
|
||||
}
|
||||
},
|
||||
bindings = {}
|
||||
}
|
||||
end
|
||||
|
||||
---Generate components.json content
|
||||
---@param config PackageConfig
|
||||
---@return string JSON content
|
||||
function M.generate_components_json(config)
|
||||
local components = {}
|
||||
|
||||
if config.components then
|
||||
for _, name in ipairs(config.components) do
|
||||
table.insert(components, M.generate_component(name, config))
|
||||
end
|
||||
end
|
||||
|
||||
return M.to_json(components)
|
||||
end
|
||||
|
||||
---Generate test file content
|
||||
---@param testName string Test name
|
||||
---@param config PackageConfig
|
||||
---@return string Lua test content
|
||||
function M.generate_test(testName, config)
|
||||
local lines = {
|
||||
"-- " .. testName .. " tests for " .. config.packageId,
|
||||
"",
|
||||
"describe(\"" .. config.name .. " - " .. testName .. "\", function()",
|
||||
" it(\"should pass basic validation\", function()",
|
||||
" expect(true).toBe(true)",
|
||||
" end)",
|
||||
"",
|
||||
" it(\"should have required fields\", function()",
|
||||
" -- TODO: Add specific tests",
|
||||
" expect(true).toBe(true)",
|
||||
" end)",
|
||||
"end)",
|
||||
""
|
||||
}
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---Generate test cases JSON
|
||||
---@param testName string
|
||||
---@return string JSON content
|
||||
function M.generate_test_cases(testName)
|
||||
local cases = {
|
||||
{
|
||||
name = "valid_input",
|
||||
input = {},
|
||||
expected = { valid = true }
|
||||
},
|
||||
{
|
||||
name = "invalid_input",
|
||||
input = { invalid = true },
|
||||
expected = { valid = false }
|
||||
}
|
||||
}
|
||||
return M.to_json(cases)
|
||||
end
|
||||
|
||||
---Generate schema YAML content
|
||||
---@param config PackageConfig
|
||||
---@return string YAML content
|
||||
function M.generate_schema_yaml(config)
|
||||
if not config.entities or #config.entities == 0 then
|
||||
return "# No entities defined\n"
|
||||
end
|
||||
|
||||
local lines = {
|
||||
"# " .. config.name .. " Entity Definitions",
|
||||
"# Auto-generated by package template generator",
|
||||
""
|
||||
}
|
||||
|
||||
for _, entity in ipairs(config.entities) do
|
||||
local prefixedEntity = "Pkg_" .. M.to_pascal_case(config.packageId) .. "_" .. entity
|
||||
table.insert(lines, prefixedEntity .. ":")
|
||||
table.insert(lines, " description: \"" .. entity .. " entity for " .. config.name .. "\"")
|
||||
table.insert(lines, " fields:")
|
||||
table.insert(lines, " id:")
|
||||
table.insert(lines, " type: string")
|
||||
table.insert(lines, " primary: true")
|
||||
table.insert(lines, " tenantId:")
|
||||
table.insert(lines, " type: string")
|
||||
table.insert(lines, " required: true")
|
||||
table.insert(lines, " index: true")
|
||||
table.insert(lines, " createdAt:")
|
||||
table.insert(lines, " type: datetime")
|
||||
table.insert(lines, " default: now")
|
||||
table.insert(lines, " updatedAt:")
|
||||
table.insert(lines, " type: datetime")
|
||||
table.insert(lines, " onUpdate: now")
|
||||
table.insert(lines, " # TODO: Add entity-specific fields")
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---Generate layout.json content
|
||||
---@param config PackageConfig
|
||||
---@return string JSON content
|
||||
function M.generate_layout_json(config)
|
||||
local layout = {
|
||||
id = config.packageId .. "_layout",
|
||||
name = config.name .. " Layout",
|
||||
type = "page",
|
||||
props = {
|
||||
title = config.name,
|
||||
minLevel = config.minLevel
|
||||
},
|
||||
children = {}
|
||||
}
|
||||
|
||||
-- Add header section
|
||||
table.insert(layout.children, {
|
||||
id = config.packageId .. "_header",
|
||||
type = "container",
|
||||
props = { variant = "header" },
|
||||
children = {
|
||||
{
|
||||
id = config.packageId .. "_title",
|
||||
type = "text",
|
||||
props = { variant = "h1", content = config.name }
|
||||
},
|
||||
{
|
||||
id = config.packageId .. "_description",
|
||||
type = "text",
|
||||
props = { variant = "body1", content = config.description }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
-- Add main content section
|
||||
table.insert(layout.children, {
|
||||
id = config.packageId .. "_content",
|
||||
type = "container",
|
||||
props = { variant = "main" },
|
||||
children = {
|
||||
{
|
||||
id = config.packageId .. "_placeholder",
|
||||
type = "text",
|
||||
props = { content = "Add your components here" }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return M.to_json(layout)
|
||||
end
|
||||
|
||||
---Generate icon SVG
|
||||
---@param config PackageConfig
|
||||
---@return string SVG content
|
||||
function M.generate_icon_svg(config)
|
||||
-- Get first letter of package name for icon
|
||||
local letter = string.upper(string.sub(config.name, 1, 1))
|
||||
|
||||
return [[<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||
<text x="12" y="16" text-anchor="middle" font-size="12" fill="currentColor" stroke="none">]] .. letter .. [[</text>
|
||||
</svg>]]
|
||||
end
|
||||
|
||||
---Generate README.md content
|
||||
---@param config PackageConfig
|
||||
---@return string Markdown content
|
||||
function M.generate_readme(config)
|
||||
local lines = {
|
||||
"# " .. config.name,
|
||||
"",
|
||||
config.description,
|
||||
"",
|
||||
"## Installation",
|
||||
"",
|
||||
"This package is part of the MetaBuilder platform and is installed automatically.",
|
||||
"",
|
||||
"## Access Level",
|
||||
"",
|
||||
"Minimum level required: **" .. config.minLevel .. "**",
|
||||
"",
|
||||
}
|
||||
|
||||
if config.primary then
|
||||
table.insert(lines, "This is a **primary package** that can own routes.")
|
||||
else
|
||||
table.insert(lines, "This is a **dependency package** that provides shared functionality.")
|
||||
end
|
||||
table.insert(lines, "")
|
||||
|
||||
if config.withSchema and config.entities and #config.entities > 0 then
|
||||
table.insert(lines, "## Entities")
|
||||
table.insert(lines, "")
|
||||
for _, entity in ipairs(config.entities) do
|
||||
table.insert(lines, "- " .. entity)
|
||||
end
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
if config.components and #config.components > 0 then
|
||||
table.insert(lines, "## Components")
|
||||
table.insert(lines, "")
|
||||
for _, comp in ipairs(config.components) do
|
||||
table.insert(lines, "- `" .. comp .. "`")
|
||||
end
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
table.insert(lines, "## Development")
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "```bash")
|
||||
table.insert(lines, "# Run tests")
|
||||
table.insert(lines, "npm run test:package " .. config.packageId)
|
||||
table.insert(lines, "```")
|
||||
table.insert(lines, "")
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---Convert table to JSON string (simple implementation)
|
||||
---@param tbl table
|
||||
---@return string
|
||||
function M.to_json(tbl)
|
||||
-- Use the global json if available, otherwise simple stringify
|
||||
if json and json.encode then
|
||||
return json.encode(tbl)
|
||||
end
|
||||
|
||||
-- Simple fallback - in real usage would use proper JSON library
|
||||
return M.stringify(tbl, 0)
|
||||
end
|
||||
|
||||
---Simple table to JSON string converter
|
||||
---@param val any
|
||||
---@param indent number
|
||||
---@return string
|
||||
function M.stringify(val, indent)
|
||||
local t = type(val)
|
||||
|
||||
if t == "nil" then
|
||||
return "null"
|
||||
elseif t == "boolean" then
|
||||
return val and "true" or "false"
|
||||
elseif t == "number" then
|
||||
return tostring(val)
|
||||
elseif t == "string" then
|
||||
return '"' .. val:gsub('"', '\\"'):gsub('\n', '\\n') .. '"'
|
||||
elseif t == "table" then
|
||||
local spaces = string.rep(" ", indent)
|
||||
local nextSpaces = string.rep(" ", indent + 1)
|
||||
|
||||
-- Check if array
|
||||
local isArray = #val > 0 or next(val) == nil
|
||||
if isArray then
|
||||
local items = {}
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(items, nextSpaces .. M.stringify(v, indent + 1))
|
||||
end
|
||||
if #items == 0 then
|
||||
return "[]"
|
||||
end
|
||||
return "[\n" .. table.concat(items, ",\n") .. "\n" .. spaces .. "]"
|
||||
else
|
||||
local items = {}
|
||||
for k, v in pairs(val) do
|
||||
table.insert(items, nextSpaces .. '"' .. tostring(k) .. '": ' .. M.stringify(v, indent + 1))
|
||||
end
|
||||
if #items == 0 then
|
||||
return "{}"
|
||||
end
|
||||
return "{\n" .. table.concat(items, ",\n") .. "\n" .. spaces .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
return "null"
|
||||
end
|
||||
|
||||
---Convert string to PascalCase
|
||||
---@param str string
|
||||
---@return string
|
||||
function M.to_pascal_case(str)
|
||||
local result = ""
|
||||
for word in string.gmatch(str, "[^_]+") do
|
||||
result = result .. string.upper(string.sub(word, 1, 1)) .. string.sub(word, 2)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,110 @@
|
||||
[
|
||||
{
|
||||
"name": "valid_basic_config",
|
||||
"input": {
|
||||
"packageId": "test_package",
|
||||
"name": "Test Package",
|
||||
"description": "A test package",
|
||||
"category": "ui",
|
||||
"minLevel": 2,
|
||||
"primary": true,
|
||||
"withSchema": false,
|
||||
"withTests": true
|
||||
},
|
||||
"expected": {
|
||||
"valid": true,
|
||||
"errorCount": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "valid_with_schema",
|
||||
"input": {
|
||||
"packageId": "data_package",
|
||||
"name": "Data Package",
|
||||
"description": "Package with schema",
|
||||
"category": "tools",
|
||||
"minLevel": 3,
|
||||
"primary": true,
|
||||
"withSchema": true,
|
||||
"entities": ["DataRecord", "DataLog"]
|
||||
},
|
||||
"expected": {
|
||||
"valid": true,
|
||||
"errorCount": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "invalid_package_id_uppercase",
|
||||
"input": {
|
||||
"packageId": "TestPackage",
|
||||
"name": "Test",
|
||||
"description": "Test",
|
||||
"category": "ui",
|
||||
"minLevel": 2,
|
||||
"primary": true
|
||||
},
|
||||
"expected": {
|
||||
"valid": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "invalid_category",
|
||||
"input": {
|
||||
"packageId": "test_package",
|
||||
"name": "Test",
|
||||
"description": "Test",
|
||||
"category": "not_a_category",
|
||||
"minLevel": 2,
|
||||
"primary": true
|
||||
},
|
||||
"expected": {
|
||||
"valid": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "invalid_min_level_too_high",
|
||||
"input": {
|
||||
"packageId": "test_package",
|
||||
"name": "Test",
|
||||
"description": "Test",
|
||||
"category": "ui",
|
||||
"minLevel": 10,
|
||||
"primary": true
|
||||
},
|
||||
"expected": {
|
||||
"valid": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "missing_entities_for_schema",
|
||||
"input": {
|
||||
"packageId": "test_package",
|
||||
"name": "Test",
|
||||
"description": "Test",
|
||||
"category": "ui",
|
||||
"minLevel": 2,
|
||||
"primary": true,
|
||||
"withSchema": true,
|
||||
"entities": []
|
||||
},
|
||||
"expected": {
|
||||
"valid": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dependency_package",
|
||||
"input": {
|
||||
"packageId": "shared_utils",
|
||||
"name": "Shared Utils",
|
||||
"description": "Shared utilities",
|
||||
"category": "core",
|
||||
"minLevel": 1,
|
||||
"primary": false,
|
||||
"withSchema": false
|
||||
},
|
||||
"expected": {
|
||||
"valid": true,
|
||||
"errorCount": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,217 @@
|
||||
-- Package template generator tests
|
||||
local package_template = require("package_template")
|
||||
|
||||
describe("Package Template Generator", function()
|
||||
|
||||
describe("get_default_config", function()
|
||||
it("should generate default config from package_id", function()
|
||||
local config = package_template.get_default_config("my_test_package")
|
||||
|
||||
expect(config.packageId).toBe("my_test_package")
|
||||
expect(config.name).toBe("My Test Package")
|
||||
expect(config.primary).toBe(true)
|
||||
expect(config.minLevel).toBe(2)
|
||||
expect(config.category).toBe("ui")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("validate_config", function()
|
||||
it("should pass for valid config", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = false,
|
||||
withTests = true,
|
||||
withComponents = false
|
||||
}
|
||||
|
||||
local valid, errors = package_template.validate_config(config)
|
||||
expect(valid).toBe(true)
|
||||
expect(#errors).toBe(0)
|
||||
end)
|
||||
|
||||
it("should fail for invalid packageId", function()
|
||||
local config = {
|
||||
packageId = "TestPackage", -- Should be lowercase
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true
|
||||
}
|
||||
|
||||
local valid, errors = package_template.validate_config(config)
|
||||
expect(valid).toBe(false)
|
||||
end)
|
||||
|
||||
it("should fail for invalid category", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "invalid_category",
|
||||
minLevel = 2,
|
||||
primary = true
|
||||
}
|
||||
|
||||
local valid, errors = package_template.validate_config(config)
|
||||
expect(valid).toBe(false)
|
||||
end)
|
||||
|
||||
it("should require entities when withSchema is true", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = true,
|
||||
entities = {} -- Empty
|
||||
}
|
||||
|
||||
local valid, errors = package_template.validate_config(config)
|
||||
expect(valid).toBe(false)
|
||||
end)
|
||||
|
||||
it("should validate entity names are PascalCase", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = true,
|
||||
entities = { "invalid_entity" } -- Should be PascalCase
|
||||
}
|
||||
|
||||
local valid, errors = package_template.validate_config(config)
|
||||
expect(valid).toBe(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("generate", function()
|
||||
it("should generate all required files", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = false,
|
||||
withTests = true,
|
||||
withComponents = false
|
||||
}
|
||||
|
||||
local result = package_template.generate(config)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(#result.files).toBeGreaterThan(0)
|
||||
expect(result.packagePath).toBe("packages/test_package")
|
||||
end)
|
||||
|
||||
it("should include schema files when withSchema is true", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true,
|
||||
withSchema = true,
|
||||
withTests = true,
|
||||
entities = { "TestEntity" }
|
||||
}
|
||||
|
||||
local result = package_template.generate(config)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
-- Check for schema file
|
||||
local hasSchema = false
|
||||
for _, file in ipairs(result.files) do
|
||||
if string.match(file.path, "schema/entities.yaml") then
|
||||
hasSchema = true
|
||||
break
|
||||
end
|
||||
end
|
||||
expect(hasSchema).toBe(true)
|
||||
end)
|
||||
|
||||
it("should fail for invalid config", function()
|
||||
local config = {
|
||||
packageId = "InvalidPackage", -- Invalid
|
||||
name = "Test",
|
||||
description = "Test",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true
|
||||
}
|
||||
|
||||
local result = package_template.generate(config)
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(#result.errors).toBeGreaterThan(0)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("generate_metadata", function()
|
||||
it("should generate valid JSON", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true
|
||||
}
|
||||
|
||||
local json = package_template.generate_metadata(config)
|
||||
|
||||
expect(type(json)).toBe("string")
|
||||
expect(string.match(json, '"packageId"')).toBeTruthy()
|
||||
expect(string.match(json, '"test_package"')).toBeTruthy()
|
||||
end)
|
||||
|
||||
it("should include permissions", function()
|
||||
local config = {
|
||||
packageId = "test_package",
|
||||
name = "Test Package",
|
||||
description = "A test package",
|
||||
category = "ui",
|
||||
minLevel = 2,
|
||||
primary = true
|
||||
}
|
||||
|
||||
local json = package_template.generate_metadata(config)
|
||||
|
||||
expect(string.match(json, '"permissions"')).toBeTruthy()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("get_template_categories", function()
|
||||
it("should return list of categories", function()
|
||||
local categories = package_template.get_template_categories()
|
||||
|
||||
expect(type(categories)).toBe("table")
|
||||
expect(#categories).toBeGreaterThan(0)
|
||||
|
||||
-- Check for some expected categories
|
||||
local hasUI = false
|
||||
local hasTools = false
|
||||
for _, cat in ipairs(categories) do
|
||||
if cat == "ui" then hasUI = true end
|
||||
if cat == "tools" then hasTools = true end
|
||||
end
|
||||
expect(hasUI).toBe(true)
|
||||
expect(hasTools).toBe(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
end)
|
||||
Reference in New Issue
Block a user