diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 7bddf2e..3399c0f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -99,11 +99,11 @@ Plans: 2. Developer can run CLI command to scaffold a new theme with structure 3. Developer can run CLI command to scaffold a component within a plugin 4. Generated scaffolds follow project conventions and are immediately usable -**Plans**: TBD +**Plans**: 2 plans Plans: -- [ ] 05-01: CLI framework and plugin scaffolding -- [ ] 05-02: Theme and component scaffolding +- [ ] 05-01-PLAN.md - CLI framework setup with ZIO CLI and plugin scaffolding command +- [ ] 05-02-PLAN.md - Theme scaffolding (blank/starter templates) and component scaffolding ### Phase 6: Backend Authentication **Goal**: Secure admin backend with user accounts and role-based access @@ -210,7 +210,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 | 2. Plugin System | 0/3 | Planned | - | | 3. Component System | 0/2 | Planned | - | | 4. Theme Engine | 0/2 | Planned | - | -| 5. CLI Scaffolding | 0/2 | Not started | - | +| 5. CLI Scaffolding | 0/2 | Planned | - | | 6. Backend Authentication | 0/3 | Not started | - | | 7. Admin Forms & Lists | 0/3 | Not started | - | | 8. Admin Dashboard | 0/2 | Not started | - | diff --git a/.planning/phases/05-cli-scaffolding/05-01-PLAN.md b/.planning/phases/05-cli-scaffolding/05-01-PLAN.md new file mode 100644 index 0000000..8f28060 --- /dev/null +++ b/.planning/phases/05-cli-scaffolding/05-01-PLAN.md @@ -0,0 +1,323 @@ +--- +phase: 05-cli-scaffolding +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - build.mill + - cli/src/Main.scala + - cli/src/commands/PluginCommands.scala + - cli/src/commands/VersionCommand.scala + - cli/src/scaffold/ScaffoldService.scala + - cli/src/scaffold/TemplateRenderer.scala + - cli/src/scaffold/templates/PluginTemplates.scala + - cli/src/output/ConsoleOutput.scala + - cli/src/config/ConfigDetector.scala + - cli/src/errors/ScaffoldError.scala +autonomous: true + +must_haves: + truths: + - "Developer can run `./mill cli.run version` and see version info with Scala/JVM versions" + - "Developer can run `./mill cli.run plugin create Author.Name` and get a complete plugin scaffold" + - "Developer can run `./mill cli.run plugin create Author.Name --dry-run` to preview without creating" + - "Generated plugin scaffold has correct directory structure (Plugin.scala, plugin.yaml, empty dirs)" + - "Invalid plugin name format produces clear error message with suggestion" + artifacts: + - path: "cli/src/Main.scala" + provides: "CLI entry point with ZIO CLI" + contains: "ZIOCliDefault" + - path: "cli/src/commands/PluginCommands.scala" + provides: "Plugin subcommands" + exports: ["command"] + - path: "cli/src/scaffold/ScaffoldService.scala" + provides: "File generation service" + exports: ["ScaffoldService"] + - path: "cli/src/output/ConsoleOutput.scala" + provides: "Colored console output" + exports: ["ConsoleOutput"] + key_links: + - from: "cli/src/Main.scala" + to: "cli/src/commands/PluginCommands.scala" + via: "subcommands composition" + pattern: "PluginCommands\\.command" + - from: "cli/src/commands/PluginCommands.scala" + to: "cli/src/scaffold/ScaffoldService.scala" + via: "ZIO service dependency" + pattern: "ZIO\\.service\\[ScaffoldService\\]" +--- + + +Create the `summer` CLI tool with ZIO CLI framework and implement plugin scaffolding command. + +Purpose: Establish CLI foundation for all developer tooling and deliver the first scaffolding capability (plugins). +Output: Working `summer` CLI with `version` and `plugin create` commands. + + + +@/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 +@build.mill + + + + + + Task 1: CLI Module Setup with ZIO CLI + + build.mill + cli/src/Main.scala + cli/src/commands/VersionCommand.scala + cli/src/output/ConsoleOutput.scala + cli/src/config/ConfigDetector.scala + cli/src/errors/ScaffoldError.scala + + + Create a new `cli` Mill module alongside the existing `summercms` module. + + 1. **Update build.mill:** + - Add `cli` module extending ScalaModule + - Same scalaVersion as summercms (3.3.4) + - Dependencies: + - `mvn"dev.zio::zio-cli:0.7.4"` (command parsing) + - `mvn"com.lihaoyi::os-lib:0.11.7"` (file operations) + - `mvn"com.lihaoyi::fansi:0.5.1"` (colored output) + - No dependency on summercms module (CLI is standalone) + + 2. **Create cli/src/errors/ScaffoldError.scala:** + - Sealed trait `ScaffoldError` extending Exception + - Case classes: `InvalidName`, `AlreadyExists`, `FileSystemError`, `NotInProject`, `ConfigError` + - Each has clear message field + + 3. **Create cli/src/output/ConsoleOutput.scala:** + - ZIO service trait with methods: `info`, `success`, `warning`, `error`, `nextSteps` + - Live layer using fansi for colors + - Accept `useColors: Boolean` parameter (for --no-color support) + - Use fansi.Color.Cyan for info, Green for success, Yellow for warning, Red for error + - `nextSteps` renders bold header with bulleted list + + 4. **Create cli/src/config/ConfigDetector.scala:** + - ZIO service trait with `findProjectRoot` method + - Look for marker files: `build.mill`, `.summercms` + - Walk up directory tree from cwd until found or reach root + - Return `os.Path` of project root or fail with `NotInProject` error + + 5. **Create cli/src/commands/VersionCommand.scala:** + - Define `VersionCommand` case object + - Create `command: Command[VersionCommand]` for "version" subcommand + - Handler prints: "SummerCMS 0.1.0 (Scala 3.3.4, JVM {java.version})" + - Note: Version hardcoded for now; BuildInfo generation deferred + + 6. **Create cli/src/Main.scala:** + - Extend ZIOCliDefault + - Define global options: + - `--no-color` (Boolean, disables colors) + - `--verbose` / `-v` (Boolean) + - `--quiet` / `-q` (Boolean) + - `--no-interaction` (Boolean, for CI) + - Create top-level `summer` command with VersionCommand as subcommand + - Use CliApp.make with name="summer", version="0.1.0" + - Wire ConsoleOutput layer based on --no-color flag + + **Do NOT use:** Complex dependency injection beyond ZIO layers. Keep it simple. + + + ```bash + ./mill cli.compile + ./mill cli.run version + # Should output: SummerCMS 0.1.0 (Scala 3.3.4, JVM 21) + ./mill cli.run --help + # Should show summer command with version subcommand + ``` + + + CLI module compiles. `summer version` outputs version info. `summer --help` shows command structure. + + + + + Task 2: Plugin Scaffolding Command + + cli/src/Main.scala + cli/src/commands/PluginCommands.scala + cli/src/scaffold/ScaffoldService.scala + cli/src/scaffold/TemplateRenderer.scala + cli/src/scaffold/templates/PluginTemplates.scala + + + Implement `summer plugin create Author.Name` command. + + 1. **Create cli/src/scaffold/ScaffoldService.scala:** + - ZIO service trait with methods: + - `createDirectory(path: os.Path): IO[ScaffoldError, Unit]` + - `writeFile(path: os.Path, content: String): IO[ScaffoldError, Unit]` + - `exists(path: os.Path): UIO[Boolean]` + - Live layer using os-lib: + - `createDirectory`: `os.makeDir.all(path)` + - `writeFile`: `os.makeDir.all(path / os.up)` then `os.write(path, content)` + - `exists`: `os.exists(path)` + - Wrap all os-lib calls in `ZIO.attemptBlocking` with error mapping + + 2. **Create cli/src/scaffold/TemplateRenderer.scala:** + - Case class `TemplateVars` with fields: + - `name`, `author`, `plugin` (raw inputs) + - Derived: `lowerName`, `lowerAuthor`, `lowerPlugin` + - Derived: `studlyName`, `studlyAuthor`, `studlyPlugin` + - Derived: `snakeName` (e.g., "BlogPost" -> "blog_post") + - Derived: `pluginId` (e.g., "acme.blog") + - Derived: `pluginNamespace` (e.g., "Acme.Blog") + - Derived: `timestamp` (yyyyMMdd_HHmmss format) + - Method `render(template: String, vars: TemplateVars): String` + - Replace placeholders: `{{name}}`, `{{lower_name}}`, `{{studly_name}}`, etc. + - Use distinctive delimiters to avoid collision with Scala code + + 3. **Create cli/src/scaffold/templates/PluginTemplates.scala:** + - Object with template string vals: + - `pluginScala`: Plugin.scala with SummerPlugin trait stub + - `manifestYaml`: plugin.yaml with vendor, name, version, dependencies + - `langYaml`: Basic lang.yaml for en locale + - Include TODO comments showing where to add code + - Templates reference plugin types that will exist in Phase 2 + + 4. **Create cli/src/commands/PluginCommands.scala:** + - Sealed trait `PluginCommand` with case class `Create(name: String, dryRun: Boolean)` + - Define options: + - `--dry-run` / `-n`: Show what would be created + - Define args: + - `name`: Plugin name in Author.Name format + - Create `createCommand: Command[PluginCommand.Create]` + - Create `command: Command[PluginCommand]` with "plugin" as parent, "create" as subcommand + - Implement handler: + 1. Validate name format (Author.Name with regex `^[A-Z][a-zA-Z0-9]*\\.[A-Z][a-zA-Z0-9]*$`) + 2. Parse into (author, plugin) tuple + 3. Build plugin directory path: `plugins/{lower_author}/{lower_plugin}/` + 4. Check if directory exists -> fail with `AlreadyExists` + 5. Build file list with template content: + - `Plugin.scala` + - `plugin.yaml` + - `routes.scala` (empty routes placeholder) + - `models/.gitkeep` + - `controllers/.gitkeep` + - `components/.gitkeep` + - `resources/db/migration/.gitkeep` + - `resources/lang/en/lang.yaml` + - `resources/views/.gitkeep` + 6. If --dry-run: print what would be created + 7. Else: create files via ScaffoldService + 8. Print next steps (edit Plugin.scala, add models, add components) + + 5. **Update cli/src/Main.scala:** + - Add PluginCommands.command to subcommands + - Wire ScaffoldService.live and ConfigDetector.live layers + + **Pattern for name validation:** + ```scala + private val pluginNamePattern = "^([A-Z][a-zA-Z0-9]*)\\.([A-Z][a-zA-Z0-9]*)$".r + + def parsePluginName(input: String): Either[String, (String, String)] = + input match + case pluginNamePattern(author, name) => Right((author, name)) + case _ => Left(s"Invalid plugin name '$input'. Use Author.Name format (e.g., Acme.Blog)") + ``` + + + ```bash + # Test plugin creation + ./mill cli.run plugin create Acme.Blog --dry-run + # Should list files that would be created + + ./mill cli.run plugin create Acme.Blog + # Should create plugins/acme/blog/ with all files + + ls -la plugins/acme/blog/ + # Should show Plugin.scala, plugin.yaml, directories + + cat plugins/acme/blog/Plugin.scala + # Should show valid Scala code with TODO comments + + cat plugins/acme/blog/plugin.yaml + # Should show valid YAML manifest + + # Test error case + ./mill cli.run plugin create invalidname + # Should error with suggestion + + # Test already exists + ./mill cli.run plugin create Acme.Blog + # Should error with "already exists" + ``` + + + `summer plugin create Author.Name` creates complete plugin scaffold. `--dry-run` previews changes. Invalid names produce helpful errors. Existing plugins are not overwritten. + + + + + + +Full plan verification: + +1. CLI module builds independently: + ```bash + ./mill cli.compile + ``` + +2. Version command works: + ```bash + ./mill cli.run version + ``` + +3. Help shows all commands: + ```bash + ./mill cli.run --help + ./mill cli.run plugin --help + ./mill cli.run plugin create --help + ``` + +4. Plugin scaffolding works end-to-end: + ```bash + rm -rf plugins/test/example # Cleanup if exists + ./mill cli.run plugin create Test.Example + ls plugins/test/example/ + # Plugin.scala, plugin.yaml, routes.scala, models/, controllers/, components/, resources/ + ``` + +5. Dry-run mode works: + ```bash + ./mill cli.run plugin create Another.Plugin --dry-run + # Shows what would be created, but doesn't create + ls plugins/another/plugin 2>/dev/null || echo "Directory not created (correct)" + ``` + +6. Error handling: + ```bash + ./mill cli.run plugin create badname + # Error: Invalid plugin name 'badname'. Use Author.Name format + ``` + + + +- [ ] CLI module exists as separate Mill module with ZIO CLI, os-lib, fansi deps +- [ ] `summer version` shows version with Scala/JVM info +- [ ] `summer plugin create Author.Name` creates valid plugin scaffold +- [ ] `--dry-run` shows preview without file creation +- [ ] Invalid plugin names produce clear error with format suggestion +- [ ] Existing plugin directories prevent overwrite with error +- [ ] Generated Plugin.scala compiles (references future types with stubs) +- [ ] Generated plugin.yaml is valid YAML +- [ ] Colored output works with --no-color fallback +- [ ] Next steps printed after successful scaffold + + + +After completion, create `.planning/phases/05-cli-scaffolding/05-01-SUMMARY.md` + diff --git a/.planning/phases/05-cli-scaffolding/05-02-PLAN.md b/.planning/phases/05-cli-scaffolding/05-02-PLAN.md new file mode 100644 index 0000000..781b0aa --- /dev/null +++ b/.planning/phases/05-cli-scaffolding/05-02-PLAN.md @@ -0,0 +1,358 @@ +--- +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` +