refactor: Update component structure to use behavior and stack attributes, enhancing clarity and functionality across services, tools, and frontends

This commit is contained in:
2026-02-23 16:32:55 -08:00
parent 3343e955fd
commit 56b9c8ddf1
45 changed files with 698 additions and 667 deletions

View File

@@ -22,6 +22,13 @@ from castle_cli.manifest import (
)
from castle_cli.templates.scaffold import scaffold_project
# Stack determines default behavior and scaffold template
STACK_DEFAULTS: dict[str, str] = {
"python-fastapi": "daemon",
"python-cli": "tool",
"react-vite": "frontend",
}
def next_available_port(config: object) -> int:
"""Find the next available port starting from 9001 (9000 is reserved for gateway)."""
@@ -42,7 +49,8 @@ def run_create(args: argparse.Namespace) -> int:
"""Create a new project."""
config = load_config()
name = args.name
proj_type = args.type
stack = args.stack
behavior = STACK_DEFAULTS.get(stack)
if name in config.components or name in config.services or name in config.jobs:
print(f"Error: '{name}' already exists in castle.yaml")
@@ -55,9 +63,9 @@ def run_create(args: argparse.Namespace) -> int:
print(f"Error: directory 'components/{name}' already exists")
return 1
# Determine port for services
# Determine port for daemons
port = args.port
if proj_type == "service" and port is None:
if behavior == "daemon" and port is None:
port = next_available_port(config)
# Package name: convert kebab-case to snake_case
@@ -68,18 +76,19 @@ def run_create(args: argparse.Namespace) -> int:
project_dir=project_dir,
name=name,
package_name=package_name,
proj_type=proj_type,
description=args.description or f"A castle {proj_type}",
stack=stack,
description=args.description or f"A castle {stack} component",
port=port,
)
# Build entries
if proj_type == "service":
if behavior == "daemon":
# Component for software identity
config.components[name] = ComponentSpec(
id=name,
description=args.description or f"A castle {proj_type}",
description=args.description or f"A castle {stack} component",
source=f"components/{name}",
stack=stack,
)
# Service for deployment
config.services[name] = ServiceSpec(
@@ -95,32 +104,34 @@ def run_create(args: argparse.Namespace) -> int:
proxy=ProxySpec(caddy=CaddySpec(path_prefix=f"/{name}")),
manage=ManageSpec(systemd=SystemdSpec()),
)
elif proj_type == "tool":
elif behavior == "tool":
config.components[name] = ComponentSpec(
id=name,
description=args.description or f"A castle {proj_type}",
description=args.description or f"A castle {stack} component",
source=f"components/{name}",
stack=stack,
tool=ToolSpec(),
install=InstallSpec(path=PathInstallSpec(alias=name)),
)
else:
# library or other
# frontend or other
config.components[name] = ComponentSpec(
id=name,
description=args.description or f"A castle {proj_type}",
description=args.description or f"A castle {stack} component",
source=f"components/{name}",
stack=stack,
)
save_config(config)
print(f"Created {proj_type} '{name}' at {project_dir}")
print(f"Created {stack} component '{name}' at {project_dir}")
if port:
print(f" Port: {port}")
print(" Registered in castle.yaml")
print("\nNext steps:")
print(f" cd components/{name}")
print(" uv sync")
if proj_type == "service":
if behavior == "daemon":
print(f" uv run {name} # starts on port {port}")
print(f" castle deploy {name} # deploy to ~/.castle/")
print(f" castle test {name}")