mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
code: scripts,package,generate (1 files)
This commit is contained in:
583
scripts/generate-package.ts
Normal file
583
scripts/generate-package.ts
Normal file
@@ -0,0 +1,583 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Package Template Generator CLI
|
||||
*
|
||||
* Usage:
|
||||
* npx ts-node scripts/generate-package.ts <package_id> [options]
|
||||
*
|
||||
* Examples:
|
||||
* npx ts-node scripts/generate-package.ts my_package
|
||||
* npx ts-node scripts/generate-package.ts my_forum --with-schema --entities ForumThread,ForumPost
|
||||
* npx ts-node scripts/generate-package.ts my_widget --dependency --category ui
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
interface PackageConfig {
|
||||
packageId: string
|
||||
name: string
|
||||
description: string
|
||||
author: string
|
||||
category: string
|
||||
minLevel: number
|
||||
primary: boolean
|
||||
withSchema: boolean
|
||||
withTests: boolean
|
||||
withComponents: boolean
|
||||
entities: string[]
|
||||
components: string[]
|
||||
dependencies: string[]
|
||||
}
|
||||
|
||||
interface GeneratedFile {
|
||||
path: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const CATEGORIES = [
|
||||
'ui', 'editors', 'tools', 'social', 'media', 'gaming',
|
||||
'admin', 'config', 'core', 'demo', 'development', 'managers'
|
||||
]
|
||||
|
||||
function parseArgs(args: string[]): { packageId: string; options: Partial<PackageConfig> } {
|
||||
const options: Partial<PackageConfig> = {
|
||||
category: 'ui',
|
||||
minLevel: 2,
|
||||
primary: true,
|
||||
withSchema: false,
|
||||
withTests: true,
|
||||
withComponents: false,
|
||||
entities: [],
|
||||
components: [],
|
||||
dependencies: []
|
||||
}
|
||||
|
||||
let packageId = ''
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
|
||||
if (!arg.startsWith('--') && !packageId) {
|
||||
packageId = arg
|
||||
} else if (arg === '--name') {
|
||||
options.name = args[++i]
|
||||
} else if (arg === '--description') {
|
||||
options.description = args[++i]
|
||||
} else if (arg === '--category') {
|
||||
options.category = args[++i]
|
||||
} else if (arg === '--min-level') {
|
||||
options.minLevel = parseInt(args[++i], 10)
|
||||
} else if (arg === '--primary') {
|
||||
options.primary = true
|
||||
} else if (arg === '--dependency') {
|
||||
options.primary = false
|
||||
} else if (arg === '--with-schema') {
|
||||
options.withSchema = true
|
||||
} else if (arg === '--entities') {
|
||||
options.entities = args[++i].split(',').map(e => e.trim())
|
||||
} else if (arg === '--with-components') {
|
||||
options.withComponents = true
|
||||
} else if (arg === '--components') {
|
||||
options.components = args[++i].split(',').map(c => c.trim())
|
||||
} else if (arg === '--deps') {
|
||||
options.dependencies = args[++i].split(',').map(d => d.trim())
|
||||
}
|
||||
}
|
||||
|
||||
return { packageId, options }
|
||||
}
|
||||
|
||||
function toPascalCase(str: string): string {
|
||||
return str.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join('')
|
||||
}
|
||||
|
||||
function toDisplayName(packageId: string): string {
|
||||
return packageId.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ')
|
||||
}
|
||||
|
||||
function generateMetadata(config: PackageConfig): string {
|
||||
const prefix = config.packageId.replace(/_/g, '.')
|
||||
|
||||
const permissions: Record<string, { minLevel: number; description: string }> = {
|
||||
[`${prefix}.view`]: {
|
||||
minLevel: config.minLevel,
|
||||
description: `View ${config.name}`
|
||||
}
|
||||
}
|
||||
|
||||
if (config.primary) {
|
||||
permissions[`${prefix}.edit`] = {
|
||||
minLevel: config.minLevel,
|
||||
description: `Edit ${config.name} content`
|
||||
}
|
||||
}
|
||||
|
||||
if (config.withSchema && config.entities.length > 0) {
|
||||
for (const entity of config.entities) {
|
||||
const entityLower = entity.toLowerCase()
|
||||
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}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
packageId: config.packageId,
|
||||
name: config.name,
|
||||
version: '1.0.0',
|
||||
description: config.description,
|
||||
icon: 'static_content/icon.svg',
|
||||
author: config.author,
|
||||
category: config.category,
|
||||
primary: config.primary,
|
||||
dependencies: config.dependencies,
|
||||
devDependencies: ['lua_test'],
|
||||
exports: {
|
||||
components: config.components,
|
||||
scripts: ['init']
|
||||
},
|
||||
tests: {
|
||||
scripts: ['tests/metadata.test.lua', 'tests/components.test.lua'],
|
||||
cases: ['tests/metadata.cases.json', 'tests/components.cases.json']
|
||||
},
|
||||
minLevel: config.minLevel,
|
||||
...(config.withSchema && config.entities.length > 0 ? {
|
||||
schema: {
|
||||
entities: config.entities,
|
||||
path: 'schema/entities.yaml'
|
||||
}
|
||||
} : {}),
|
||||
permissions
|
||||
}
|
||||
|
||||
return JSON.stringify(metadata, null, 2)
|
||||
}
|
||||
|
||||
function generateInitLua(config: PackageConfig): string {
|
||||
return `--- ${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
|
||||
`
|
||||
}
|
||||
|
||||
function generateTestLua(testName: string, config: PackageConfig): string {
|
||||
return `-- ${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)
|
||||
`
|
||||
}
|
||||
|
||||
function generateTestCases(): string {
|
||||
return JSON.stringify([
|
||||
{ name: 'valid_input', input: {}, expected: { valid: true } },
|
||||
{ name: 'invalid_input', input: { invalid: true }, expected: { valid: false } }
|
||||
], null, 2)
|
||||
}
|
||||
|
||||
function generateSchemaYaml(config: PackageConfig): string {
|
||||
if (!config.entities.length) return '# No entities defined\n'
|
||||
|
||||
const lines = [
|
||||
`# ${config.name} Entity Definitions`,
|
||||
'# Auto-generated by package template generator',
|
||||
''
|
||||
]
|
||||
|
||||
for (const entity of config.entities) {
|
||||
const prefixedEntity = `Pkg_${toPascalCase(config.packageId)}_${entity}`
|
||||
lines.push(`${prefixedEntity}:`)
|
||||
lines.push(` description: "${entity} entity for ${config.name}"`)
|
||||
lines.push(' fields:')
|
||||
lines.push(' id:')
|
||||
lines.push(' type: string')
|
||||
lines.push(' primary: true')
|
||||
lines.push(' tenantId:')
|
||||
lines.push(' type: string')
|
||||
lines.push(' required: true')
|
||||
lines.push(' index: true')
|
||||
lines.push(' createdAt:')
|
||||
lines.push(' type: datetime')
|
||||
lines.push(' default: now')
|
||||
lines.push(' updatedAt:')
|
||||
lines.push(' type: datetime')
|
||||
lines.push(' onUpdate: now')
|
||||
lines.push(' # TODO: Add entity-specific fields')
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function generateDbOperations(config: PackageConfig): string {
|
||||
const lines = [
|
||||
`-- Database operations for ${config.name}`,
|
||||
'-- Auto-generated by package template generator',
|
||||
'',
|
||||
'local M = {}',
|
||||
''
|
||||
]
|
||||
|
||||
for (const entity of config.entities) {
|
||||
const entityLower = entity.toLowerCase()
|
||||
|
||||
lines.push(`-- ${entity} operations`)
|
||||
lines.push('')
|
||||
lines.push(`---List all ${entity} records`)
|
||||
lines.push('---@param ctx DBALContext')
|
||||
lines.push('---@return table[]')
|
||||
lines.push(`function M.list_${entityLower}(ctx)`)
|
||||
lines.push(' -- TODO: Implement list operation')
|
||||
lines.push(' return {}')
|
||||
lines.push('end')
|
||||
lines.push('')
|
||||
lines.push(`---Get a single ${entity} by ID`)
|
||||
lines.push('---@param ctx DBALContext')
|
||||
lines.push('---@param id string')
|
||||
lines.push('---@return table|nil')
|
||||
lines.push(`function M.get_${entityLower}(ctx, id)`)
|
||||
lines.push(' -- TODO: Implement get operation')
|
||||
lines.push(' return nil')
|
||||
lines.push('end')
|
||||
lines.push('')
|
||||
lines.push(`---Create a new ${entity}`)
|
||||
lines.push('---@param ctx DBALContext')
|
||||
lines.push('---@param data table')
|
||||
lines.push('---@return table')
|
||||
lines.push(`function M.create_${entityLower}(ctx, data)`)
|
||||
lines.push(' -- TODO: Implement create operation')
|
||||
lines.push(' return data')
|
||||
lines.push('end')
|
||||
lines.push('')
|
||||
lines.push(`---Update an existing ${entity}`)
|
||||
lines.push('---@param ctx DBALContext')
|
||||
lines.push('---@param id string')
|
||||
lines.push('---@param data table')
|
||||
lines.push('---@return table|nil')
|
||||
lines.push(`function M.update_${entityLower}(ctx, id, data)`)
|
||||
lines.push(' -- TODO: Implement update operation')
|
||||
lines.push(' return nil')
|
||||
lines.push('end')
|
||||
lines.push('')
|
||||
lines.push(`---Delete a ${entity}`)
|
||||
lines.push('---@param ctx DBALContext')
|
||||
lines.push('---@param id string')
|
||||
lines.push('---@return boolean')
|
||||
lines.push(`function M.delete_${entityLower}(ctx, id)`)
|
||||
lines.push(' -- TODO: Implement delete operation')
|
||||
lines.push(' return false')
|
||||
lines.push('end')
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
lines.push('return M')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function generateComponentsJson(config: PackageConfig): string {
|
||||
const components = config.components.map(name => ({
|
||||
id: `${config.packageId}_${name.toLowerCase()}`,
|
||||
type: 'container',
|
||||
name,
|
||||
description: `${name} component for ${config.name}`,
|
||||
props: {},
|
||||
layout: { type: 'flex', props: { direction: 'column', gap: 2 } },
|
||||
bindings: {}
|
||||
}))
|
||||
return JSON.stringify(components, null, 2)
|
||||
}
|
||||
|
||||
function generateLayoutJson(config: PackageConfig): string {
|
||||
const layout = {
|
||||
id: `${config.packageId}_layout`,
|
||||
name: `${config.name} Layout`,
|
||||
type: 'page',
|
||||
props: { title: config.name, minLevel: config.minLevel },
|
||||
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 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: `${config.packageId}_content`,
|
||||
type: 'container',
|
||||
props: { variant: 'main' },
|
||||
children: [
|
||||
{ id: `${config.packageId}_placeholder`, type: 'text', props: { content: 'Add your components here' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
return JSON.stringify(layout, null, 2)
|
||||
}
|
||||
|
||||
function generateIconSvg(config: PackageConfig): string {
|
||||
const letter = config.name.charAt(0).toUpperCase()
|
||||
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>`
|
||||
}
|
||||
|
||||
function generateReadme(config: PackageConfig): string {
|
||||
const 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}**`,
|
||||
'',
|
||||
config.primary
|
||||
? 'This is a **primary package** that can own routes.'
|
||||
: 'This is a **dependency package** that provides shared functionality.',
|
||||
''
|
||||
]
|
||||
|
||||
if (config.withSchema && config.entities.length > 0) {
|
||||
lines.push('## Entities')
|
||||
lines.push('')
|
||||
for (const entity of config.entities) {
|
||||
lines.push(`- ${entity}`)
|
||||
}
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
if (config.components.length > 0) {
|
||||
lines.push('## Components')
|
||||
lines.push('')
|
||||
for (const comp of config.components) {
|
||||
lines.push(`- \`${comp}\``)
|
||||
}
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
lines.push('## Development')
|
||||
lines.push('')
|
||||
lines.push('```bash')
|
||||
lines.push('# Run tests')
|
||||
lines.push(`npm run test:package ${config.packageId}`)
|
||||
lines.push('```')
|
||||
lines.push('')
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function generateIndexTs(config: PackageConfig): string {
|
||||
return `// ${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
|
||||
`
|
||||
}
|
||||
|
||||
function generate(config: PackageConfig): GeneratedFile[] {
|
||||
const files: GeneratedFile[] = []
|
||||
|
||||
// Core files
|
||||
files.push({ path: 'seed/metadata.json', content: generateMetadata(config) })
|
||||
files.push({ path: 'seed/components.json', content: generateComponentsJson(config) })
|
||||
files.push({ path: 'seed/layout.json', content: generateLayoutJson(config) })
|
||||
files.push({ path: 'seed/scripts/init.lua', content: generateInitLua(config) })
|
||||
files.push({ path: 'seed/index.ts', content: generateIndexTs(config) })
|
||||
|
||||
// Schema files
|
||||
if (config.withSchema && config.entities.length > 0) {
|
||||
files.push({ path: 'seed/schema/entities.yaml', content: generateSchemaYaml(config) })
|
||||
files.push({ path: 'seed/scripts/db/operations.lua', content: generateDbOperations(config) })
|
||||
}
|
||||
|
||||
// Test files
|
||||
if (config.withTests) {
|
||||
files.push({ path: 'seed/scripts/tests/metadata.test.lua', content: generateTestLua('metadata', config) })
|
||||
files.push({ path: 'seed/scripts/tests/components.test.lua', content: generateTestLua('components', config) })
|
||||
files.push({ path: 'seed/scripts/tests/metadata.cases.json', content: generateTestCases() })
|
||||
files.push({ path: 'seed/scripts/tests/components.cases.json', content: generateTestCases() })
|
||||
}
|
||||
|
||||
// Static content
|
||||
files.push({ path: 'static_content/icon.svg', content: generateIconSvg(config) })
|
||||
files.push({ path: 'README.md', content: generateReadme(config) })
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
function writeFiles(packagePath: string, files: GeneratedFile[]): void {
|
||||
for (const file of files) {
|
||||
const fullPath = path.join(packagePath, file.path)
|
||||
const dir = path.dirname(fullPath)
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
|
||||
fs.writeFileSync(fullPath, file.content)
|
||||
console.log(` Created: ${file.path}`)
|
||||
}
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`
|
||||
Package Template Generator
|
||||
==========================
|
||||
|
||||
Usage: npx ts-node scripts/generate-package.ts <package_id> [options]
|
||||
|
||||
Options:
|
||||
--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, PascalCase)
|
||||
--with-components Include component scaffolding
|
||||
--components <c1,c2> Component names (comma-separated, PascalCase)
|
||||
--deps <d1,d2> Package dependencies (comma-separated)
|
||||
|
||||
Categories: ${CATEGORIES.join(', ')}
|
||||
|
||||
Examples:
|
||||
npx ts-node scripts/generate-package.ts my_package
|
||||
npx ts-node scripts/generate-package.ts my_forum --with-schema --entities ForumThread,ForumPost
|
||||
npx ts-node scripts/generate-package.ts my_widget --dependency --category ui
|
||||
npx ts-node scripts/generate-package.ts my_dashboard --with-components --components StatCard,Chart
|
||||
`)
|
||||
}
|
||||
|
||||
// Main
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
||||
printHelp()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const { packageId, options } = parseArgs(args)
|
||||
|
||||
if (!packageId) {
|
||||
console.error('Error: package_id is required')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!/^[a-z][a-z0-9_]*$/.test(packageId)) {
|
||||
console.error('Error: package_id must be lowercase with underscores, starting with a letter')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (options.category && !CATEGORIES.includes(options.category)) {
|
||||
console.error(`Error: Invalid category. Must be one of: ${CATEGORIES.join(', ')}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const config: PackageConfig = {
|
||||
packageId,
|
||||
name: options.name || toDisplayName(packageId),
|
||||
description: options.description || `${toDisplayName(packageId)} package for MetaBuilder`,
|
||||
author: 'MetaBuilder',
|
||||
category: options.category || 'ui',
|
||||
minLevel: options.minLevel ?? 2,
|
||||
primary: options.primary ?? true,
|
||||
withSchema: options.withSchema || false,
|
||||
withTests: options.withTests ?? true,
|
||||
withComponents: options.withComponents || false,
|
||||
entities: options.entities || [],
|
||||
components: options.components || [],
|
||||
dependencies: options.dependencies || []
|
||||
}
|
||||
|
||||
const packagesDir = path.join(process.cwd(), 'packages')
|
||||
const packagePath = path.join(packagesDir, packageId)
|
||||
|
||||
if (fs.existsSync(packagePath)) {
|
||||
console.error(`Error: Package directory already exists: ${packagePath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`\nGenerating package: ${config.name}`)
|
||||
console.log(` Location: ${packagePath}`)
|
||||
console.log(` Category: ${config.category}`)
|
||||
console.log(` Level: ${config.minLevel}`)
|
||||
console.log(` Type: ${config.primary ? 'Primary' : 'Dependency'}`)
|
||||
if (config.withSchema) console.log(` Entities: ${config.entities.join(', ')}`)
|
||||
if (config.components.length) console.log(` Components: ${config.components.join(', ')}`)
|
||||
console.log('')
|
||||
|
||||
const files = generate(config)
|
||||
writeFiles(packagePath, files)
|
||||
|
||||
console.log(`\n✅ Package '${packageId}' created successfully!`)
|
||||
console.log(`\nNext steps:`)
|
||||
console.log(` 1. Review generated files in packages/${packageId}/`)
|
||||
console.log(` 2. Add package-specific logic to seed/scripts/`)
|
||||
console.log(` 3. Update components.json with your component definitions`)
|
||||
console.log(` 4. Run tests: npm run test:package ${packageId}`)
|
||||
Reference in New Issue
Block a user