npm package: @locotineee/cli
Description: CLI tool for adding NestJS modules from a registry (shadcn-style)
Latest version: 0.1.2

# @locotineee/cli

A shadcn-style CLI tool for adding pre-built NestJS modules to your project. Instead of installing opaque npm packages, this CLI copies module source code directly into your project — giving you full ownership and customization ability.

## Table of Contents

- [How It Works](#how-it-works)
- [Quick Start](#quick-start)
- [Commands](#commands)
  - [init](#init)
  - [list](#list)
  - [add](#add)
- [Configuration](#configuration)
- [Registry Format](#registry-format)
- [Available Modules](#available-modules)
- [Self-Hosting a Registry](#self-hosting-a-registry)
- [Troubleshooting](#troubleshooting)

---

## How It Works

```
┌──────────┐     fetch      ┌──────────────┐     copy      ┌──────────────┐
│  CLI     │ ──────────────>│   Registry   │ ──────────────>│  Your Project│
│  (local) │                │   (remote)   │                │  src/modules/│
└──────────┘                └──────────────┘                └──────────────┘
                                                                   │
                                                            ┌──────┴───────┐
                                                            │ Auto-wires   │
                                                            │ app.module.ts│
                                                            │ package.json │
                                                            │ .env.example │
                                                            └──────────────┘
```

1. **Fetch** — The CLI downloads the module descriptor (`component.json`) from a remote registry
2. **Copy** — Template files are fetched and written to your `src/modules/<name>/` directory
3. **Wire** — The CLI auto-modifies `app.module.ts` (via TypeScript AST), `package.json`, and `.env.example`

You own the code. Edit it however you want after installation.

---

## Quick Start

```bash
# 1. Initialize the CLI config (one-time setup)
npx @locotineee/cli init

# 2. See what modules are available
npx @locotineee/cli list

# 3. Add a module to your project
npx @locotineee/cli add health

# 4. Install new dependencies that were added
npm install
```

### Running from the monorepo (development)

If you're developing within this monorepo, you can run the CLI directly:

```bash
# From the project root
node packages/cli/dist/cli.js init
node packages/cli/dist/cli.js list
node packages/cli/dist/cli.js add health
```

---

## Commands

### `init`

Initializes the CLI configuration for your project. Creates a `nestjs-cli.config.json` file in your project root.

```bash
npx @locotineee/cli init
```

**What it does:**

1. Reads your `nest-cli.json` to detect project structure
2. If monorepo, prompts you to select which app to target
3. Prompts for a registry URL (defaults to the official registry)
4. Writes `nestjs-cli.config.json`

**Monorepo detection:**

The CLI reads `nest-cli.json`. If `"monorepo": true`, it lists all application projects and lets you pick one:

```
? Select target application:
❯ nestjs-ultimate  (apps/nestjs-ultimate/src)
  nestjs-starter   (apps/nestjs-starter/src)
```

**Output file (`nestjs-cli.config.json`):**

```json
{
  "registryUrl": "https://raw.githubusercontent.com/AggregatorIcapaci/nestjs-registry/main",
  "modulesDir": "apps/nestjs-ultimate/src/modules",
  "appModulePath": "apps/nestjs-ultimate/src/app.module.ts",
  "targetApp": "nestjs-ultimate"
}
```

---

### `list`

Lists all available modules in the configured registry.

```bash
npx @locotineee/cli list
```

**Example output:**

```
Available modules (registry v1.0.0)

  Name      Version   Description
  ──────────────────────────────────────────
  health    1.0.0     Health check endpoints using @nestjs/terminus [infrastructure, monitoring]

  Total: 1 module(s)
```

---

### `add`

Adds a module from the registry into your project.

```bash
npx @locotineee/cli add <module-name>
```

**Example:**

```bash
npx @locotineee/cli add health
```

**What it does (step by step):**

1. **Loads config** — Reads `nestjs-cli.config.json`. If missing, tells you to run `init` first.

2. **Validates module** — Fetches `registry.json` and confirms the module exists.

3. **Checks prerequisites** — Some modules depend on others. For example, `health` requires `DatabaseModule` to be present in `app.module.ts`. If a prerequisite is missing, the CLI aborts with a clear message.

4. **Checks idempotency** — If the module directory already exists, prompts whether to overwrite.

5. **Shows installation plan** — Displays what will be installed:
   ```
   Adding module: health v1.0.0
   ℹ Health check module with database, disk, and memory indicators

     Files: 5
     Dependencies: @nestjs/terminus
     Env vars: HEALTH_DISK_THRESHOLD_PERCENT, HEALTH_MEMORY_HEAP_MB

   ? Proceed with installation? (Y/n)
   ```

6. **Copies files** — Downloads and writes all module files:
   ```
   ✔ Created apps/nestjs-ultimate/src/modules/health/health.module.ts
   ✔ Created apps/nestjs-ultimate/src/modules/health/health.controller.ts
   ✔ Created apps/nestjs-ultimate/src/modules/health/indicators/database.indicator.ts
   ✔ Created apps/nestjs-ultimate/src/modules/health/indicators/disk.indicator.ts
   ✔ Created apps/nestjs-ultimate/src/modules/health/indicators/memory.indicator.ts
   ```

7. **Auto-wires `app.module.ts`** — Uses TypeScript AST manipulation (ts-morph) to safely:
   - Add `import { HealthModule } from '@modules/health/health.module';`
   - Insert `HealthModule` into the `@Module({ imports: [...] })` array under the correct section comment

8. **Updates `package.json`** — Adds any new dependencies:
   ```
   ✔ Added dependency: @nestjs/terminus@^11.0.0
   ```

9. **Updates `.env.example`** — Appends new environment variables:
   ```
   # Disk usage threshold for health check (0-1)
   HEALTH_DISK_THRESHOLD_PERCENT=0.9
   # Max heap memory in MB for health check
   HEALTH_MEMORY_HEAP_MB=300
   ```

10. **Prints post-install message**:
    ```
    ✔ Module "health" added successfully!
    ℹ Run `npm install` to install @nestjs/terminus, then start your app.
    ```

**After installation, remember to:**

```bash
# Install any new dependencies that were added to package.json
npm install

# Start the app
npm run start:dev

# Test the health endpoint
curl http://localhost:3000/api/v1/health
```

---

## Configuration

### `nestjs-cli.config.json`

Created by `init`. All fields:

| Field | Type | Description |
|-------|------|-------------|
| `registryUrl` | `string` | Base URL of the module registry |
| `modulesDir` | `string` | Where module files are copied to (relative to project root) |
| `appModulePath` | `string` | Path to `app.module.ts` for auto-wiring (relative to project root) |
| `targetApp` | `string` | Name of the target application (from `nest-cli.json`) |

You can edit this file manually. For example, to point to a different registry or change the modules directory.

---

## Registry Format

The registry is a static file server (GitHub raw, S3, any CDN) with this structure:

```
registry/
  registry.json                           # Master manifest
  modules/
    <module-name>/
      component.json                      # Module descriptor
      files/
        <...template files...>            # Actual source code
```

### `registry.json` (Master Manifest)

```json
{
  "version": "1.0.0",
  "modules": [
    {
      "name": "health",
      "description": "Health check endpoints using @nestjs/terminus",
      "version": "1.0.0",
      "tags": ["infrastructure", "monitoring"]
    }
  ]
}
```

### `component.json` (Module Descriptor)

Each module has a `component.json` that describes everything the CLI needs:

```json
{
  "name": "health",
  "description": "Health check module with database, disk, and memory indicators",
  "version": "1.0.0",
  "dependencies": {
    "@nestjs/terminus": "^11.0.0"
  },
  "devDependencies": {},
  "files": [
    { "source": "files/health.module.ts", "target": "health.module.ts" },
    { "source": "files/health.controller.ts", "target": "health.controller.ts" }
  ],
  "moduleImport": {
    "moduleName": "HealthModule",
    "importPath": "@modules/health/health.module",
    "section": "Feature Modules"
  },
  "envVars": [
    {
      "key": "HEALTH_DISK_THRESHOLD_PERCENT",
      "value": "0.9",
      "comment": "Disk usage threshold for health check (0-1)"
    }
  ],
  "requiredModules": ["DatabaseModule"],
  "postInstallMessage": "Run `npm install` to install @nestjs/terminus, then start your app."
}
```

**Fields:**

| Field | Description |
|-------|-------------|
| `name` | Module identifier (used in `add <name>`) |
| `description` | Human-readable description |
| `version` | Semantic version of this module template |
| `dependencies` | npm dependencies to add to `package.json` |
| `devDependencies` | npm devDependencies to add |
| `files` | Array of `{ source, target }` — source is relative to module dir in registry, target is relative to destination dir |
| `moduleImport.moduleName` | The exported class name (e.g., `HealthModule`) |
| `moduleImport.importPath` | The import path to use in `app.module.ts` (uses project path aliases) |
| `moduleImport.section` | Which comment section in `app.module.ts` to insert under (e.g., `"Feature Modules"`) |
| `envVars` | Environment variables to add to `.env.example` |
| `requiredModules` | Other modules that must be present in `app.module.ts` before this one can be added |
| `postInstallMessage` | Message displayed after successful installation |

---

## Available Modules

### `health`

Health check endpoints using `@nestjs/terminus`.

**Adds:**

| File | Description |
|------|-------------|
| `health.module.ts` | Module definition with Terminus |
| `health.controller.ts` | `GET /health` endpoint with Swagger docs |
| `indicators/database.indicator.ts` | Checks database connectivity via Prisma `SELECT 1` |
| `indicators/disk.indicator.ts` | Checks disk usage against configurable threshold |
| `indicators/memory.indicator.ts` | Checks heap memory usage against configurable limit |

**Dependencies:** `@nestjs/terminus@^11.0.0`

**Prerequisites:** `DatabaseModule` (for the database health indicator)

**Environment variables:**

| Variable | Default | Description |
|----------|---------|-------------|
| `HEALTH_DISK_THRESHOLD_PERCENT` | `0.9` | Disk usage threshold (0-1, where 0.9 = 90%) |
| `HEALTH_MEMORY_HEAP_MB` | `300` | Maximum heap memory in MB |

**Endpoint:** `GET /api/v1/health`

**Example response:**

```json
{
  "status": "ok",
  "info": {
    "database": { "status": "up" },
    "disk": { "status": "up", "usagePercent": 45, "threshold": 90 },
    "memory": { "status": "up", "heapUsedMb": 85, "maxHeapMb": 300 }
  }
}
```

---

## Self-Hosting a Registry

You can create your own private registry. The CLI only needs a static file server.

### Option 1: GitHub Repository

1. Create a repo with the registry structure above
2. Push to GitHub
3. Use the raw content URL: `https://raw.githubusercontent.com/<org>/<repo>/main`

### Option 2: Any Static Host (S3, Cloudflare R2, Nginx)

1. Upload the registry files preserving the directory structure
2. Ensure files are served with correct MIME types (`.json` as `application/json`, `.ts` as `text/plain`)
3. Use the base URL when running `init`

### Creating a Custom Module

1. Create `modules/<name>/component.json` following the schema above
2. Add your template files under `modules/<name>/files/`
3. Add the module entry to `registry.json`
4. Template files are plain TypeScript — use the same path aliases as the target project (`@modules/*`, `@database/*`, `@config/*`, `@common/*`)

---

## Troubleshooting

### "No nestjs-cli.config.json found"

Run `npx @locotineee/cli init` first to generate the config file.

### "Required module X not found in app.module.ts"

The module you're trying to add has a prerequisite. Add the required module first. For example, `health` requires `DatabaseModule`.

### "Failed to fetch registry"

Check that your `registryUrl` in `nestjs-cli.config.json` is correct and accessible. The CLI uses native `fetch` with a 10-second timeout.

### Auto-wiring failed

If the CLI can't safely modify `app.module.ts` (unusual file structure, parse errors), it prints manual instructions:

```
⚠ Please add the following manually to app.module.ts:

  import { HealthModule } from '@modules/health/health.module';

  Then add HealthModule to the imports array in @Module().
```

The module files are still copied — only the auto-wiring step is skipped.

### Module directory already exists

The CLI detects existing modules and prompts before overwriting. Choose "No" to abort, or "Yes" to overwrite files (the auto-wiring step will skip if the import already exists in `app.module.ts`).