> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# stub

> 빠른 개발을 위한 코드 스텁 생성하기

export const FileItem = ({name, description, children}) => {
  const isLeaf = !children;
  const getFileExtension = filename => {
    if (filename.endsWith('/')) return null;
    const match = filename.match(/\.([^.]+)$/);
    return match ? match[1].toUpperCase() : null;
  };
  const getExtensionStyle = ext => {
    const styles = {
      TS: {
        backgroundColor: '#3178c6',
        color: 'white'
      },
      TSX: {
        backgroundColor: '#3178c6',
        color: 'white'
      },
      JS: {
        backgroundColor: '#f7df1e',
        color: 'black'
      },
      JSX: {
        backgroundColor: '#f7df1e',
        color: 'black'
      },
      JSON: {
        backgroundColor: '#ffd700',
        color: 'black'
      },
      YML: {
        backgroundColor: '#cb171e',
        color: 'white'
      },
      YAML: {
        backgroundColor: '#cb171e',
        color: 'white'
      },
      SQL: {
        backgroundColor: '#e38c00',
        color: 'white'
      },
      SH: {
        backgroundColor: '#4eaa25',
        color: 'white'
      },
      MD: {
        backgroundColor: '#083fa1',
        color: 'white'
      },
      CSS: {
        backgroundColor: '#563d7c',
        color: 'white'
      },
      HTML: {
        backgroundColor: '#e34c26',
        color: 'white'
      },
      ENV: {
        backgroundColor: '#6c757d',
        color: 'white'
      }
    };
    return styles[ext] || ({
      backgroundColor: '#6c757d',
      color: 'white'
    });
  };
  const ext = getFileExtension(name);
  const extStyle = ext ? getExtensionStyle(ext) : null;
  return <div style={{
    marginLeft: '30px'
  }}>
      <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '4px'
  }}>
        <span style={{
    position: 'relative',
    display: 'inline-block'
  }}>
          <span className="file-icon">{isLeaf && !name.endsWith("/") ? "📄" : "📁"}</span>
          {ext && <span style={{
    position: 'absolute',
    bottom: '2px',
    right: '0px',
    fontSize: '5px',
    fontWeight: 'bold',
    padding: '1px',
    borderRadius: '2px',
    lineHeight: '1',
    minWidth: '8px',
    textAlign: 'center',
    ...extStyle
  }}>
              {ext}
            </span>}
        </span>
        <code>{name}</code>
        {description && <span className="description"> - {description}</span>}
      </div>
      {children}
    </div>;
};

export const FileTree = ({children}) => {
  return <div className="file-tree" style={{
    margin: '20px',
    marginLeft: '-30px'
  }}>{children}</div>;
};

`pnpm stub` 명령어는 반복 작업을 줄이기 위한 **코드 템플릿**을 자동 생성합니다. Practice 스크립트나 새 Entity를 빠르게 만들 수 있어 개발 속도를 높입니다.

## 명령어

### practice - Practice 스크립트 생성

임시 실험이나 데이터 처리를 위한 스크립트를 생성합니다.

```bash theme={null}
pnpm stub practice <name>
```

**예시**:

```bash theme={null}
pnpm stub practice test-api

# 생성됨:
# src/practices/p1-test-api.ts
# VS Code에서 자동으로 열림
# 실행 명령어가 클립보드에 복사됨
```

**생성된 파일**:

```typescript title="src/practices/p1-test-api.ts" theme={null}
import { Sonamu } from "sonamu";

console.clear();
console.log("p1-test-api.ts");

Sonamu.runScript(async () => {
  // TODO
});
```

**실행**:

```bash theme={null}
# 클립보드에서 붙여넣기 (Cmd+V)
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-test-api.js
```

### entity - Entity 생성

새로운 Entity를 생성합니다. 기본적으로 Entity 생성 후 **템플릿 cone**이 자동으로 함께 생성됩니다.

```bash theme={null}
pnpm stub entity <entityId> [옵션]
```

**옵션**:

| 옵션           | 설명                                                 | 예시                                    |
| ------------ | -------------------------------------------------- | ------------------------------------- |
| (없음)         | Entity 생성 후 템플릿 cone 자동 생성 (기본 동작)                 | `pnpm stub entity Product`            |
| `--ai`       | Entity 생성 후 LLM으로 cone 생성 (`ANTHROPIC_API_KEY` 필요) | `pnpm stub entity Product --ai`       |
| `--no-cones` | Entity만 생성하고 cone 생성을 스킵                           | `pnpm stub entity Product --no-cones` |

**예시**:

```bash theme={null}
# 기본: 템플릿 cone과 함께 Entity 생성
pnpm stub entity Product
# ✓ Entity 'Product' created with template cones
# 💡 Tip: Run 'pnpm sonamu cone gen Product' to improve with AI

# AI를 활용한 cone 생성
pnpm stub entity Product --ai
# ✓ Entity 'Product' created
# 🌟 Generating AI-powered cones...
# ✅ Done (1234 tokens)

# cone 없이 Entity만 생성
pnpm stub entity Product --no-cones
# ✓ Entity 'Product' created without cones
```

<Note>
  템플릿 cone은 `ANTHROPIC_API_KEY` 없이도 생성할 수 있습니다. 나중에 `cone gen` 명령어로 AI를 활용해 업그레이드할 수 있습니다.
</Note>

**생성된 파일**:

```typescript title="src/entities/Product.entity.ts" theme={null}
import type { EntityType } from "sonamu";

export const ProductEntity = {
  properties: [
    {
      name: "id",
      type: "int",
      primaryKey: true,
      autoIncrement: true,
    },
    {
      name: "title",
      type: "string",
      length: 255,
    },
    {
      name: "created_at",
      type: "datetime",
      default: "CURRENT_TIMESTAMP",
    },
  ],
} satisfies EntityType;
```

## Practice 스크립트

### 사용 시나리오

Practice 스크립트는 다음과 같은 작업에 활용됩니다:

| 용도         | 설명            | 예시                            |
| ---------- | ------------- | ----------------------------- |
| API 테스트    | 외부 API 호출 테스트 | `p1-test-payment-api.ts`      |
| 데이터 마이그레이션 | 일회성 데이터 변환    | `p2-migrate-user-data.ts`     |
| 디버깅        | 특정 로직 검증      | `p3-debug-cache-issue.ts`     |
| 데이터 생성     | 테스트 데이터 생성    | `p4-create-sample-posts.ts`   |
| 분석         | 데이터 통계 확인     | `p5-analyze-user-activity.ts` |

### 순서 관리

Practice 파일은 **자동으로 번호가 부여**됩니다:

<FileTree>
  <FileItem name="src/practices/">
    <FileItem name="p1-first-practice.ts" />

    <FileItem name="p2-second-practice.ts" />

    <FileItem name="p3-third-practice.ts" description="다음은 p4" />
  </FileItem>
</FileTree>

새 practice를 생성하면 가장 큰 번호 + 1이 자동으로 할당됩니다.

### 실전 예제

#### 1. API 테스트

```typescript title="src/practices/p1-test-payment-api.ts" theme={null}
import { Sonamu } from "sonamu";

console.clear();
console.log("p1-test-payment-api.ts");

Sonamu.runScript(async () => {
  const paymentService = new PaymentService();
  
  // 테스트 결제 시도
  const result = await paymentService.charge({
    amount: 10000,
    orderId: "TEST-001",
  });
  
  console.log("결제 결과:", result);
});
```

#### 2. 데이터 마이그레이션

```typescript title="src/practices/p2-migrate-user-data.ts" theme={null}
import { Sonamu } from "sonamu";
import { UserModel } from "../models/User.model";

console.clear();
console.log("p2-migrate-user-data.ts");

Sonamu.runScript(async () => {
  // 모든 사용자 조회
  const users = await UserModel.findAll();
  
  for (const user of users) {
    // legacy 필드를 새 필드로 마이그레이션
    await UserModel.update(user.id, {
      new_field: transformData(user.legacy_field),
    });
    
    console.log(`✓ Migrated user ${user.id}`);
  }
  
  console.log(`\n✓ Total: ${users.length} users migrated`);
});
```

#### 3. 배치 작업

```typescript title="src/practices/p3-send-welcome-emails.ts" theme={null}
import { Sonamu } from "sonamu";
import { UserModel } from "../models/User.model";

console.clear();
console.log("p3-send-welcome-emails.ts");

Sonamu.runScript(async () => {
  // 신규 가입 사용자
  const newUsers = await UserModel.findMany({
    where: { welcome_email_sent: false },
  });
  
  console.log(`Sending welcome emails to ${newUsers.length} users...`);
  
  for (const user of newUsers) {
    try {
      await sendWelcomeEmail(user.email);
      await UserModel.update(user.id, {
        welcome_email_sent: true,
      });
      console.log(`✓ Sent to ${user.email}`);
    } catch (error) {
      console.error(`✗ Failed to send to ${user.email}:`, error);
    }
  }
});
```

## Entity 생성

### 기본 구조

`stub entity`로 생성된 Entity는 기본 필드들을 포함합니다:

```typescript theme={null}
export const ProductEntity = {
  properties: [
    { name: "id", type: "int", primaryKey: true, autoIncrement: true },
    { name: "title", type: "string", length: 255 },
    { name: "created_at", type: "datetime", default: "CURRENT_TIMESTAMP" },
  ],
} satisfies EntityType;
```

### 커스터마이징

생성 후 필요한 필드를 추가합니다:

```typescript theme={null}
export const ProductEntity = {
  properties: [
    // 기본 필드
    { name: "id", type: "int", primaryKey: true, autoIncrement: true },
    { name: "title", type: "string", length: 255 },
    
    // 추가 필드
    { name: "description", type: "text", nullable: true },
    { name: "price", type: "decimal", precision: 10, scale: 2 },
    { name: "stock", type: "int", default: 0 },
    { name: "category_id", type: "int" },
    
    // 타임스탬프
    { name: "created_at", type: "datetime", default: "CURRENT_TIMESTAMP" },
    { name: "updated_at", type: "datetime", onUpdate: "CURRENT_TIMESTAMP" },
  ],
  
  indexes: [
    { fields: ["category_id"] },
    { fields: ["title"], type: "fulltext" },
  ],
  
  belongsTo: [
    { entityId: "Category", as: "category" },
  ],
} satisfies EntityType;
```

## 개발 워크플로우

### 1. 아이디어 검증

```bash theme={null}
# 빠르게 practice 생성
pnpm stub practice test-idea

# 코드 작성 및 실행
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-test-idea.js

# 동작 확인 후 삭제 또는 보관
```

### 2. Entity 추가

```bash theme={null}
# Entity 생성
pnpm stub entity Order

# Entity 정의 작성
# src/entities/Order.entity.ts

# 마이그레이션 생성 및 적용
pnpm migrate run

# Model 스캐폴딩
pnpm scaffold model Order
```

### 3. 데이터 작업

```bash theme={null}
# Practice 스크립트 생성
pnpm stub practice import-orders

# 데이터 처리 로직 작성
# src/practices/p1-import-orders.ts

# 실행
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-import-orders.js
```

## 실전 팁

### 1. Practice 스크립트 재사용

```typescript theme={null}
// ✅ 재사용 가능하도록 함수화
async function processUsers(filter: any) {
  const users = await UserModel.findMany({ where: filter });
  // 처리 로직...
}

Sonamu.runScript(async () => {
  // 다양한 필터로 재사용
  await processUsers({ role: "admin" });
  await processUsers({ role: "user" });
});
```

### 2. 에러 처리

```typescript theme={null}
Sonamu.runScript(async () => {
  try {
    await riskyOperation();
  } catch (error) {
    console.error("Error occurred:", error);
    process.exit(1);  // 실패 시 종료
  }
});
```

### 3. 진행 상황 표시

```typescript theme={null}
Sonamu.runScript(async () => {
  const users = await UserModel.findAll();
  
  for (let i = 0; i < users.length; i++) {
    await processUser(users[i]);
    
    // 진행률 표시
    const progress = Math.round(((i + 1) / users.length) * 100);
    console.log(`Progress: ${progress}% (${i + 1}/${users.length})`);
  }
});
```

### 4. Practice 정리

```bash theme={null}
# 완료된 practice는 삭제
rm src/practices/p1-old-practice.ts

# 또는 보관용 디렉토리로 이동
mkdir src/practices/archived
mv src/practices/p1-*.ts src/practices/archived/
```

## 주의사항

<Warning>
  **Practice 스크립트 사용 시 주의사항**:

  1. **프로덕션 데이터**: Practice는 실제 DB를 사용합니다. 조심하세요!
  2. **백업**: 중요한 데이터 작업 전에는 반드시 백업하세요.
  3. **트랜잭션**: 데이터 변경 시 트랜잭션을 사용하세요.
  4. **테스트**: 프로덕션 실행 전 개발 환경에서 먼저 테스트하세요.
</Warning>

## 다음 단계

<CardGroup cols={2}>
  <Card title="scaffold" icon="wand-magic-sparkles" href="/ko/tools-and-cli/sonamu-cli/scaffold">
    코드 스캐폴딩으로 Model 자동 생성하기
  </Card>

  <Card title="Entity" icon="cube" href="/ko/core-concepts/entity">
    Entity 정의 자세히 알아보기
  </Card>
</CardGroup>
