# Codemods

`@wishket/design-system` 메이저 마이그레이션을 컨슈머 앱(`wishket-service`, `yozm-service`, `account-service` 등)에 일괄 적용하기 위한 jscodeshift 스크립트 모음입니다.

## v3.0 — `add-as-link`

v3.0은 `TextLink`, `Menu`, `GNBList.Item`이 더 이상 자동으로 `next/link`를 사용하지 않습니다. 컨슈머는 `as={Link}`를 명시적으로 넘겨야 합니다. 이 codemod는 그 변환을 자동화합니다.

### 변환 규칙

1. `@wishket/design-system`을 import한 파일에서만 동작.
2. 다음 호출만 대상으로 함: `<TextLink ... />`, `<Menu ... />`, `<GNBList.Item ... />`.
3. **`href`가 있는 호출만** 대상 (Menu의 `<button>` 폴백을 보호).
4. 이미 `as` prop이 있으면 건너뜀.
5. `next/link` 기본 import가 없으면 자동으로 `import Link from 'next/link';`를 파일 상단에 추가.

### 변환 예시

```tsx
// === Before ===
import { TextLink, Menu, GNBList } from '@wishket/design-system';

<TextLink href="/about" text="About" />
<Menu name="Settings" href="/settings" />
<GNBList.Item href="/products">Products</GNBList.Item>

// === After ===
import Link from 'next/link';
import { TextLink, Menu, GNBList } from '@wishket/design-system';

<TextLink as={Link} href="/about" text="About" />
<Menu as={Link} name="Settings" href="/settings" />
<GNBList.Item as={Link} href="/products">Products</GNBList.Item>
```

### 권장 4단계 실행 절차

> 시니어 친화적 운영 절차. 각 단계를 순서대로 실행하고 결과를 확인 후 다음으로 넘어가세요.

#### Step 1 — 사전 준비

```bash
# jscodeshift 설치 (없는 경우)
yarn add -D jscodeshift @types/jscodeshift

# v3 설치/업그레이드
yarn up @wishket/design-system@3
```

#### Step 2 — Dry run (미리 보기)

```bash
npx jscodeshift \
  -t node_modules/@wishket/design-system/scripts/codemods/add-as-link.ts \
  --extensions=tsx,ts \
  --parser=tsx \
  --dry --print \
  src/
```

`--dry`로 실제 파일 변경 없이 변환될 코드를 콘솔에 표시. 변환 규모와 후보 호출을 확인.

#### Step 3 — 실제 적용 + 포맷 + 검증

```bash
# 1) codemod 적용
npx jscodeshift \
  -t node_modules/@wishket/design-system/scripts/codemods/add-as-link.ts \
  --extensions=tsx,ts \
  --parser=tsx \
  src/

# 2) prettier로 출력 품질 보정 (codemod는 원래 스타일을 100% 보존하지 못함)
yarn prettier:write src/

# 3) 타입 체크
yarn type-check          # 또는: npx tsc -p tsconfig.json --noEmit

# 4) 빌드 확인
yarn build               # 또는 next build / vite build 등
```

#### Step 4 — 수동 처리 후보 grep

codemod가 안전하게 변환하지 못하는 케이스를 후처리:

```bash
# spread props 케이스
grep -rn -E "TextLink \{\.\.\.|Menu \{\.\.\.|GNBList\.Item \{\.\.\." src/

# alias import 케이스 (있다면)
grep -rn "as DSTextLink\|as DSMenu\|as DSGNBList" src/

# UrlObject href 패턴 (v3에서 string으로 좁혀짐)
grep -rn "href={{" src/
```

발견된 케이스는 [`MIGRATION_V2_TO_V3.md`](../../MIGRATION_V2_TO_V3.md) §4의 수동 수정 가이드 참조.

> 디자인시스템을 v3로 올린 뒤 `node_modules`에 codemod가 함께 들어옵니다 (`scripts/codemods/`는 `package.json`의 `files` 필드에 포함됨).

### 한계와 주의사항

- **Spread props는 변환 못 함.** `<TextLink {...linkProps} />`처럼 props가 spread로 들어오는 호출은 자동 변환에서 제외됩니다. 다음 명령으로 후보를 찾아 수동 처리하세요.

  ```bash
  grep -rn "TextLink {\\.\\.\\.\\|Menu {\\.\\.\\.\\|GNBList\\.Item {\\.\\.\\." src/
  ```

- **alias import 미지원.** `import { TextLink as DSTextLink } from '@wishket/design-system'`처럼 alias를 쓰는 경우는 잡지 못합니다. 사내 컨벤션이 alias를 안 쓰면 무시 가능.
- **Menu의 button 폴백.** `href`가 없는 `<Menu>` 호출은 의도적으로 변환되지 않습니다. (폴백으로 `<button>`이 렌더되기 때문에 `as`를 붙이면 안 됨.)
- **codemod는 1차 자동화입니다.** 변환 후 반드시 사람이 diff를 검토하고 CI를 한 번 돌려보세요.

## v3.0 — `menu-split`

v3.0에서는 `Menu` 컴포넌트가 `MenuLink`(anchor) / `MenuButton`(button)으로 분리됩니다. 시맨틱 분기를 prop 존재 여부로 추론하던 기존 구조를 제거하고 컴포넌트 단위에서 강제하기 위함입니다.

> 이 codemod는 `add-as-link` 이후에 실행하세요. `add-as-link`가 `Menu`에 `as={Link}`를 미리 붙여둔 상태라도 `menu-split`이 안전하게 처리합니다.

### 변환 규칙

1. `@wishket/design-system`에서 `Menu`가 import된 파일만 대상.
2. JSX 호출 분석:
   - `href` prop 있음 → `<MenuLink ...>`로 rename + import 정리.
   - `href` 없고 `onClick` 있음 → `<MenuButton ...>`로 rename + import 정리.
   - **둘 다 있음 (모순 케이스)** → 변환하지 않고 위에 `// TODO: codemod could not safely resolve — manually choose MenuLink or MenuButton` 코멘트 삽입.
   - **둘 다 없음** → 변환하지 않고 같은 형식의 TODO 코멘트 삽입.
   - **spread props** (`<Menu {...props} />`) → 변환하지 않고 TODO 코멘트 삽입.
3. `Menu` import는 사용된 변형으로 교체. 변환 못 한 호출이 남아있으면 `Menu`도 함께 유지.
4. **alias import** (예: `Menu as M`)는 안전하게 변환할 수 없으므로 파일 상단에 TODO 코멘트만 추가합니다.

### 변환 예시

```tsx
// === Before ===
import { Menu } from '@wishket/design-system';

<Menu name="Projects" href="/projects" />
<Menu name="Settings" onClick={() => openModal()} />

// === After ===
import { MenuLink, MenuButton } from '@wishket/design-system';

<MenuLink name="Projects" href="/projects" />
<MenuButton name="Settings" onClick={() => openModal()} />
```

### 실행 방법

```bash
# 미리 보기
npx jscodeshift \
  -t node_modules/@wishket/design-system/scripts/codemods/menu-split.ts \
  --extensions=tsx,ts --parser=tsx \
  --dry --print \
  src/

# 실제 적용
npx jscodeshift \
  -t node_modules/@wishket/design-system/scripts/codemods/menu-split.ts \
  --extensions=tsx,ts --parser=tsx \
  src/

# 후처리
yarn prettier:write src/
yarn type-check
```

### 한계와 주의사항

- **alias import 미지원.** `import { Menu as M } from '@wishket/design-system'`는 안전 변환 불가. TODO 코멘트만 추가.
- **spread props 미지원.** `<Menu {...props} />`는 prop 존재 여부를 정적으로 결정할 수 없어 변환되지 않습니다. 다음 명령으로 후보를 찾으세요.

  ```bash
  grep -rn "Menu {\\.\\.\\." src/
  ```

- **모순 케이스(`href` + `onClick`).** 기존 `Menu`는 `href`가 있으면 anchor로 렌더되며 `onClick`은 무시됐습니다. codemod는 의도를 알 수 없으므로 변환하지 않고 TODO 코멘트를 답니다 — 컨슈머가 `MenuLink`로 옮길지 `MenuButton`으로 옮길지 직접 결정해야 합니다.
