diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 3653755..59ec7f9 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,8 +1,52 @@ +# # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +# language: "en-US" + +# tone_instructions: | +# Senior Engineer & Security Specialist. Review for: 1. Security (no leaks, URL/port validation). 2. Performance (prefer Promise.allSettled). 3. TUI/UX (intuitive CLI flows, handle cancellations). 4. Tests (full vitest coverage for new logic). + +# reviews: +# profile: "assertive" +# high_level_summary: true +# auto_review: +# enabled: true +# drafts: false + +# path_filters: +# - "!dist/**" +# - "!node_modules/**" +# - "!package-lock.json" +# - "src/**" +# - ".github/workflows/**" + +# path_instructions: +# - path: "src/commands/**/*.ts" +# instructions: | +# - Verify that all commands follow the modular registration pattern. +# - Ensure errors are handled gracefully and logged using the project's logger. +# - For TUI interactions, verify that `@vr_patel/tui` tools are used correctly. +# - path: "src/utils/config.ts" +# instructions: | +# - Ensure configuration keys are type-safe. +# - Verify that sensitive information is not stored in plain text if possible. +# - path: "src/__tests__/**/*.ts" +# instructions: | +# - Ensure mocks are clean and shared correctly. +# - Verify that tests cover both happy paths and error conditions. + +# pre_merge_checks: +# linked_issue_assessment: +# mode: "warning" + + +# updated + # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json + language: "en-US" -tone_instructions: | - Senior Engineer & Security Specialist. Review for: 1. Security (no leaks, URL/port validation). 2. Performance (prefer Promise.allSettled). 3. TUI/UX (intuitive CLI flows, handle cancellations). 4. Tests (full vitest coverage for new logic). +# ✅ Fix: tone_instructions was > 250 characters, causing a parse error +# and falling back to defaults on every review. +tone_instructions: "Senior Engineer. Review for: security (no leaks, validate inputs), performance (prefer Promise.allSettled), TUI/UX (handle cancellations), and full vitest test coverage." reviews: profile: "assertive" @@ -10,20 +54,17 @@ reviews: auto_review: enabled: true drafts: false - path_filters: - "!dist/**" - "!node_modules/**" - "!package-lock.json" - "src/**" - ".github/workflows/**" - path_instructions: - path: "src/commands/**/*.ts" instructions: | - Verify that all commands follow the modular registration pattern. - Ensure errors are handled gracefully and logged using the project's logger. - - For TUI interactions, verify that `@vr_patel/tui` tools are used correctly. - path: "src/utils/config.ts" instructions: | - Ensure configuration keys are type-safe. @@ -33,6 +74,6 @@ reviews: - Ensure mocks are clean and shared correctly. - Verify that tests cover both happy paths and error conditions. - pre_merge_checks: - linked_issue_assessment: - mode: "warning" +pre_merge_checks: + linked_issue_assessment: + mode: "warning" \ No newline at end of file diff --git a/docs/v1.2.1/health/README.md b/docs/v1.2.1/health/README.md index a9b1251..4b26df3 100644 --- a/docs/v1.2.1/health/README.md +++ b/docs/v1.2.1/health/README.md @@ -1,91 +1,154 @@ # `kdm health` — Health Status -**Version:** v1.2.1 +> **Version:** `v1.2.1` + +--- ## Overview -The `kdm health` command checks and reports the health status of Kubernetes pods or Docker containers. It provides insights into readiness, crash loops, and other health conditions. +The `kdm health` command checks and reports the health status of Kubernetes pods or Docker containers. It provides a clean, color-coded table with real-time status fetched directly from Docker and Kubernetes clients. + +> **Fix (Issue #1):** +> This command previously returned only a placeholder message. It now displays real health data from Docker containers and Kubernetes pods. + +--- -## Syntax +# Syntax ```bash kdm health ``` -## Parameters +--- + +# Parameters | Parameter | Description | Valid Values | -|-----------|-------------|--------------| +| :--- | :--- | :--- | | `target` | The workload type to check | `pods`, `containers`, `all` | -## Usage +--- -### `kdm health pods` +# Usage + +--- + +## `kdm health pods` Checks the health status of all Kubernetes pods. -**Example:** +### Example ```bash kdm health pods ``` -**Expected Output:** +### Expected Output -``` -Checking health for pods... -Showing health for pods... +```text +╭──────┬─────────────────────────────┬─────────┬──────────────────────────────────────╮ +│ TYPE │ NAME │ HEALTH │ DETAILS │ +├──────┼─────────────────────────────┼─────────┼──────────────────────────────────────┤ +│ pod │ node-app-6f65c56b74-7tkvf │ Running │ namespace: default, restarts: 0 │ +│ pod │ nginx-deployment-abc123 │ Failed │ namespace: production, restarts: 5 │ +╰──────┴─────────────────────────────┴─────────┴──────────────────────────────────────╯ ``` -**Health Indicators:** +### Health Indicators -- **Ready** — Pod is running and accepting traffic. -- **Unhealthy** — Pod is running but probes are failing. -- **CrashLoopBackOff** — Pod is repeatedly crashing and restarting. +| Status Color | Meaning | +| :--- | :--- | +| 🟢 Green | `healthy`, `running`, `Running` | +| 🔴 Red | `unhealthy`, `exited`, `Failed` | +| 🟡 Yellow | Other states like `Pending`, `paused` | -**Use Cases:** +### Use Cases -- Monitor pod health in real time. -- Identify failing services before they cause outages. +- Monitor pod health in real time +- Identify failing services before outages occur --- -### `kdm health containers` +## `kdm health containers` Checks the health status of all Docker containers. -**Example:** +### Example ```bash kdm health containers ``` -**Expected Output:** +### Expected Output -``` -Checking health for containers... -Showing health for containers... +```text +╭───────────┬────────────┬─────────┬───────────────╮ +│ TYPE │ NAME │ HEALTH │ DETAILS │ +├───────────┼────────────┼─────────┼───────────────┤ +│ container │ test-nginx │ running │ Up 8 minutes │ +│ container │ my-app │ exited │ Exited (1) │ +╰───────────┴────────────┴─────────┴───────────────╯ ``` -**Use Cases:** +### Use Cases -- Verify container health. -- Detect containers that are unhealthy or restarting. +- Verify container health +- Detect unhealthy or restarting containers --- -### `kdm health all` +## `kdm health all` -Checks health for both pods and containers in a single call. +Checks health for both pods and containers in a single command. If one source (Docker or Kubernetes) is unavailable, the other still renders successfully. -**Example:** +### Example ```bash kdm health all ``` -## Common Errors +--- + +### Expected Output (Kubernetes unavailable) + +```text +⚠ Kubernetes unavailable: connect ECONNREFUSED 127.0.0.1:8080 + +╭───────────┬────────────┬─────────┬──────────────╮ +│ TYPE │ NAME │ HEALTH │ DETAILS │ +├───────────┼────────────┼─────────┼──────────────┤ +│ container │ test-nginx │ running │ Up 8 minutes │ +╰───────────┴────────────┴─────────┴──────────────╯ +``` + +--- + +### Expected Output (Both available) + +```text +╭───────────┬───────────────────────────┬─────────┬──────────────────────────────────────╮ +│ TYPE │ NAME │ HEALTH │ DETAILS │ +├───────────┼───────────────────────────┼─────────┼──────────────────────────────────────┤ +│ container │ test-nginx │ running │ Up 8 minutes │ +│ pod │ node-app-6f65c56b74-7tkvf │ Running │ namespace: default, restarts: 0 │ +╰───────────┴───────────────────────────┴─────────┴──────────────────────────────────────╯ +``` + +--- + +# Common Errors + +| Error | Cause | Fix | +| :--- | :--- | :--- | +| `Unknown target` | Invalid target passed | Use `pods`, `containers`, or `all` | +| `No workloads found` | No containers or pods running | Start Docker containers or Kubernetes pods | +| `Docker unavailable` | Docker daemon not running | Run `sudo systemctl start docker` | +| `Kubernetes unavailable` | No cluster connection | Run `minikube start` or configure `kubectl` | + +--- + +# Notes -- **Invalid target** — Use `pods`, `containers`, or `all`. -- **Docker daemon not running** — Start Docker before running `kdm health containers`. -- **Kubernetes context not found** — Run `kubectl config get-contexts` to verify your cluster connection. +- Supports both Docker and Kubernetes environments +- Displays partial results even if one backend fails +- Provides readable CLI tables for faster debugging and monitoring \ No newline at end of file diff --git a/docs/v1.2.1/logs/README.md b/docs/v1.2.1/logs/README.md index 370f419..42ace9a 100644 --- a/docs/v1.2.1/logs/README.md +++ b/docs/v1.2.1/logs/README.md @@ -1,52 +1,143 @@ # `kdm logs` — Show Logs -**Version:** v1.2.1 +> **Version:** `v1.2.1` + +--- ## Overview -The `kdm logs` command retrieves and displays logs from a specified Docker container or Kubernetes pod. This is useful for debugging application issues and reviewing runtime events. +The `kdm logs` command retrieves and displays logs from a specified Docker container or Kubernetes pod. Docker is tried first, and Kubernetes is used automatically as a fallback if no matching container is found. + +> **Fix (Issue #1):** +> This command previously returned only a placeholder message. It now fetches real logs from Docker containers with automatic fallback to Kubernetes pods. + +--- -## Syntax +# Syntax ```bash kdm logs ``` -## Parameters +--- + +# Parameters | Parameter | Description | -|-----------|-------------| -| `name` | The name of the container or pod to fetch logs from | +| :--- | :--- | +| `name` | Container ID prefix, Docker container name, or Kubernetes pod name | + +--- + +# Usage + +--- + +## `kdm logs ` + +Fetches and displays the last 100 lines of logs for the specified workload. + +--- + +### Example (Docker Container) + +```bash +kdm logs test-nginx +``` + +### Expected Output + +```text +✔ Fetching logs for test-nginx... (0.0s) + +/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration +/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ +2026/05/17 06:43:38 [notice] 1#1: nginx/1.31.0 +2026/05/17 06:43:38 [notice] 1#1: start worker processes +``` + +--- -## Usage +### Example (Kubernetes Pod) -### `kdm logs ` +```bash +kdm logs node-app-6f65c56b74-7tkvf +``` + +### Expected Output + +```text +✔ Fetching logs for node-app-6f65c56b74-7tkvf... (0.0s) + +Server listening on port 3000 +Connected to database +GET /api/health 200 12ms +``` -Fetches and displays logs for the specified workload. +--- -**Example:** +### Example (Container ID Prefix) ```bash -kdm logs nginx-abc12 +kdm logs a1b2c3d4 ``` -**Expected Output:** +--- + +# How It Works +```text +Docker containers ──► match found? ──► show logs + │ + ▼ no match +Kubernetes pods ──► match found? ──► show logs + │ + ▼ no match +"No container or pod named X found" ``` -Fetching logs for nginx-abc12... -Logs for nginx-abc12 fetched -[timestamp] GET / 200 12ms -[timestamp] GET /favicon.ico 404 2ms + +### Workflow + +1. **Docker First** + - Searches running and stopped containers by ID prefix or container name + +2. **Kubernetes Fallback** + - If no Docker match is found, searches all Kubernetes pods across namespaces + +3. **Error Handling** + - Displays an error if neither Docker nor Kubernetes has a matching workload + +--- + +# Finding the Right Name + +```bash +# List Docker containers +docker ps + +# List Kubernetes pods +kubectl get pods --all-namespaces + +# Or use KDM itself +kdm show containers +kdm show pods ``` -**Use Cases:** +--- + +# Common Errors + +| Error | Cause | Fix | +| :--- | :--- | :--- | +| `No container or pod named X found` | Name does not match any workload | Use `kdm show containers` or `kdm show pods` | +| `Docker unavailable` | Docker daemon is not running | Run `sudo systemctl start docker` | +| `Failed to fetch logs` | Kubernetes API unreachable | Run `minikube start` or configure `kubectl` | -- Debug application errors by reviewing container/pod logs. -- Monitor application events in real time. -- Investigate crash or restart causes. +--- -## Common Errors +# Notes -- **Name not found** — Verify the container or pod name using `kdm show containers` or `kdm show pods`. -- **Docker daemon not running** — Start Docker before fetching container logs. -- **Kubernetes context not found** — Ensure `kubectl` is configured with a valid cluster context. +- Automatically falls back from Docker to Kubernetes +- Supports container name and ID prefix matching +- Displays real-time logs directly from workloads +- Retrieves the last 100 log lines for faster debugging \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index eb3212d..bb0ec92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "commander": "^12.0.0", "conf": "^15.1.0", "cosmiconfig": "^9.0.0", - "dockerode": "^4.0.0", + "dockerode": "^4.0.2", "ink": "^4.4.1", "nodemailer": "^8.0.7", "react": "^18.2.0" @@ -73,8 +73,7 @@ "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "license": "Apache-2.0" + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -528,55 +527,6 @@ "node": ">=18" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", - "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -629,16 +579,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/@kubernetes/client-node": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.20.0.tgz", @@ -664,70 +604,6 @@ "openid-client": "^5.3.0" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause" - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", @@ -1533,8 +1409,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", @@ -1549,7 +1424,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1574,7 +1448,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1833,73 +1706,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -1912,24 +1718,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2182,7 +1970,6 @@ "version": "5.0.7", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.7.tgz", "integrity": "sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==", - "license": "Apache-2.0", "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", @@ -2194,18 +1981,13 @@ } }, "node_modules/dockerode": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.12.tgz", - "integrity": "sha512-/bCZd6KlGcjZO8Buqmi/vXuqEGVEZ0PNjx/biBNqJD3MhK9DmdiAuKxqfNhflgDESDIiBz3qF+0e55+CpnrUcw==", - "license": "Apache-2.0", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz", + "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==", "dependencies": { "@balena/dockerignore": "^1.0.2", - "@grpc/grpc-js": "^1.11.1", - "@grpc/proto-loader": "^0.7.13", - "docker-modem": "^5.0.7", - "protobufjs": "^7.3.2", - "tar-fs": "^2.1.4", - "uuid": "^10.0.0" + "docker-modem": "^5.0.3", + "tar-fs": "~2.0.1" }, "engines": { "node": ">= 8.0" @@ -2281,7 +2063,6 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -2391,15 +2172,6 @@ "@esbuild/win32-x64": "0.27.7" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -2587,8 +2359,7 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-minipass": { "version": "2.1.0", @@ -2638,15 +2409,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -2832,8 +2594,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/import-fresh": { "version": "3.3.1", @@ -2866,8 +2627,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ink": { "version": "4.4.1", @@ -3180,18 +2940,6 @@ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3344,8 +3092,7 @@ "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "node_modules/mlly": { "version": "1.8.2", @@ -3379,10 +3126,9 @@ } }, "node_modules/nan": { - "version": "2.26.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", - "integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==", - "license": "MIT", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz", + "integrity": "sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==", "optional": true }, "node_modules/nanoid": { @@ -3485,7 +3231,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", "dependencies": { "wrappy": "1" } @@ -3750,30 +3495,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/protobufjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", - "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.1", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -3790,7 +3511,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -3853,7 +3573,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3933,15 +3652,6 @@ "uuid": "bin/uuid" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4149,8 +3859,7 @@ "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", - "license": "ISC" + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" }, "node_modules/ssh2": { "version": "1.17.0", @@ -4233,7 +3942,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -4409,28 +4117,25 @@ } }, "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^2.0.0" } }, "node_modules/tar-fs/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -4706,22 +4411,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/verror": { "version": "1.10.0", @@ -5450,8 +5140,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.20.0", @@ -5474,48 +5163,12 @@ } } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/yocto-queue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", diff --git a/package.json b/package.json index 632106f..8062459 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "scripts": { "build": "tsup src/index.ts --format esm --dts", "dev": "tsup src/index.ts --format esm --watch", - "test": "vitest run", + "test": "vitest run --pool=forks", "prepublishOnly": "npm run build" }, "keywords": [ diff --git a/src/__tests__/health.test.ts b/src/__tests__/health.test.ts index ba0b47a..38f80c6 100644 --- a/src/__tests__/health.test.ts +++ b/src/__tests__/health.test.ts @@ -1,21 +1,65 @@ +// import { describe, it, expect, vi, beforeEach } from 'vitest'; +// import { Command } from 'commander'; +// import { registerHealthCommand } from '../commands/health'; +// import { logger } from '../utils/logger'; + +// vi.mock('../utils/logger', () => ({ +// logger: { +// info: vi.fn(), +// }, +// })); + +// vi.mock('../ui/spinner', () => ({ +// createSpinner: vi.fn(() => ({ +// start: vi.fn().mockReturnThis(), +// stop: vi.fn().mockReturnThis(), +// fail: vi.fn().mockReturnThis(), +// })), +// })); + +// describe('health command', () => { +// let program: Command; + +// beforeEach(() => { +// vi.clearAllMocks(); +// program = new Command(); +// registerHealthCommand(program); +// }); + +// it('should register health command', () => { +// const healthCmd = program.commands.find((c) => c.name() === 'health'); +// expect(healthCmd).toBeDefined(); +// }); + +// it('should call logger.info on health ', async () => { +// await program.parseAsync(['node', 'test', 'health', 'all']); +// expect(logger.info).toHaveBeenCalledWith('Showing health for all...'); +// }); +// }); + + +// updated + import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Command } from 'commander'; import { registerHealthCommand } from '../commands/health'; +import { getRunningContainers } from '../docker/containers'; +import { getRunningPods } from '../kubernetes/pods'; import { logger } from '../utils/logger'; +import * as tableUtils from '../ui/table'; +vi.mock('../docker/containers', () => ({ getRunningContainers: vi.fn() })); +vi.mock('../kubernetes/pods', () => ({ getRunningPods: vi.fn() })); vi.mock('../utils/logger', () => ({ - logger: { - info: vi.fn(), - }, + logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, })); - vi.mock('../ui/spinner', () => ({ createSpinner: vi.fn(() => ({ start: vi.fn().mockReturnThis(), - stop: vi.fn().mockReturnThis(), - fail: vi.fn().mockReturnThis(), + stop: vi.fn().mockReturnThis(), })), })); +vi.mock('../ui/table', () => ({ renderTable: vi.fn() })); describe('health command', () => { let program: Command; @@ -26,13 +70,126 @@ describe('health command', () => { registerHealthCommand(program); }); - it('should register health command', () => { + it('should register the health command', () => { const healthCmd = program.commands.find((c) => c.name() === 'health'); expect(healthCmd).toBeDefined(); }); - it('should call logger.info on health ', async () => { + it('should render a table with containers and pods for "health all"', async () => { + vi.mocked(getRunningContainers).mockResolvedValue([ + { id: '1', name: 'web', image: 'nginx', state: 'running', status: 'Up 2 hours' }, + ]); + vi.mocked(getRunningPods).mockResolvedValue([ + { name: 'api', namespace: 'default', status: 'Running', restarts: 0 }, + ]); + await program.parseAsync(['node', 'test', 'health', 'all']); + expect(logger.info).toHaveBeenCalledWith('Showing health for all...'); + expect(tableUtils.renderTable).toHaveBeenCalledWith( + expect.objectContaining({ + head: ['TYPE', 'NAME', 'HEALTH', 'DETAILS'], + rows: expect.arrayContaining([ + expect.arrayContaining(['container', 'web']), + expect.arrayContaining(['pod', 'api']), + ]), + }), + ); + }); + + it('should render only containers when target is "containers"', async () => { + vi.mocked(getRunningContainers).mockResolvedValue([ + { id: '2', name: 'nginx', image: 'nginx', state: 'running', status: 'Up 5 minutes' }, + ]); + + await program.parseAsync(['node', 'test', 'health', 'containers']); + + expect(getRunningPods).not.toHaveBeenCalled(); + expect(tableUtils.renderTable).toHaveBeenCalledWith( + expect.objectContaining({ + rows: expect.arrayContaining([ + expect.arrayContaining(['container', 'nginx']), + ]), + }), + ); + }); + + it('should render only pods when target is "pods"', async () => { + vi.mocked(getRunningPods).mockResolvedValue([ + { name: 'worker', namespace: 'staging', status: 'Running', restarts: 1 }, + ]); + + await program.parseAsync(['node', 'test', 'health', 'pods']); + + expect(getRunningContainers).not.toHaveBeenCalled(); + expect(tableUtils.renderTable).toHaveBeenCalledWith( + expect.objectContaining({ + rows: expect.arrayContaining([ + expect.arrayContaining(['pod', 'worker']), + ]), + }), + ); + }); + + it('should warn and NOT render a table when no workloads are found', async () => { + vi.mocked(getRunningContainers).mockResolvedValue([]); + vi.mocked(getRunningPods).mockResolvedValue([]); + + await program.parseAsync(['node', 'test', 'health', 'all']); + + expect(logger.warn).toHaveBeenCalledWith('No workloads found.'); + expect(tableUtils.renderTable).not.toHaveBeenCalled(); + }); + + it('should log an error for unknown targets', async () => { + await program.parseAsync(['node', 'test', 'health', 'bad-target']); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('Unknown target'), + ); + expect(tableUtils.renderTable).not.toHaveBeenCalled(); + }); + + it('should log a warning when fetching containers throws', async () => { + vi.mocked(getRunningContainers).mockRejectedValue(new Error('Docker connection failed')); + + await program.parseAsync(['node', 'test', 'health', 'containers']); + + expect(logger.warn).toHaveBeenCalledWith( + expect.stringContaining('Docker unavailable'), + ); + expect(tableUtils.renderTable).not.toHaveBeenCalled(); + }); + + it('should log a warning when fetching pods throws', async () => { + vi.mocked(getRunningContainers).mockResolvedValue([]); + vi.mocked(getRunningPods).mockRejectedValue(new Error('K8s API unreachable')); + + await program.parseAsync(['node', 'test', 'health', 'pods']); + + expect(logger.warn).toHaveBeenCalledWith( + expect.stringContaining('Kubernetes unavailable'), + ); + expect(tableUtils.renderTable).not.toHaveBeenCalled(); + }); + + it('should render available pods when containers fail for "health all"', async () => { + vi.mocked(getRunningContainers).mockRejectedValue(new Error('Docker connection failed')); + vi.mocked(getRunningPods).mockResolvedValue([ + { name: 'api', namespace: 'default', status: 'Running', restarts: 0 }, + ]); + + await program.parseAsync(['node', 'test', 'health', 'all']); + + expect(logger.warn).toHaveBeenCalledWith( + expect.stringContaining('Docker unavailable'), + ); + expect(tableUtils.renderTable).toHaveBeenCalledWith( + expect.objectContaining({ + rows: expect.arrayContaining([ + expect.arrayContaining(['pod', 'api']), + ]), + }), + ); }); -}); +}); \ No newline at end of file diff --git a/src/commands/health.ts b/src/commands/health.ts index d662144..49695c9 100644 --- a/src/commands/health.ts +++ b/src/commands/health.ts @@ -1,22 +1,115 @@ +// import { Command } from 'commander'; +// import { logger } from '../utils/logger'; +// import { createSpinner } from '../ui/spinner'; + +// export const registerHealthCommand = (program: Command) => { +// program +// .command('health ') +// .description('Show health status for pods or containers') +// .action(async (target) => { +// const spinner = createSpinner(`Checking health for ${target}...`).start(); +// try { +// // TODO: Implement actual health check logic +// spinner.stop(`Health check for ${target} complete`); +// logger.info(`Showing health for ${target}...`); +// } catch (error) { +// const errorMessage = (error as Error).message; +// spinner.fail(`Health check for ${target} failed: ${errorMessage}`); +// logger.error(`Health check for ${target} failed: ${errorMessage}`, error); +// throw error; +// } +// }); +// }; + + +// updated + import { Command } from 'commander'; +import chalk from 'chalk'; +import { getRunningContainers } from '../docker/containers'; +import { getRunningPods } from '../kubernetes/pods'; import { logger } from '../utils/logger'; import { createSpinner } from '../ui/spinner'; +import { renderTable } from '../ui/table'; + +const healthColor = (status: string): string => { + if (status === 'healthy' || status === 'running' || status === 'Running') { + return chalk.green(status); + } + if (status === 'unhealthy' || status === 'exited' || status === 'Failed') { + return chalk.red(status); + } + return chalk.yellow(status); +}; + +export const showHealth = async (target: string): Promise => { + logger.info?.(`Showing health for ${target}...`); + + const validTargets = ['all', 'containers', 'pods']; + if (!validTargets.includes(target)) { + logger.error?.( + `Unknown target: ${target}. Valid targets are: ${validTargets.join(', ')}.`, + ); + process.exitCode = 1; + return; + } -export const registerHealthCommand = (program: Command) => { + const spinner = createSpinner(`Checking ${target} health...`).start(); + const rows: (string | number)[][] = []; + + if (target === 'all' || target === 'containers') { + try { + const containers = await getRunningContainers(); + rows.push( + ...containers.map((container) => [ + 'container', + container.name, + healthColor(container.state), + container.status, + ]), + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn?.(`Docker unavailable: ${message}`); + } + } + + if (target === 'all' || target === 'pods') { + try { + const pods = await getRunningPods(); + rows.push( + ...pods.map((pod) => [ + 'pod', + pod.name, + healthColor(pod.status), + `namespace: ${pod.namespace}, restarts: ${pod.restarts}`, + ]), + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn?.(`Kubernetes unavailable: ${message}`); + } + } + + spinner.stop(); + + if (rows.length === 0) { + logger.warn?.(`No ${target === 'all' ? 'workloads' : target} found.`); + return; + } + + renderTable({ + head: ['TYPE', 'NAME', 'HEALTH', 'DETAILS'], + rows, + }); +}; + +export const registerHealthCommand = (program: Command): void => { program .command('health ') - .description('Show health status for pods or containers') - .action(async (target) => { - const spinner = createSpinner(`Checking health for ${target}...`).start(); - try { - // TODO: Implement actual health check logic - spinner.stop(`Health check for ${target} complete`); - logger.info(`Showing health for ${target}...`); - } catch (error) { - const errorMessage = (error as Error).message; - spinner.fail(`Health check for ${target} failed: ${errorMessage}`); - logger.error(`Health check for ${target} failed: ${errorMessage}`, error); - throw error; - } - }); -}; + .description( + 'Show health status for pods, containers, or all workloads.\n' + + 'Valid targets: all | containers | pods', + ) + .action(showHealth); +}; \ No newline at end of file diff --git a/src/commands/logs.ts b/src/commands/logs.ts index 1fbf3a6..444607b 100644 --- a/src/commands/logs.ts +++ b/src/commands/logs.ts @@ -1,22 +1,114 @@ +// import { Command } from 'commander'; +// import { logger } from '../utils/logger'; +// import { createSpinner } from '../ui/spinner'; + +// export const registerLogsCommand = (program: Command) => { +// program +// .command('logs ') +// .description('Show logs for a container or pod') +// .action(async (name) => { +// const spinner = createSpinner(`Fetching logs for ${name}...`).start(); +// try { +// // TODO: Implement actual log fetching logic +// spinner.stop(`Logs for ${name} fetched`); +// logger.info(`Showing logs for ${name}...`); +// } catch (error) { +// const errorMessage = (error as Error).message; +// spinner.fail(`Failed to fetch logs for ${name}: ${errorMessage}`); +// logger.error(`Failed to fetch logs for ${name}: ${errorMessage}`, error); +// throw error; +// } +// }); +// }; + + +// updated + import { Command } from 'commander'; +import { getDockerClient } from '../docker/client'; +import { getK8sApi } from '../kubernetes/client'; import { logger } from '../utils/logger'; import { createSpinner } from '../ui/spinner'; -export const registerLogsCommand = (program: Command) => { - program - .command('logs ') - .description('Show logs for a container or pod') - .action(async (name) => { - const spinner = createSpinner(`Fetching logs for ${name}...`).start(); - try { - // TODO: Implement actual log fetching logic - spinner.stop(`Logs for ${name} fetched`); - logger.info(`Showing logs for ${name}...`); - } catch (error) { - const errorMessage = (error as Error).message; - spinner.fail(`Failed to fetch logs for ${name}: ${errorMessage}`); - logger.error(`Failed to fetch logs for ${name}: ${errorMessage}`, error); - throw error; - } +// ✅ CodeRabbit (Nitpick): simplified — Buffer and non-Buffer values both +// convert correctly with String(), no branch needed +const printStream = (value: unknown): void => + void process.stdout.write(String(value)); + +export const showLogs = async (name: string): Promise => { + if (!name?.trim()) { + logger.error?.('A container ID prefix, container name, or pod name is required.'); + return; + } + + logger.info?.(`Showing logs for ${name}...`); + const spinner = createSpinner(`Fetching logs for ${name}...`).start(); + // Docker first + try { + const docker = getDockerClient(); + const containers = await docker.listContainers({ all: true }); + + const match = containers.find( + (container) => + container.Id.startsWith(name) || + container.Names.some( + (containerName) => containerName.replace(/^\//, '') === name, + ), + ); + + if (match) { + const output = await docker + .getContainer(match.Id) + .logs({ stdout: true, stderr: true, tail: 100 }); + spinner.stop(); + printStream(output); + return; + } + } catch (error) { + // ✅ CodeRabbit (Minor): log why Docker failed instead of swallowing silently + logger.debug?.( + `Docker unavailable, trying Kubernetes: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } + + // Kubernetes fallback + try { + const api = getK8sApi(); + const pods = await api.listPodForAllNamespaces(); + const pod = pods.body.items.find((item) => item.metadata?.name === name); + + if (!pod?.metadata?.name || !pod.metadata.namespace) { + spinner.stop(); + logger.error?.(`No container or pod named "${name}" found.`); + return; + } + + // ✅ CodeRabbit (Major): use options-object form — NOT 10 positional undefineds + // The original: readNamespacedPodLog(name, ns, undef, undef, ... x8 ..., 100) + // is fragile; this form is forward-compatible with library version changes. + const response = await api.readNamespacedPodLog({ + name: pod.metadata.name, + namespace: pod.metadata.namespace, + tailLines: 100, }); + + spinner.stop(); + printStream(response.body); + } catch (error) { + spinner.stop(); + const message = error instanceof Error ? error.message : String(error); + logger.error?.(`Failed to fetch logs for "${name}": ${message}`); + } }; + +export const registerLogsCommand = (program: Command): void => { + program + .command('logs ') + .description( + 'Show logs for a container or pod.\n' + + 'Accepts a container ID prefix, container name, or pod name.', + ) + .action(showLogs); +}; \ No newline at end of file