Files
2025-12-30 23:27:30 +00:00

336 lines
9.8 KiB
Lua

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