// Package domain contains pure domain models with no external dependencies. package domain import "regexp" // ComponentType represents the type of component in a monorepo. type ComponentType string const ( ComponentTypeService ComponentType = "service" ComponentTypeWorker ComponentType = "worker" ComponentTypeAppAstro ComponentType = "app-astro" ComponentTypeAppReact ComponentType = "app-react" ComponentTypeAppNextJS ComponentType = "app-nextjs" ComponentTypeCLI ComponentType = "cli" // Infrastructure component types - these trigger provisioning, not scaffolding. ComponentTypePostgres ComponentType = "postgres" ComponentTypeRedis ComponentType = "redis" ComponentTypeGCS ComponentType = "gcs" ) // ValidComponentTypes lists all valid component types. var ValidComponentTypes = []ComponentType{ ComponentTypeService, ComponentTypeWorker, ComponentTypeAppAstro, ComponentTypeAppReact, ComponentTypeAppNextJS, ComponentTypeCLI, ComponentTypePostgres, ComponentTypeRedis, ComponentTypeGCS, } // IsValidComponentType checks if a string is a valid component type. func IsValidComponentType(t string) bool { for _, valid := range ValidComponentTypes { if string(valid) == t { return true } } return false } // Component represents a component in a monorepo project. type Component struct { Type ComponentType `json:"type"` Name string `json:"name"` Path string `json:"path"` // e.g., "services/auth-api" Port int `json:"port"` // 0 if not applicable Template string `json:"template"` // template used Dependencies []string `json:"dependencies"` // e.g., ["postgres", "redis"] } // DestDir returns the destination directory for this component type. func (c ComponentType) DestDir() string { switch c { case ComponentTypeService: return "services" case ComponentTypeWorker: return "workers" case ComponentTypeAppAstro, ComponentTypeAppReact, ComponentTypeAppNextJS: return "apps" case ComponentTypeCLI: return "cli" default: return "" } } // StartingPort returns the starting port number for this component type. // Workers and CLIs don't expose ports (return 0). func (c ComponentType) StartingPort() int { switch c { case ComponentTypeService: return 8001 case ComponentTypeAppAstro, ComponentTypeAppReact, ComponentTypeAppNextJS: return 3001 case ComponentTypeWorker, ComponentTypeCLI: return 0 default: return 0 } } // NeedsPort returns true if this component type requires a port assignment. func (c ComponentType) NeedsPort() bool { return c == ComponentTypeService || c == ComponentTypeAppAstro || c == ComponentTypeAppReact || c == ComponentTypeAppNextJS } // IsGoComponent returns true if this component type uses Go (and needs go.work entry). func (c ComponentType) IsGoComponent() bool { return c == ComponentTypeService || c == ComponentTypeWorker || c == ComponentTypeCLI } // IsAppComponent returns true if this component type is a frontend app (and gets "/" path). func (c ComponentType) IsAppComponent() bool { return c == ComponentTypeAppAstro || c == ComponentTypeAppReact || c == ComponentTypeAppNextJS } // IsInfraComponent returns true if this component type is infrastructure (database, cache, storage). // Infrastructure components trigger provisioning instead of template scaffolding. func (c ComponentType) IsInfraComponent() bool { return c == ComponentTypePostgres || c == ComponentTypeRedis || c == ComponentTypeGCS } // componentNameRegex validates component names (slug format: lowercase, alphanumeric, dashes). var componentNameRegex = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) // ValidateComponentName validates that a component name is in slug format. // Must be lowercase, start with a letter, and contain only letters, numbers, and dashes. func ValidateComponentName(name string) error { if name == "" { return ErrInvalidComponentName } if len(name) > MaxProjectNameLen { // Reuse the 63-char limit from K8s return ErrInvalidComponentName } if !componentNameRegex.MatchString(name) { return ErrInvalidComponentName } return nil }