# @vuer-ai/dial-cli

A powerful CLI tool for generating dial metadata from TypeScript/React components. This tool analyzes JSDoc `@dial` annotations and TypeScript type definitions to automatically create UI control schemas.

## Features

- ✅ **Automatic Schema Generation** - Parse TypeScript/React components and generate dial schemas
- ✅ **Full Type System Support** - Handles inheritance, intersections, utility types (Pick, Omit, etc.)
- ✅ **Cross-File Type Resolution** - Resolves types across multiple files
- ✅ **Extensible Annotation System** - Custom `@dial-*` properties are automatically preserved
- ✅ **Performance Optimized** - Two-phase parsing strategy with caching
- ✅ **Comprehensive Testing** - 42+ test cases covering all features

## Installation

```bash
npm install -g @vuer-ai/dial-cli
# or
pnpm install -g @vuer-ai/dial-cli
# or
yarn global add @vuer-ai/dial-cli
```

## Quick Start

### 1. Add @dial Annotations to Your Components

```typescript
interface BoxProps {
  /**
   * Position in 3D space
   * @dial transform
   * @dial-dtype vector3
   * @dial-min -10
   * @dial-max 10
   * @dial-step 0.1
   */
  position: [number, number, number];

  /**
   * Box color
   * @dial appearance
   * @dial-dtype color
   */
  color: string;

  /**
   * Visibility toggle
   * @dial visibility
   * @dial-dtype boolean
   */
  visible: boolean;
}

export const Box: React.FC<BoxProps> = (props) => <mesh />;
```

### 2. Generate Dial Schema

```bash
dial-cli Box.tsx -o ./metadata
```

### 3. Use Generated Schema in Your App

```typescript
import dialSchemas from './metadata/schema.dial';
import { DialProvider } from '@vuer-ai/vuer-uikit';

function App() {
  return (
    <DialProvider schemas={dialSchemas[0].schemas}>
      <Box position={[0, 0, 0]} color="#ff0000" visible={true} />
    </DialProvider>
  );
}
```

The DialProvider will automatically generate UI controls for all properties with `@dial` annotations.

## Usage

### Basic Commands

```bash
# Process single file
dial-cli MyComponent.tsx

# Specify output directory
dial-cli MyComponent.tsx -o ./docs/metadata

# Process multiple files
dial-cli Component1.tsx Component2.tsx Component3.tsx

# Process all components in a directory
dial-cli src/components/**/*.tsx

# Enable verbose output
dial-cli MyComponent.tsx --verbose

# Exclude specific properties
dial-cli MyComponent.tsx --ignore "className,style,children"
```

### CLI Options

| Option | Description | Default |
|--------|-------------|---------|
| `-o, --output <dir>` | Output directory for metadata files | `./metadata` |
| `-i, --ignore <props>` | Property names or patterns to exclude (comma-separated, supports glob) | - |
| `--verbose` | Enable verbose output with detailed processing information | `false` |
| `--quiet` | Suppress all output except errors | `false` |
| `--debug` | Alias for `--verbose` | `false` |
| `-r, --remove` | Remove generated metadata files | `false` |
| `-v, --version` | Display version information | - |
| `-h, --help` | Display help message | - |

### Ignore Patterns

The `--ignore` flag supports glob patterns:

```bash
# Exact match
dial-cli Box.tsx --ignore className,style

# Glob patterns
dial-cli Box.tsx --ignore "on*"        # Exclude all props starting with "on"
dial-cli Box.tsx --ignore "_*"         # Exclude all private props
dial-cli Box.tsx --ignore "*Event*"    # Exclude props containing "Event"

# Multiple patterns
dial-cli Box.tsx --ignore "className,on*,_*"
```

## Dial Annotations

Dial annotations are JSDoc comments that configure how properties are displayed in the UI. They are divided into two categories: **Property Tags** (control individual properties) and **Group Tags** (configure entire groups).

**Note on @dial requirement**: Properties without a `@dial <group>` annotation will still be included in the schema, but will be displayed in a default layout. It's recommended to assign properties to groups for better organization.

### Property Tags

Property tags control individual property behavior and appearance. They are added to property JSDoc comments.

#### Core Property Tags

| Annotation | Description | Values | Example |
|------------|-------------|--------|---------|
| `@dial <group>` | **Optional**: Assign property to a named group. Properties without this tag will be displayed in default layout. | Any string (group name) | `@dial transform` |
| `@dial-dtype <type>` | **Important**: Explicit data type for UI control. If not set, the CLI will infer the type from TypeScript type definitions (e.g., `boolean` → `"boolean"`, `[number, number, number]` → `"vector3"`). Use this to override automatic inference. | See [Supported Data Types](#supported-data-types) | `@dial-dtype vector3` |
| `@dial-ignore` | Exclude property from schema generation | No value needed | `@dial-ignore` |
| `@dial-options <json>` | Options for select/dropdown controls | JSON array or object | `@dial-options ["a", "b", "c"]` |

#### UI Display Tags

Control how properties are labeled and displayed in the UI.

| Annotation | Description | Values | Example |
|------------|-------------|--------|---------|
| `@dial-label <text>` | Custom display label for the property. If not set, defaults to the property name. | Any string | `@dial-label 3D Position` |
| `@dial-icon <name>` | Icon name from Lucide icon library to display with the property | Lucide icon name | `@dial-icon Move3d` |
| `@dial-label-position <pos>` | Position of the label relative to the input control. **Shortcuts**: `@dial-label-left`, `@dial-label-right`, `@dial-label-top`, `@dial-label-bottom`, `@dial-label-center`, `@dial-label-inline` | `left`, `right`, `top`, `bottom`, `center`, `inline` | `@dial-label-position top` or `@dial-label-top` |

#### Numeric Constraint Tags

| Annotation | Description | Values | Example |
|------------|-------------|--------|---------|
| `@dial-min <number>` | Minimum value constraint | Any number | `@dial-min 0` |
| `@dial-max <number>` | Maximum value constraint | Any number | `@dial-max 100` |
| `@dial-step <number>` | Step increment for sliders | Any number | `@dial-step 0.1` |
| `@dial-value <value>` | Default value | Any valid value | `@dial-value 1.0` |
| `@dial-default <value>` | Alias for `@dial-value` | Any valid value | `@dial-default 0` |

#### Vector-Specific Property Tags

For vectors (tuples), you can specify per-element constraints. If not provided, these values are automatically merged from individual element annotations.

**Note**: Values must be provided as JSON arrays for optimal performance.

| Annotation | Description | Values | Example |
|------------|-------------|--------|---------|
| `@dial-mins <values>` | Min values per element | JSON array of numbers | `@dial-mins [0,0,0]` |
| `@dial-maxs <values>` | Max values per element | JSON array of numbers | `@dial-maxs [255,255,255]` |
| `@dial-steps <values>` | Step values per element | JSON array of numbers | `@dial-steps [1,1,1]` |
| `@dial-dtypes <values>` | Data types per element | JSON array of strings | `@dial-dtypes ["int","int","int"]` |
| `@dial-placeholders <values>` | Placeholder text per element | JSON array of strings | `@dial-placeholders ["R","G","B"]` |
| `@dial-tooltips <values>` | Tooltip text per element | JSON array of strings | `@dial-tooltips ["Red","Green","Blue"]` |

**Auto-merging behavior**: If vector-specific tags are not provided, the CLI automatically merges annotations from each tuple element:

```typescript
type RGB = [
  // @dial-min 0 @dial-max 255 @dial-step 1 @dial-dtype int
  r: number,
  // @dial-min 0 @dial-max 255 @dial-step 1 @dial-dtype int
  g: number,
  // @dial-min 0 @dial-max 255 @dial-step 1 @dial-dtype int
  b: number,
];

interface Props {
  /**
   * RGB color
   * @dial appearance
   * @dial-placeholders R,G,B  // Optional: override element names
   */
  color: RGB;
}

// Output schema will automatically include:
// mins: [0, 0, 0], maxs: [255, 255, 255], steps: [1, 1, 1], dtypes: ["int", "int", "int"]
```

#### Property Layout Tags

Control how individual properties are displayed within the grouped UI layout.

| Annotation | Description | Values | Example |
|------------|-------------|--------|---------|
| `@dial-col-span <num>` | How many grid columns this property spans | Number | `@dial-col-span 2` |
| `@dial-row-span <num>` | How many grid rows this property spans | Number | `@dial-row-span 2` |

### Group Tags

Group tags configure the layout and behavior of entire property groups. These tags control **group-level layout** (how the group container is structured), not individual property behavior.

**Important**: Group tags only apply when a `@dial <groupname>` annotation is present. Group tags are ignored if no `@dial <groupname>` is specified. The CLI will warn you if group tags are used without a group name.

#### Group Layout Configuration

| Annotation | Description | Values | Example |
|------------|-------------|--------|---------|
| `@dial-group-layout <type>` | Layout system for the group container. **Shorthand**: `@dial-layout` | `grid`, `flex` | `@dial-group-layout grid` or `@dial-layout grid` |
| `@dial-group-grid-cols <num>` | Number of columns in group grid layout. **Shorthand**: `@dial-cols` or `@dial-grid-cols` | Number | `@dial-group-grid-cols 2` or `@dial-cols 2` or `@dial-grid-cols 2` |
| `@dial-group-grid-rows <num>` | Number of rows in group grid layout. **Shorthand**: `@dial-rows` or `@dial-grid-rows` | Number | `@dial-group-grid-rows 3` or `@dial-rows 3` or `@dial-grid-rows 3` |
| `@dial-group-grid-flow <dir>` | Grid auto-flow direction for the group. **Shorthand**: `@dial-flow` or `@dial-grid-flow` | `row`, `column` | `@dial-group-grid-flow row` or `@dial-flow row` or `@dial-grid-flow row` |
| `@dial-group-grid-col-template <val>` | CSS grid column template for the group. **Shorthand**: `@dial-col-template` or `@dial-grid-col-template` | CSS grid template value | `@dial-group-grid-col-template 1fr 2fr` or `@dial-col-template 1fr 2fr` or `@dial-grid-col-template 1fr 2fr` |
| `@dial-group-grid-row-template <val>` | CSS grid row template for the group. **Shorthand**: `@dial-row-template` or `@dial-grid-row-template` | CSS grid template value | `@dial-group-grid-row-template auto 1fr` or `@dial-row-template auto 1fr` or `@dial-grid-row-template auto 1fr` |
| `@dial-group-flex-wrap <val>` | Flex wrap behavior for the group. **Shorthand**: `@dial-wrap` or `@dial-flex-wrap` | `nowrap`, `wrap`, `wrap-reverse` | `@dial-group-flex-wrap wrap` or `@dial-wrap wrap` or `@dial-flex-wrap wrap` |
| `@dial-group-flex-justify-content <val>` | Flex justify-content for the group. **Shorthand**: `@dial-justify` or `@dial-flex-justify-content` | `flex-start`, `flex-end`, `center`, `space-between`, `space-around`, `space-evenly` | `@dial-group-flex-justify-content center` or `@dial-justify center` or `@dial-flex-justify-content center` |

Example:
```typescript
interface Props {
  /**
   * X position
   * @dial transform
   * @dial-layout grid
   * @dial-cols 3
   * @dial-flow row
   */
  x: number;

  /**
   * Y position
   * @dial transform
   */
  y: number;

  /**
   * Z position
   * @dial transform
   */
  z: number;
}

// Result: All three properties are displayed in a 3-column grid
```

### Supported Data Types

The CLI infers data types from TypeScript types, but you can override with `@dial-dtype`.

| dtype | TypeScript Type | UI Control | Description |
|-------|----------------|------------|-------------|
| `boolean` | `boolean` | Toggle switch | On/off control |
| `number` | `number` | Slider/input | Floating-point number |
| `int` or `number-int` | `number` with explicit dtype | Integer slider | Whole numbers only |
| `string` | `string` | Text input | Text value |
| `color` | `string` | Color picker | Hex color (#RRGGBB) |
| `select` | Union type with `@dial-options` | Dropdown | Select from options |
| `array` | `string[]`, `number[]`, `boolean[]` | Dynamic list | Array of primitive types with add/remove |
| `vector` | Tuple with number/boolean elements | Vector input | Multi-dimensional numeric/boolean input |
| `tuple` | Tuple with mixed types | Nested panel | General tuple with any element types |
| `vector3` | `[number, number, number]` | 3D vector input | X, Y, Z components (simplified) |
| `euler` | `[number, number, number]` | Euler angles | Rotation in degrees/radians |
| `interface` | Interface type | Nested panel | Nested control panel |
| `object` | Object type | Nested panel | Nested control panel |

**Type Inference Examples**:

```typescript
interface Props {
  // Inferred as "boolean"
  visible: boolean;

  // Inferred as "number"
  opacity: number;

  // Inferred as "string"
  name: string;

  // Inferred as "vector3" (from type name or tuple length)
  /** @dial transform */
  position: [number, number, number];

  // Explicit dtype override
  /** @dial geometry @dial-dtype int */
  segments: number;  // Will be treated as integer

  // Named tuple with numbers/booleans inferred as "vector"
  /** @dial appearance */
  color: RGB;  // where RGB is [r: number, g: number, b: number]

  // Mixed-type tuple inferred as "tuple" (general tuple)
  /** @dial metadata */
  config: [string, number, {name: string}];  // Will use nested panel UI

  // Arrays of primitives inferred as "array"
  /** @dial content */
  tags: string[];  // Dynamic list with add/remove

  /** @dial settings */
  values: number[];  // Array of numbers

  // Note: Complex element types are not supported
  // items: ComplexItem[];  // Will fallback to string
}
```

## Advanced Features

### Type System Support

The CLI fully supports TypeScript's type system, automatically merging annotations from multiple sources:

- **Interface Inheritance** (`extends`): Properties and annotations from parent interfaces are merged
- **Type Intersections** (`&`): Multiple types are combined with their annotations
- **Utility Types** (`Pick`, `Omit`, `Partial`, `Required`): Type transformations are properly resolved
- **Cross-File Types**: Types imported from other files are fully resolved

Example combining multiple type features:

```typescript
// BaseTypes.tsx
export interface Transform {
  /** @dial transform @dial-dtype vector3 */
  position: [number, number, number];
}

// Component.tsx
import { Transform } from "./BaseTypes";

interface MeshProps extends Transform {
  /** @dial appearance @dial-dtype color */
  color: string;
}

// Result: MeshProps schema includes both position and color with their annotations
```

### Component-Level Annotations

The `@dial-ignore` annotation can be added to component JSDoc comments to exclude the entire component from schema generation. This is a special case - only `@dial-ignore` is supported at the component level.

**Important**: Type-level annotations have no effect. Annotations on interface/type definitions (like `InternalComponentProps` below) do not apply to properties - they are only processed when attached to actual component props.

```typescript
/**
 * This component will be excluded from schema generation
 * @dial-ignore
 */
export const InternalComponent: FC<Props> = ({ ... }) => {
  // Component will not generate dial schema
}

// ❌ INCORRECT: This annotation has no effect
/**
 * @dial-ignore
 */
interface SomeType {  // This does nothing
  field1: string;
  field2: number;
}

// ✓ CORRECT: Annotations apply to the component's props
interface ComponentProps {
  /**
   * This annotation applies to the 'position' property
   * @dial transform
   * @dial-dtype vector3
   */
  position: Vector3;
}
```

**Scope clarification**:
- Component-level `@dial-ignore`: Applies to the React component itself
- Property-level annotations: Apply to individual properties within the component's props interface
- Type definition annotations: Have no effect and are ignored (types are just containers for structure)

## Output Format

### Generated Files

```
metadata/
└── schema.dial      # Combined schema for all components (JSON format)
```

### Schema Structure

```json
[
  {
    "component": "Box",
    "description": "Box component description",
    "schemas": [
      {
        "name": "position",
        "dtype": "vector3",
        "min": -10,
        "max": 10,
        "step": 0.1,
        "icon": "Move3d",
        "label": "3D Position",
        "value": [0, 0, 0],
        "tags": {
          "grouping": "transform"
        }
      }
    ],
    "groups": [
      {
        "name": "transform",
        "noWrap": false
      }
    ]
  }
]
```

## Loading schema.dial Files

### In Vite Projects

Configure Vite to recognize `.dial` files:

```javascript
// vite.config.js
import { defineConfig } from 'vite';
import json from '@rollup/plugin-json';

export default defineConfig({
  plugins: [
    json({
      include: [
        '**/*.json',
        '**/*.dial'  // Add support for .dial files
      ]
    })
  ]
});
```

Then import directly:

```typescript
import dialSchemas from './metadata/schema.dial';

<DialProvider schemas={dialSchemas[0].schemas}>
  {/* Your components */}
</DialProvider>
```

### TypeScript Support

Add type declaration for `.dial` files:

```typescript
// dial.d.ts or in your global types file
declare module '*.dial' {
  import type { DialSchema, GroupSchema } from '@vuer-ai/vuer-uikit';

  interface DialComponentSchema {
    component: string;
    schemas: DialSchema[];
    groups?: GroupSchema[];
  }

  const schemas: DialComponentSchema[];
  export default schemas;
}
```

### Alternative: Rename to .json

```bash
dial-cli MyComponent.tsx -o ./metadata
mv ./metadata/schema.dial ./metadata/schema.json
```

Then import as JSON:

```typescript
import dialSchemas from './metadata/schema.json';
```

## Development

### Local Development

```bash
# Clone repository
git clone https://github.com/vuer-ai/vuer-uikit
cd vuer-uikit/packages/vuer-uikit/dial-cli

# Install dependencies
pnpm install

# Build for development (watch mode)
pnpm dev

# In another terminal, test your changes
dial-cli test-file.tsx -o ./output
```

### Running Tests

```bash
# Navigate to dial-cli directory
cd packages/vuer-uikit/dial-cli

# Run all tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage report
pnpm test:coverage
```

### Test Structure

The test suite includes:
- **Data Types Tests** (`spec/tests/1-dtypes/`) - All supported dtypes
- **Inheritance Tests** (`spec/tests/2-inheritance/`) - Type inheritance patterns
- **Annotation Tests** (`spec/tests/3-annotations/`) - @dial annotations
- **CLI Flags Tests** (`spec/tests/4-cli-flags/`) - Command-line flags
- **Complex Scenarios** (`spec/tests/5-complex/`) - Integration tests

## Documentation

- [Implementation Guide](./IMPLEMENTATION.md) - Detailed architecture and implementation guide
- [API Documentation](https://uikit.vuer.ai/dial) - Full API documentation
- [GitHub Repository](https://github.com/vuer-ai/vuer-uikit) - Source code and issues

## Troubleshooting

### Properties not appearing in schema

**Problem**: Properties don't show up in generated schema

**Solution**: Make sure to add at least one `@dial-*` annotation. Any dial annotation will include the property in the schema (except `@dial-ignore` which explicitly excludes it).

```typescript
// Any of these will work:
/**
 * @dial transform                    ← Assigns to group "transform"
 * @dial-dtype vector3
 */
position: Vector3;

/**
 * @dial-min 0                        ← Just constraints work too
 * @dial-max 100
 */
opacity: number;

/**
 * @dial-dtype color                  ← Just dtype also works
 */
color: string;

// This property will be EXCLUDED:
/**
 * @dial-ignore                       ← Explicitly excluded
 */
internal: any;

// This property will NOT appear (no @dial-* annotations):
/**
 * Regular JSDoc comment without dial annotations
 */
className: string;
```

### Inherited properties missing

**Problem**: Properties from parent interfaces are missing

**Solution**:
- Ensure base type is in the same file or properly imported
- Check that import paths are correct
- Run with `--verbose` flag to see type resolution details

### Wrong dtype inferred

**Problem**: CLI infers incorrect data type

**Solution**: Explicitly specify the dtype:
```typescript
/** @dial-dtype vector3 */
```

### CLI not finding files

**Problem**: Files not being processed

**Solution**: Use absolute paths or ensure working directory is correct:
```bash
cd project-root
dial-cli src/components/Box.tsx -o ./metadata
```

## Performance

### Benchmarks

| Scenario | Components | Time | Notes |
|----------|-----------|------|-------|
| Single simple component | 1 | ~100ms | No inheritance |
| Component with inheritance | 1 | ~200ms | With type resolution |
| Complex project | 20 | ~2s | Mixed complexity |
| Large codebase | 100 | ~10s | Many inheritance chains |

*Benchmarks on M1 Mac with TypeScript 5.8*

### Optimization Tips

1. **Use `--ignore` to skip unnecessary props**:
   ```bash
   dial-cli Box.tsx --ignore "on*,_*,className"
   ```

2. **Process files in parallel**:
   ```bash
   dial-cli src/**/*.tsx -o ./metadata
   ```

3. **Use `--quiet` in CI/CD**:
   ```bash
   dial-cli src/**/*.tsx --quiet -o ./dist/metadata
   ```

## Contributing

Contributions are welcome! Please see our [GitHub repository](https://github.com/vuer-ai/vuer-uikit) for more information.

## License

MIT

## Author

Ge Yang

## Links

- [GitHub Repository](https://github.com/vuer-ai/vuer-uikit)
- [Issue Tracker](https://github.com/vuer-ai/vuer-uikit/issues)
- [Documentation](https://uikit.vuer.ai/dial)
- [npm Package](https://www.npmjs.com/package/@vuer-ai/dial-cli)
