Files
MosisService/portal/cmd/mosis/cmd/init.go

313 lines
7.4 KiB
Go

package cmd
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/spf13/cobra"
"omixlab.com/mosis-portal/pkg/mospkg"
)
// InitCmd returns the init command
func InitCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "init [directory]",
Short: "Create a new Mosis app project",
Long: `Create a new Mosis app project with boilerplate files.
If directory is not specified, the current directory is used.
If the directory contains files, you will be prompted to confirm.`,
Args: cobra.MaximumNArgs(1),
RunE: runInit,
}
cmd.Flags().String("name", "", "App name")
cmd.Flags().String("id", "", "Package ID (e.g., com.yourname.app)")
cmd.Flags().String("description", "", "App description")
cmd.Flags().String("author", "", "Author name")
cmd.Flags().String("email", "", "Author email")
cmd.Flags().Bool("yes", false, "Skip confirmation prompts")
return cmd
}
func runInit(cmd *cobra.Command, args []string) error {
// Determine target directory
dir := "."
if len(args) > 0 {
dir = args[0]
}
// Create directory if needed
if dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("create directory: %w", err)
}
}
// Check if directory has files
entries, _ := os.ReadDir(dir)
skip, _ := cmd.Flags().GetBool("yes")
if len(entries) > 0 && !skip {
fmt.Printf("Directory %s is not empty. Continue? [y/N] ", dir)
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
if strings.ToLower(strings.TrimSpace(answer)) != "y" {
fmt.Println("Aborted.")
return nil
}
}
// Gather project info
info, err := gatherProjectInfo(cmd)
if err != nil {
return err
}
fmt.Println("\nCreating project structure...")
// Create manifest.json
manifest := mospkg.Manifest{
ID: info.id,
Name: info.name,
Version: "1.0.0",
VersionCode: 1,
Entry: "assets/main.rml",
MinMosisVersion: "1.0.0",
Description: info.description,
Author: &mospkg.Author{
Name: info.author,
Email: info.email,
},
Permissions: []string{},
Icons: mospkg.Icons{
Size32: "icons/icon-32.png",
Size64: "icons/icon-64.png",
Size128: "icons/icon-128.png",
},
Orientation: "portrait",
BackgroundColor: "#FFFFFF",
}
manifestJSON, _ := json.MarshalIndent(manifest, "", " ")
if err := writeFile(dir, "manifest.json", manifestJSON); err != nil {
return err
}
fmt.Println("✓ Created manifest.json")
// Create main.rml
mainRML := fmt.Sprintf(`<rml>
<head>
<title>%s</title>
<link type="text/rcss" href="styles/theme.rcss"/>
<script src="scripts/app.lua"></script>
</head>
<body>
<div class="container">
<h1>%s</h1>
<p>%s</p>
</div>
</body>
</rml>
`, info.name, info.name, info.description)
if err := writeFile(dir, "assets/main.rml", []byte(mainRML)); err != nil {
return err
}
fmt.Println("✓ Created assets/main.rml")
// Create theme.rcss
themeRCSS := `/* Theme styles for the app */
body {
font-family: LatoLatin;
font-size: 16dp;
color: #333333;
background-color: #FFFFFF;
}
.container {
padding: 16dp;
}
h1 {
font-size: 24dp;
font-weight: bold;
margin-bottom: 8dp;
}
p {
line-height: 1.5;
}
`
if err := writeFile(dir, "assets/styles/theme.rcss", []byte(themeRCSS)); err != nil {
return err
}
fmt.Println("✓ Created assets/styles/theme.rcss")
// Create app.lua
appLua := `-- App initialization script
function onLoad()
print("App loaded: ` + info.name + `")
end
function onUnload()
print("App unloaded")
end
`
if err := writeFile(dir, "assets/scripts/app.lua", []byte(appLua)); err != nil {
return err
}
fmt.Println("✓ Created assets/scripts/app.lua")
// Create placeholder icon files
if err := createPlaceholderIcons(dir); err != nil {
return err
}
fmt.Println("✓ Created icons/ (placeholder icons)")
// Create .mosisignore
mosisignore := `# Files to exclude from package
.git/
.gitignore
*.md
dist/
.mosisignore
`
if err := writeFile(dir, ".mosisignore", []byte(mosisignore)); err != nil {
return err
}
fmt.Println("✓ Created .mosisignore")
// Print next steps
projectDir := info.dirName
if dir == "." {
projectDir = "."
}
fmt.Printf(`
Project created! Next steps:
`)
if projectDir != "." {
fmt.Printf(" cd %s\n", projectDir)
}
fmt.Println(" mosis run # Preview in designer")
fmt.Println(" mosis build # Create package")
fmt.Println(" mosis publish # Submit to store")
return nil
}
type projectInfo struct {
name string
id string
description string
author string
email string
dirName string
}
func gatherProjectInfo(cmd *cobra.Command) (*projectInfo, error) {
reader := bufio.NewReader(os.Stdin)
info := &projectInfo{}
// Get from flags or prompt
name, _ := cmd.Flags().GetString("name")
if name == "" {
fmt.Print("? App name: ")
name, _ = reader.ReadString('\n')
name = strings.TrimSpace(name)
}
if name == "" {
return nil, fmt.Errorf("app name is required")
}
info.name = name
id, _ := cmd.Flags().GetString("id")
if id == "" {
fmt.Print("? Package ID (e.g., com.yourname.app): ")
id, _ = reader.ReadString('\n')
id = strings.TrimSpace(id)
}
if id == "" || !isValidPackageID(id) {
return nil, fmt.Errorf("valid package ID is required (e.g., com.yourname.app)")
}
info.id = id
description, _ := cmd.Flags().GetString("description")
if description == "" {
fmt.Print("? Description: ")
description, _ = reader.ReadString('\n')
description = strings.TrimSpace(description)
}
info.description = description
author, _ := cmd.Flags().GetString("author")
if author == "" {
fmt.Print("? Author name: ")
author, _ = reader.ReadString('\n')
author = strings.TrimSpace(author)
}
info.author = author
email, _ := cmd.Flags().GetString("email")
if email == "" {
fmt.Print("? Author email: ")
email, _ = reader.ReadString('\n')
email = strings.TrimSpace(email)
}
info.email = email
// Generate directory name from app name
info.dirName = strings.ToLower(strings.ReplaceAll(info.name, " ", "-"))
info.dirName = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(info.dirName, "")
return info, nil
}
var packageIDPattern = regexp.MustCompile(`^[a-z][a-z0-9]*(\.[a-z][a-z0-9_]*)+$`)
func isValidPackageID(id string) bool {
return packageIDPattern.MatchString(id)
}
func writeFile(dir, relPath string, content []byte) error {
fullPath := filepath.Join(dir, relPath)
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
return fmt.Errorf("create directory for %s: %w", relPath, err)
}
return os.WriteFile(fullPath, content, 0644)
}
func createPlaceholderIcons(dir string) error {
// Create minimal PNG placeholders (1x1 white pixel)
// In a real implementation, you'd generate proper placeholder icons
minimalPNG := []byte{
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, // IHDR chunk
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41,
0x54, 0x08, 0xD7, 0x63, 0xF8, 0xFF, 0xFF, 0xFF,
0x00, 0x05, 0xFE, 0x02, 0xFE, 0xDC, 0xCC, 0x59,
0xE7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E,
0x44, 0xAE, 0x42, 0x60, 0x82, // IEND chunk
}
sizes := []int{32, 64, 128}
for _, size := range sizes {
path := fmt.Sprintf("icons/icon-%d.png", size)
if err := writeFile(dir, path, minimalPNG); err != nil {
return err
}
}
return nil
}