diff --git a/.eslintrc.json b/.eslintrc.json index e11cb306..44d7c2ba 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,6 +18,8 @@ "unicorn/no-array-for-each": "off", "unicorn/prefer-object-from-entries": "off", "unicorn/prefer-type-error": "off", + "unicorn/no-static-only-class": "off", + "@typescript-eslint/no-duplicate-enum-values": "off", "quotes": [ "error", "single" diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index f5ffe6d1..74aaad0c 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -7,15 +7,18 @@ on: ['push'] jobs: build-and-test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 - - run: echo -e "\n//npm.pkg.github.com/:_authToken=${{ secrets.PACKAGES_PAT_GITHUB }}" >> ./.npmrc - - name: Use Node.js 20 + - name: Use Node.js 22 uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' cache: 'npm' - run: npm ci - - run: npm test + - run: npm run test diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 45ed2122..00000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@codifycli:registry=https://npm.pkg.github.com diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..47bb7c3c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,187 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +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." + +## Development Commands + +### Building +```bash +npm run build # Build TypeScript to dist/ +npm run lint # Type-check with tsc +``` + +### Testing +```bash +npm test # Run all tests with Vitest +npm test -- path/to/test # Run specific test file +npm run posttest # Runs lint after tests +``` + +### Running Locally +```bash +./bin/dev.js # Run CLI in development mode +./bin/dev.js apply # Example: run apply command +``` + +### Test Command (VM Testing) +The `test` command spins up a Tart VM to test Codify configs in isolation: +```bash +./bin/dev.js test --vm-os darwin # Test on macOS VM +./bin/dev.js test --vm-os linux # Test on Linux VM +``` + +## High-Level Architecture + +### Core Architectural Patterns + +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. + +2. **Multi-Process Plugin System**: The most unique architectural decision is running plugins as separate Node.js child processes communicating via IPC: + - **Why**: Isolation (crashes don't crash CLI), security (parent controls sudo), flexibility + - **Plugin Process** (`src/plugins/plugin-process.ts`): Spawns plugins using `fork()` + - **IPC Protocol** (`src/plugins/plugin-message.ts`): Type-safe message passing + - **Security**: Plugins run isolated; parent process controls all sudo operations + - When plugins need sudo, they send `COMMAND_REQUEST` events back to parent + +3. **Event-Driven Architecture**: Central event bus (`src/events/context.ts`) using EventEmitter: + - Tracks process/subprocess lifecycle (PLAN, APPLY, INITIALIZE_PLUGINS, etc.) + - Enables plugin-to-CLI communication (sudo prompts, login credentials, etc.) + - Powers progress tracking for UI + +4. **Reporter Pattern**: Abstract `Reporter` interface with multiple implementations selected via `--output` flag: + - `DefaultReporter`: Rich Ink-based TUI with React components + - `PlainReporter`: Simple text output + - `JsonReporter`: Machine-readable JSON + - `DebugReporter`: Verbose logging + - `StubReporter`: No-op for testing + +5. **Resource Lifecycle State Machine**: + ``` + Parse Config → Validate → Resolve Dependencies → Plan → Apply + ``` + - **ResourceConfig**: Desired state from config file + - **Plan**: Computed difference between desired and current state + - **ResourcePlan**: Per-resource operations (CREATE, UPDATE, DELETE, NOOP) + - **Project**: Container with dependency graph + +6. **Dependency Resolution**: + - Explicit: `dependsOn` field in config + - Implicit: Extracted from parameter references (e.g., `${other-resource.param}`) + - Plugin-level: Plugins declare type dependencies (e.g., xcode-tools on macOS) + - Topological sort ensures correct evaluation order (`src/utils/dependency-graph-resolver.ts`) + +### Key Directory Structure + +- **`/src/orchestrators/`**: Business logic layer - each file implements one CLI command's workflow + - `plan.ts`: Parse → Validate → Resolve deps → Generate plan + - `apply.ts`: Execute plan after user confirmation + - `import.ts`: Import existing resources into config + - `test.ts`: VM-based testing with live config sync via file watcher + +- **`/src/plugins/`**: Plugin infrastructure + - `plugin-manager.ts`: Registry routing operations to plugins + - `plugin-process.ts`: Child process lifecycle and IPC + - `plugin.ts`: High-level plugin API + +- **`/src/entities/`**: Domain models with rich behavior + - `Project`: Container with dependency resolution + - `ResourceConfig`: Mutable config with dependency tracking + - `Plan`: Immutable plan with sorting/filtering + +- **`/src/parser/`**: Multi-format config parsing (JSON, JSONC, JSON5, YAML) + - All parsers maintain source maps for error messages + - Cloud parser fetches from Dashboard API via UUID + +- **`/src/ui/`**: User interface layer + - `/reporters/`: Output strategy implementations + - `/components/`: React components for Ink TUI + - `/store/`: Jotai state management for UI + +- **`/src/connect/`**: Dashboard integration + - WebSocket server for persistent connection + - OAuth flow handling + - JWT credential management + +- **`/src/generators/`**: Config file writers + - Computes diffs for updating existing configs + - Writes to local files or cloud (via Dashboard API) + +### Important Data Flows + +**Apply Command Flow:** +``` +ApplyOrchestrator.run() + → PlanOrchestrator.run() + → PluginInitOrchestrator.run() + → Parse configs → Project + → PluginManager.initialize() → ResourceDefinitions + → Project.resolveDependencies() + → PluginManager.plan() → Plan + → Reporter.promptConfirmation() + → PluginManager.apply() + → For each resource (topologically sorted): + → Plugin.apply() [IPC to child process] +``` + +**Plugin Communication Flow:** +``` +Parent Process Plugin Process + |-- initialize() -------->| + |<-- resourceDefinitions -| + |-- plan(resource) ------>| + | [Plugin needs sudo] + |<-- COMMAND_REQUEST -----| + |-- prompt user | + |-- COMMAND_GRANTED ----->| + |<-- PlanResponse --------| +``` + +### Key Architectural Decisions + +1. **Single file Projects**: Projects only currently support one file +2. **Cloud-First**: UUIDs are valid "file paths" - enables seamless local/cloud switching +3. **XCode Tools Injection**: On macOS, `xcode-tools` automatically prepended (most resources depend on it) +4. **Test VM Strategy**: Uses Tart VMs with bind mounts (not copying) + file watcher for live config editing +5. **OS Filtering**: Resources specify `os: ["Darwin", "Linux"]` for conditional inclusion +6. **Secure Mode**: `--secure` flag forces sudo prompt for every command (no password caching) + +### Common Implementation Patterns + +1. **Plugin Resolution**: Local plugins use file paths (`.ts`/`.js`), network plugins use semver versions +2. **Source Maps**: Preserved through entire parse → validate → plan flow for accurate error messages +3. **Event Timing**: Events fire synchronously; use `ctx.once()` carefully to avoid race conditions +4. **Process Cleanup**: Plugins must be killed on exit via `registerKillListeners` +5. **Reporter Lifecycle**: Call `reporter.hide()` before synchronous output to prevent UI corruption + +### Testing Patterns + +- **Ink Component Tests**: Must polyfill `console.Console` for test environment: + ```typescript + import { Console } from 'node:console'; + if (!console.Console) { + console.Console = Console; + } + ``` +- **Plugin Tests**: Use `StubReporter` to avoid UI initialization +- **VM Tests**: `test` command uses Tart VMs with bind mounts for integration testing + +## Build & Distribution + +- **Framework**: oclif CLI framework with manifest generation +- **Module System**: ES modules with NodeNext resolution +- **Packaging**: `oclif pack tarballs` for multi-platform binaries +- **Updates**: Self-updating via S3 (`@oclif/plugin-update`) +- **Code Signing**: macOS notarization via `scripts/notarize.sh` + +## Common Gotchas + +1. **Import Paths**: Use `.js` extensions in imports even though files are `.ts` (ES module resolution) +2. **Schema Validation**: Config changes require updating schemas in `@codifycli/schemas` package +3. **Plugin IPC**: Plugins cannot directly read stdin (security isolation) +4. **Sudo Caching**: Password cached in memory during session unless `--secure` flag used +5. **File Watcher**: Use `persistent: false` option to prevent hanging processes +6. **Linting**: ESLint enforces single quotes, specific import ordering, and strict type safety \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..520ae3fd --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Kevin Wang + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 7b245d41..3425398f 100644 --- a/README.md +++ b/README.md @@ -1,325 +1,355 @@ -oclif-hello-world -================= - -oclif example Hello World CLI - -[![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io) -[![CircleCI](https://circleci.com/gh/oclif/hello-world/tree/main.svg?style=shield)](https://circleci.com/gh/oclif/hello-world/tree/main) -[![GitHub license](https://img.shields.io/github/license/oclif/hello-world)](https://github.com/oclif/hello-world/blob/main/LICENSE) - - -* [Usage](#usage) -* [Commands](#commands) - -# Usage - -```sh-session -$ npm install -g codify -$ codify COMMAND -running command... -$ codify (--version) -codify/0.9.0 darwin-arm64 node-v20.15.1 -$ codify --help [COMMAND] -USAGE - $ codify COMMAND -... -``` - -# Commands - -* [`codify apply`](#codify-apply) -* [`codify destroy`](#codify-destroy) -* [`codify help [COMMAND]`](#codify-help-command) -* [`codify import`](#codify-import) -* [`codify init`](#codify-init) -* [`codify plan`](#codify-plan) -* [`codify update [CHANNEL]`](#codify-update-channel) -* [`codify validate`](#codify-validate) - -## `codify apply` - -Install or update resources on the system based on a codify.jsonc file. - +# Codify - Your Development Environment as Code + +**Stop manually setting up your development environment. Define it once, replicate it everywhere.** + +Codify is a command-line tool that brings the power of Infrastructure as Code (IaC) to your local development machine. Manage system settings, install packages, configure tools, and automate your entire setup using a simple, declarative configuration file—just like you manage your infrastructure with Terraform. + +

+ Website • + Web Editor • + Documentation +

+ +

+ oclif + Github Actions + License +

+ +--- + +## The Problem + +Every developer has been there: +- **New machine?** Spend hours reinstalling and configuring everything +- **Team onboarding?** Send them a scattered wiki page of manual installation steps +- **Multiple machines?** Keep them in sync manually +- **What's installed?** No clear record of your development environment +- **Configuration drift?** Your laptop works differently than your colleague's + +## The Solution + +With Codify, your entire development environment is defined in a single `codify.jsonc` file: + +```jsonc +[ + { + "type": "homebrew", + "formulae": ["git", "node"] + }, + { + "type": "vscode" + }, + { + "type": "docker" + } +] ``` -USAGE - $ codify apply [--debug] [-o plain|default|json] [-p ] [-S ] - -FLAGS - -S, --sudoPassword= Automatically use this password for any commands that require elevated permissions. - -o, --output=