313 lines
7.4 KiB
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
|
|
}
|