--- phase: 05-cli-scaffolding plan: 02 type: execute wave: 2 depends_on: ["05-01"] files_modified: - cli/src/Main.scala - cli/src/commands/ThemeCommands.scala - cli/src/commands/ComponentCommands.scala - cli/src/scaffold/templates/ThemeTemplates.scala - cli/src/scaffold/templates/ComponentTemplates.scala - cli/src/output/Progress.scala autonomous: true must_haves: truths: - "Developer can run `summer theme create Author.Theme` and get a blank theme scaffold" - "Developer can run `summer theme create Author.Theme --template=starter` and get a starter theme with Vite/Tailwind" - "Developer can run `summer component create Author.Plugin MyComponent` and get a component scaffold" - "Component scaffolding fails with clear error if plugin doesn't exist" - "Theme scaffolding shows progress bar during file creation" artifacts: - path: "cli/src/commands/ThemeCommands.scala" provides: "Theme scaffolding commands" exports: ["command"] - path: "cli/src/commands/ComponentCommands.scala" provides: "Component scaffolding commands" exports: ["command"] - path: "cli/src/scaffold/templates/ThemeTemplates.scala" provides: "Theme file templates (blank and starter)" exports: ["blankThemeFiles", "starterThemeFiles"] - path: "cli/src/scaffold/templates/ComponentTemplates.scala" provides: "Component file templates" exports: ["componentClass", "componentPartial", "componentYaml"] key_links: - from: "cli/src/Main.scala" to: "cli/src/commands/ThemeCommands.scala" via: "subcommands composition" pattern: "ThemeCommands\\.command" - from: "cli/src/commands/ComponentCommands.scala" to: "cli/src/scaffold/ScaffoldService.scala" via: "plugin existence check" pattern: "exists.*Plugin\\.scala" --- Implement theme and component scaffolding commands for the `summer` CLI. Purpose: Complete the core scaffolding capabilities for developer productivity (CORE-08, CORE-09). Output: Working `summer theme create` and `summer component create` commands with templates. @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/05-cli-scaffolding/05-CONTEXT.md @.planning/phases/05-cli-scaffolding/05-RESEARCH.md @.planning/phases/05-cli-scaffolding/05-01-SUMMARY.md @build.mill @cli/src/Main.scala @cli/src/scaffold/ScaffoldService.scala Task 1: Theme Scaffolding Command cli/src/Main.scala cli/src/commands/ThemeCommands.scala cli/src/scaffold/templates/ThemeTemplates.scala cli/src/output/Progress.scala Implement `summer theme create Author.Theme [--template=blank|starter]` command. 1. **Create cli/src/output/Progress.scala:** - ZIO service trait with methods: - `start(total: Int, description: String): UIO[ProgressHandle]` - `update(handle: ProgressHandle, current: Int): UIO[Unit]` - `complete(handle: ProgressHandle): UIO[Unit]` - Case class `ProgressHandle(id: Long, total: Int, description: String)` - Live layer implementation: - Use `print` with `\r` for in-place updates - Progress bar format: `[==== ] 4/10` - Width: 30 characters for the bar - On complete: print newline to preserve final state 2. **Create cli/src/scaffold/templates/ThemeTemplates.scala:** - Object with template string vals for BLANK theme: - `themeYaml`: theme.yaml manifest (name, author, description, version) - `defaultLayout`: layouts/default.htm with HTML5 boilerplate, {{page}} placeholder - `homePage`: pages/home.htm with basic content, url="/", title="Home" - `notFoundPage`: pages/404.htm with 404 content, url="/404" - Additional templates for STARTER theme: - `packageJson`: package.json with Vite, Tailwind dependencies - `viteConfig`: vite.config.js with build output to assets/ - `tailwindCss`: assets/css/app.css with @tailwind directives - `starterJs`: assets/js/app.js with basic imports - `gitignore`: .gitignore for node_modules, dist - Helper function `blankThemeFiles(dir: os.Path, vars: TemplateVars): List[(os.Path, String)]` - Helper function `starterThemeFiles(dir: os.Path, vars: TemplateVars): List[(os.Path, String)]` - Starter includes all blank files PLUS the npm/Vite files 3. **Create cli/src/commands/ThemeCommands.scala:** - Sealed trait `ThemeCommand` with case class `Create(name: String, template: String, dryRun: Boolean)` - Define options: - `--template`: String with default "blank", help "Theme template: blank or starter" - `--dry-run` / `-n`: Show what would be created - Define args: - `name`: Theme name in Author.Name format - Validate template option: must be "blank" or "starter" - Create `createCommand: Command[ThemeCommand.Create]` - Create `command: Command[ThemeCommand]` with "theme" parent, "create" subcommand - Implement handler: 1. Validate name format (same regex as plugin: `^[A-Z][a-zA-Z0-9]*\\.[A-Z][a-zA-Z0-9]*$`) 2. Parse into (author, theme) tuple 3. Build theme directory path: `themes/{lower_author}/{lower_theme}/` 4. Check if directory exists -> fail with `AlreadyExists` 5. Get file list based on template choice (blank or starter) 6. If --dry-run: print file list 7. Else: - Start progress bar - Create each file, update progress - Complete progress bar 8. Print next steps: - Blank: "Add layouts, pages, and partials to your theme" - Starter: "Run 'npm install' in theme directory, then 'npm run build'" 4. **Update cli/src/Main.scala:** - Add ThemeCommands.command to subcommands - Wire Progress.live layer **Template placeholder format (use {{var}} consistently):** - `{{name}}`, `{{lower_name}}`, `{{studly_name}}` - `{{author}}`, `{{lower_author}}`, `{{studly_author}}` ```bash # Test blank theme ./mill cli.run theme create Acme.Corporate --dry-run # Should list: theme.yaml, layouts/default.htm, pages/home.htm, pages/404.htm ./mill cli.run theme create Acme.Corporate # Should create themes/acme/corporate/ with blank structure ls themes/acme/corporate/ # theme.yaml, layouts/, pages/, partials/, assets/ cat themes/acme/corporate/theme.yaml # Valid YAML with name, author, version # Test starter theme ./mill cli.run theme create Acme.Modern --template=starter --dry-run # Should list additional: package.json, vite.config.js, etc. ./mill cli.run theme create Acme.Modern --template=starter # Should create with npm files ls themes/acme/modern/ # Includes package.json, vite.config.js, etc. # Test invalid template ./mill cli.run theme create Test.Theme --template=invalid # Should error with valid options # Test already exists ./mill cli.run theme create Acme.Corporate # Should error ``` `summer theme create Author.Theme` creates blank theme. `--template=starter` adds Vite/Tailwind setup. Progress bar shows during creation. Invalid templates rejected. Task 2: Component Scaffolding Command cli/src/Main.scala cli/src/commands/ComponentCommands.scala cli/src/scaffold/templates/ComponentTemplates.scala Implement `summer component create Author.Plugin ComponentName` command. 1. **Create cli/src/scaffold/templates/ComponentTemplates.scala:** - Object with template string vals: - `componentScala`: Component class extending SummerComponent - `componentDetails`: name and description - `defineProperties`: returns List.empty (placeholder) - `onRun`: returns Map.empty (placeholder) - Include TODO comments for each section - `componentPartial`: default.htm partial with basic HTML - Include example of accessing component properties - `componentYaml`: component.yaml with name, description, properties section - Template variables used: `{{name}}`, `{{studly_name}}`, `{{lower_name}}`, `{{plugin_namespace}}` 2. **Create cli/src/commands/ComponentCommands.scala:** - Sealed trait `ComponentCommand` with case class `Create(plugin: String, name: String, dryRun: Boolean)` - Define args: - `plugin`: Plugin identifier in Author.Name format (required) - `name`: Component name (single word, PascalCase) - Define options: - `--dry-run` / `-n`: Show what would be created - Create `createCommand: Command[ComponentCommand.Create]` - Create `command: Command[ComponentCommand]` with "component" parent, "create" subcommand - Implement handler: 1. Validate plugin name format (Author.Name) 2. Parse plugin into (author, pluginName) tuple 3. Validate component name (PascalCase: `^[A-Z][a-zA-Z0-9]*$`) 4. Build plugin directory path: `plugins/{lower_author}/{lower_plugin}/` 5. **Check if plugin exists** by verifying `Plugin.scala` exists in plugin dir - If not: fail with `PluginNotFound` error (add to ScaffoldError if needed) 6. Build component paths: - `components/{StudlyName}.scala` - component class - `components/{lower_name}/default.htm` - default partial - `components/{lower_name}/component.yaml` - component config 7. Check if component already exists -> fail with `AlreadyExists` 8. If --dry-run: print file list 9. Else: create files via ScaffoldService 10. Print next steps: - "Register component in Plugin.scala register() method" - "Edit {StudlyName}.scala to add component logic" - "Edit default.htm partial for component output" 3. **Update cli/src/Main.scala:** - Add ComponentCommands.command to subcommands 4. **Add PluginNotFound to ScaffoldError if not present:** - `case class PluginNotFound(pluginId: String) extends ScaffoldError` - Message: "Plugin '{pluginId}' not found. Create it first with: summer plugin create {pluginId}" **Component name validation pattern:** ```scala private val componentNamePattern = "^[A-Z][a-zA-Z0-9]*$".r def validateComponentName(name: String): Either[String, String] = if componentNamePattern.matches(name) then Right(name) else Left(s"Invalid component name '$name'. Use PascalCase (e.g., BlogPost, FeaturedItems)") ``` ```bash # Ensure test plugin exists first ./mill cli.run plugin create Test.Blog 2>/dev/null || true # Test component creation ./mill cli.run component create Test.Blog FeaturedPosts --dry-run # Should list: FeaturedPosts.scala, featuredposts/default.htm, featuredposts/component.yaml ./mill cli.run component create Test.Blog FeaturedPosts # Should create component files ls plugins/test/blog/components/ # FeaturedPosts.scala, featuredposts/ ls plugins/test/blog/components/featuredposts/ # default.htm, component.yaml cat plugins/test/blog/components/FeaturedPosts.scala # Valid Scala extending SummerComponent # Test plugin not found ./mill cli.run component create Nonexistent.Plugin MyComponent # Error: Plugin 'Nonexistent.Plugin' not found... # Test invalid component name ./mill cli.run component create Test.Blog invalid_name # Error: Invalid component name... # Test component already exists ./mill cli.run component create Test.Blog FeaturedPosts # Error: Already exists... ``` `summer component create Author.Plugin ComponentName` creates component scaffold inside existing plugin. Plugin existence is verified. Invalid names produce clear errors. Generated files include TODO comments. Full plan verification: 1. Theme scaffolding works: ```bash rm -rf themes/verify # Cleanup ./mill cli.run theme create Verify.Blank ls themes/verify/blank/ # theme.yaml, layouts/, pages/, partials/, assets/ ``` 2. Starter theme includes npm files: ```bash rm -rf themes/verify/starter ./mill cli.run theme create Verify.Starter --template=starter ls themes/verify/starter/ # Includes package.json, vite.config.js cat themes/verify/starter/package.json # Valid JSON with Vite, Tailwind deps ``` 3. Component scaffolding requires existing plugin: ```bash ./mill cli.run component create Missing.Plugin Test 2>&1 | grep -q "not found" echo "Plugin check works: $?" ``` 4. Component creates correct structure: ```bash # Use existing test plugin from above ./mill cli.run component create Test.Blog LatestPosts test -f plugins/test/blog/components/LatestPosts.scala && echo "Component class created" test -f plugins/test/blog/components/latestposts/default.htm && echo "Partial created" test -f plugins/test/blog/components/latestposts/component.yaml && echo "Config created" ``` 5. All help commands work: ```bash ./mill cli.run theme --help ./mill cli.run theme create --help ./mill cli.run component --help ./mill cli.run component create --help ``` 6. Progress bar displays during theme creation: ```bash ./mill cli.run theme create Verify.Progress --template=starter 2>&1 | head -5 # Should show progress output ``` - [ ] `summer theme create Author.Theme` creates valid blank theme - [ ] `--template=starter` adds Vite/Tailwind configuration files - [ ] `--template=invalid` produces error listing valid options - [ ] Progress bar displays during theme file creation - [ ] `summer component create Plugin.Name ComponentName` creates component scaffold - [ ] Component command validates plugin exists before creating - [ ] Component command validates component name is PascalCase - [ ] Generated component includes .scala, .htm partial, .yaml config - [ ] All generated files contain appropriate TODO comments - [ ] Next steps printed after each successful scaffold - [ ] Dry-run mode works for both commands After completion, create `.planning/phases/05-cli-scaffolding/05-02-SUMMARY.md`