|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Codify is a configuration-as-code CLI tool that brings Infrastructure-as-Code principles to local development environments. It allows developers to declaratively define their development setup (packages, tools, system settings) in configuration files and apply them in a reproducible way. Think "Terraform for your local machine." |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +### Building |
| 12 | +```bash |
| 13 | +npm run build # Build TypeScript to dist/ |
| 14 | +npm run lint # Type-check with tsc |
| 15 | +``` |
| 16 | + |
| 17 | +### Testing |
| 18 | +```bash |
| 19 | +npm test # Run all tests with Vitest |
| 20 | +npm test -- path/to/test # Run specific test file |
| 21 | +npm run posttest # Runs lint after tests |
| 22 | +``` |
| 23 | + |
| 24 | +### Running Locally |
| 25 | +```bash |
| 26 | +./bin/dev.js <command> # Run CLI in development mode |
| 27 | +./bin/dev.js apply # Example: run apply command |
| 28 | +``` |
| 29 | + |
| 30 | +### Test Command (VM Testing) |
| 31 | +The `test` command spins up a Tart VM to test Codify configs in isolation: |
| 32 | +```bash |
| 33 | +./bin/dev.js test --vm-os darwin # Test on macOS VM |
| 34 | +./bin/dev.js test --vm-os linux # Test on Linux VM |
| 35 | +``` |
| 36 | + |
| 37 | +## High-Level Architecture |
| 38 | + |
| 39 | +### Core Architectural Patterns |
| 40 | + |
| 41 | +1. **Command-Orchestrator Pattern**: Commands (`src/commands/`) are thin oclif wrappers. Orchestrators (`src/orchestrators/`) contain all business logic and workflow coordination. This separation enables reusability. |
| 42 | + |
| 43 | +2. **Multi-Process Plugin System**: The most unique architectural decision is running plugins as separate Node.js child processes communicating via IPC: |
| 44 | + - **Why**: Isolation (crashes don't crash CLI), security (parent controls sudo), flexibility |
| 45 | + - **Plugin Process** (`src/plugins/plugin-process.ts`): Spawns plugins using `fork()` |
| 46 | + - **IPC Protocol** (`src/plugins/plugin-message.ts`): Type-safe message passing |
| 47 | + - **Security**: Plugins run isolated; parent process controls all sudo operations |
| 48 | + - When plugins need sudo, they send `COMMAND_REQUEST` events back to parent |
| 49 | + |
| 50 | +3. **Event-Driven Architecture**: Central event bus (`src/events/context.ts`) using EventEmitter: |
| 51 | + - Tracks process/subprocess lifecycle (PLAN, APPLY, INITIALIZE_PLUGINS, etc.) |
| 52 | + - Enables plugin-to-CLI communication (sudo prompts, login credentials, etc.) |
| 53 | + - Powers progress tracking for UI |
| 54 | + |
| 55 | +4. **Reporter Pattern**: Abstract `Reporter` interface with multiple implementations selected via `--output` flag: |
| 56 | + - `DefaultReporter`: Rich Ink-based TUI with React components |
| 57 | + - `PlainReporter`: Simple text output |
| 58 | + - `JsonReporter`: Machine-readable JSON |
| 59 | + - `DebugReporter`: Verbose logging |
| 60 | + - `StubReporter`: No-op for testing |
| 61 | + |
| 62 | +5. **Resource Lifecycle State Machine**: |
| 63 | + ``` |
| 64 | + Parse Config → Validate → Resolve Dependencies → Plan → Apply |
| 65 | + ``` |
| 66 | + - **ResourceConfig**: Desired state from config file |
| 67 | + - **Plan**: Computed difference between desired and current state |
| 68 | + - **ResourcePlan**: Per-resource operations (CREATE, UPDATE, DELETE, NOOP) |
| 69 | + - **Project**: Container with dependency graph |
| 70 | + |
| 71 | +6. **Dependency Resolution**: |
| 72 | + - Explicit: `dependsOn` field in config |
| 73 | + - Implicit: Extracted from parameter references (e.g., `${other-resource.param}`) |
| 74 | + - Plugin-level: Plugins declare type dependencies (e.g., xcode-tools on macOS) |
| 75 | + - Topological sort ensures correct evaluation order (`src/utils/dependency-graph-resolver.ts`) |
| 76 | + |
| 77 | +### Key Directory Structure |
| 78 | + |
| 79 | +- **`/src/orchestrators/`**: Business logic layer - each file implements one CLI command's workflow |
| 80 | + - `plan.ts`: Parse → Validate → Resolve deps → Generate plan |
| 81 | + - `apply.ts`: Execute plan after user confirmation |
| 82 | + - `import.ts`: Import existing resources into config |
| 83 | + - `test.ts`: VM-based testing with live config sync via file watcher |
| 84 | + |
| 85 | +- **`/src/plugins/`**: Plugin infrastructure |
| 86 | + - `plugin-manager.ts`: Registry routing operations to plugins |
| 87 | + - `plugin-process.ts`: Child process lifecycle and IPC |
| 88 | + - `plugin.ts`: High-level plugin API |
| 89 | + |
| 90 | +- **`/src/entities/`**: Domain models with rich behavior |
| 91 | + - `Project`: Container with dependency resolution |
| 92 | + - `ResourceConfig`: Mutable config with dependency tracking |
| 93 | + - `Plan`: Immutable plan with sorting/filtering |
| 94 | + |
| 95 | +- **`/src/parser/`**: Multi-format config parsing (JSON, JSONC, JSON5, YAML) |
| 96 | + - All parsers maintain source maps for error messages |
| 97 | + - Cloud parser fetches from Dashboard API via UUID |
| 98 | + |
| 99 | +- **`/src/ui/`**: User interface layer |
| 100 | + - `/reporters/`: Output strategy implementations |
| 101 | + - `/components/`: React components for Ink TUI |
| 102 | + - `/store/`: Jotai state management for UI |
| 103 | + |
| 104 | +- **`/src/connect/`**: Dashboard integration |
| 105 | + - WebSocket server for persistent connection |
| 106 | + - OAuth flow handling |
| 107 | + - JWT credential management |
| 108 | + |
| 109 | +- **`/src/generators/`**: Config file writers |
| 110 | + - Computes diffs for updating existing configs |
| 111 | + - Writes to local files or cloud (via Dashboard API) |
| 112 | + |
| 113 | +### Important Data Flows |
| 114 | + |
| 115 | +**Apply Command Flow:** |
| 116 | +``` |
| 117 | +ApplyOrchestrator.run() |
| 118 | + → PlanOrchestrator.run() |
| 119 | + → PluginInitOrchestrator.run() |
| 120 | + → Parse configs → Project |
| 121 | + → PluginManager.initialize() → ResourceDefinitions |
| 122 | + → Project.resolveDependencies() |
| 123 | + → PluginManager.plan() → Plan |
| 124 | + → Reporter.promptConfirmation() |
| 125 | + → PluginManager.apply() |
| 126 | + → For each resource (topologically sorted): |
| 127 | + → Plugin.apply() [IPC to child process] |
| 128 | +``` |
| 129 | + |
| 130 | +**Plugin Communication Flow:** |
| 131 | +``` |
| 132 | +Parent Process Plugin Process |
| 133 | + |-- initialize() -------->| |
| 134 | + |<-- resourceDefinitions -| |
| 135 | + |-- plan(resource) ------>| |
| 136 | + | [Plugin needs sudo] |
| 137 | + |<-- COMMAND_REQUEST -----| |
| 138 | + |-- prompt user | |
| 139 | + |-- COMMAND_GRANTED ----->| |
| 140 | + |<-- PlanResponse --------| |
| 141 | +``` |
| 142 | + |
| 143 | +### Key Architectural Decisions |
| 144 | + |
| 145 | +1. **Single file Projects**: Projects only currently support one file |
| 146 | +2. **Cloud-First**: UUIDs are valid "file paths" - enables seamless local/cloud switching |
| 147 | +3. **XCode Tools Injection**: On macOS, `xcode-tools` automatically prepended (most resources depend on it) |
| 148 | +4. **Test VM Strategy**: Uses Tart VMs with bind mounts (not copying) + file watcher for live config editing |
| 149 | +5. **OS Filtering**: Resources specify `os: ["Darwin", "Linux"]` for conditional inclusion |
| 150 | +6. **Secure Mode**: `--secure` flag forces sudo prompt for every command (no password caching) |
| 151 | + |
| 152 | +### Common Implementation Patterns |
| 153 | + |
| 154 | +1. **Plugin Resolution**: Local plugins use file paths (`.ts`/`.js`), network plugins use semver versions |
| 155 | +2. **Source Maps**: Preserved through entire parse → validate → plan flow for accurate error messages |
| 156 | +3. **Event Timing**: Events fire synchronously; use `ctx.once()` carefully to avoid race conditions |
| 157 | +4. **Process Cleanup**: Plugins must be killed on exit via `registerKillListeners` |
| 158 | +5. **Reporter Lifecycle**: Call `reporter.hide()` before synchronous output to prevent UI corruption |
| 159 | + |
| 160 | +### Testing Patterns |
| 161 | + |
| 162 | +- **Ink Component Tests**: Must polyfill `console.Console` for test environment: |
| 163 | + ```typescript |
| 164 | + import { Console } from 'node:console'; |
| 165 | + if (!console.Console) { |
| 166 | + console.Console = Console; |
| 167 | + } |
| 168 | + ``` |
| 169 | +- **Plugin Tests**: Use `StubReporter` to avoid UI initialization |
| 170 | +- **VM Tests**: `test` command uses Tart VMs with bind mounts for integration testing |
| 171 | + |
| 172 | +## Build & Distribution |
| 173 | + |
| 174 | +- **Framework**: oclif CLI framework with manifest generation |
| 175 | +- **Module System**: ES modules with NodeNext resolution |
| 176 | +- **Packaging**: `oclif pack tarballs` for multi-platform binaries |
| 177 | +- **Updates**: Self-updating via S3 (`@oclif/plugin-update`) |
| 178 | +- **Code Signing**: macOS notarization via `scripts/notarize.sh` |
| 179 | + |
| 180 | +## Common Gotchas |
| 181 | + |
| 182 | +1. **Import Paths**: Use `.js` extensions in imports even though files are `.ts` (ES module resolution) |
| 183 | +2. **Schema Validation**: Config changes require updating schemas in `@codifycli/schemas` package |
| 184 | +3. **Plugin IPC**: Plugins cannot directly read stdin (security isolation) |
| 185 | +4. **Sudo Caching**: Password cached in memory during session unless `--secure` flag used |
| 186 | +5. **File Watcher**: Use `persistent: false` option to prevent hanging processes |
| 187 | +6. **Linting**: ESLint enforces single quotes, specific import ordering, and strict type safety |
0 commit comments