create-entity-server 0.0.27 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/package.json +1 -1
  2. package/template/configs/auth/password.json +17 -5
  3. package/template/configs/cache.json +2 -1
  4. package/template/entities/System/Auth/account.json +158 -18
  5. package/template/entities/System/Auth/account_audit.json +11 -3
  6. package/template/entities/System/Auth/account_device.json +13 -12
  7. package/template/entities/System/Auth/account_login_log.json +21 -6
  8. package/template/entities/System/Auth/account_oauth.json +42 -13
  9. package/template/entities/System/Auth/api_keys.json +18 -9
  10. package/template/entities/System/Auth/identity_verification.json +18 -7
  11. package/template/entities/System/Auth/license.json +41 -20
  12. package/template/entities/System/Auth/password_history.json +4 -5
  13. package/template/entities/System/Auth/rbac_roles.json +10 -7
  14. package/template/entities/System/Backup/backup_log.json +11 -7
  15. package/template/entities/System/Email/smtp_log.json +9 -5
  16. package/template/entities/System/Email/smtp_msg.json +5 -5
  17. package/template/entities/System/Notification/alimtalk_log.json +32 -12
  18. package/template/entities/System/Notification/alimtalk_msg.json +23 -9
  19. package/template/entities/System/Notification/friendtalk_log.json +59 -18
  20. package/template/entities/System/Notification/friendtalk_msg.json +43 -15
  21. package/template/entities/System/Notification/sms_log.json +55 -16
  22. package/template/entities/System/Notification/sms_msg.json +45 -15
  23. package/template/entities/System/Notification/sms_verification.json +17 -7
  24. package/template/entities/System/Payment/pg_cancel.json +25 -10
  25. package/template/entities/System/Payment/pg_order.json +40 -13
  26. package/template/entities/System/Payment/pg_webhook_log.json +24 -9
  27. package/template/entities/System/Push/push_log.json +18 -3
  28. package/template/entities/System/Push/push_msg.json +6 -5
  29. package/template/entities/System/Storage/file_backup_log.json +11 -6
  30. package/template/entities/System/Storage/file_download_log.json +7 -5
  31. package/template/entities/System/Storage/file_meta.json +20 -7
  32. package/template/entities/System/system_audit_log.json +38 -34
  33. package/template/entities/company.json +5 -2
  34. package/template/entities/goods.json +10 -3
  35. package/template/entities/todo.json +4 -2
  36. package/template/samples/entities/01_basic_fields.json +15 -2
  37. package/template/samples/entities/02_types_and_defaults.json +15 -5
  38. package/template/samples/entities/03_hash_and_unique.json +18 -3
  39. package/template/samples/entities/04_fk_and_composite_unique.json +18 -3
  40. package/template/samples/entities/05_cache.json +15 -9
  41. package/template/samples/entities/06_history_and_hard_delete.json +19 -6
  42. package/template/samples/entities/07_license_scope.json +18 -3
  43. package/template/samples/entities/08_hook_sql.json +24 -5
  44. package/template/samples/entities/09_hook_entity.json +12 -2
  45. package/template/samples/entities/10_hook_submit_delete.json +14 -5
  46. package/template/samples/entities/11_hook_webhook.json +20 -6
  47. package/template/samples/entities/12_hook_push.json +15 -2
  48. package/template/samples/entities/13_read_only.json +8 -4
  49. package/template/samples/entities/14_optimistic_lock.json +13 -2
  50. package/template/samples/entities/15_reset_defaults.json +7 -1
  51. package/template/samples/entities/16_isolated_license.json +19 -6
  52. package/template/scripts/reset-all.sh +57 -3
  53. package/template/scripts/run.sh +56 -6
  54. package/template/templates/ocr/business_reg.json +145 -0
  55. package/template/templates/ocr/career_cert.json +93 -0
  56. package/template/templates/ocr/driver_license.json +89 -0
  57. package/template/templates/ocr/facility_card.json +82 -0
  58. package/template/templates/ocr/id_card.json +55 -0
  59. package/template/templates/ocr/invoice.json +92 -0
  60. package/template/templates/ocr/namecard.json +116 -0
  61. package/template/templates/ocr/prompts/business_reg.json +14 -0
  62. package/template/templates/ocr/prompts/career_cert.json +16 -0
  63. package/template/templates/ocr/prompts/driver_license.json +14 -0
  64. package/template/templates/ocr/prompts/facility_card.json +15 -0
  65. package/template/templates/ocr/prompts/general.json +13 -0
  66. package/template/templates/ocr/prompts/id_card.json +11 -0
  67. package/template/templates/ocr/prompts/invoice.json +17 -0
  68. package/template/templates/ocr/prompts/namecard.json +15 -0
  69. package/template/templates/ocr/prompts/receipt.json +14 -0
  70. package/template/templates/ocr/receipt.json +79 -0
  71. package/template/configs/auth/identity.json +0 -27
  72. package/template/configs/auth/privacy_policy.json +0 -28
  73. package/template/configs/auth/two_factor.json +0 -12
  74. package/template/configs/extensions/pg.json +0 -37
  75. package/template/configs/extensions/tax-invoice.json +0 -59
  76. package/template/configs/notification/alimtalk.json +0 -75
  77. package/template/configs/notification/sms.json +0 -54
  78. package/template/templates/email/account/dormancy_warning.html +0 -20
  79. package/template/templates/email/account/password_expiry_warning.html +0 -21
  80. package/template/templates/email/auth/email_verification.html +0 -18
  81. package/template/templates/email/auth/force_reset.html +0 -18
  82. package/template/templates/email/auth/password_reset.html +0 -19
  83. package/template/templates/email/auth/verification.html +0 -15
  84. package/template/templates/email/auth/verification_link.html +0 -25
@@ -1,28 +1,37 @@
1
1
  {
2
2
  "name": "project",
3
3
  "description": "submit & delete 훅 예제 — 프로젝트 생성 시 기본 태스크 자동 생성, 삭제 시 연관 데이터 정리",
4
- "index": {
4
+ "fields": {
5
5
  "owner_seq": {
6
+ "index": true,
6
7
  "comment": "프로젝트 소유자 user seq",
7
8
  "required": true
8
9
  },
9
10
  "name": {
11
+ "index": true,
10
12
  "comment": "프로젝트명",
11
13
  "required": true
12
14
  },
13
15
  "status": {
16
+ "index": true,
14
17
  "comment": "진행 상태",
15
- "type": ["planning", "active", "on_hold", "completed", "cancelled"],
18
+ "type": [
19
+ "planning",
20
+ "active",
21
+ "on_hold",
22
+ "completed",
23
+ "cancelled"
24
+ ],
16
25
  "default": "planning"
17
26
  },
18
27
  "due_date": {
28
+ "index": true,
19
29
  "comment": "마감일 (*_date → DATE 자동 추론)"
20
30
  },
21
31
  "task_count": {
32
+ "index": true,
22
33
  "comment": "태스크 수 (*_count → INT 자동 추론)"
23
- }
24
- },
25
- "fields": {
34
+ },
26
35
  "description": {
27
36
  "type": "text",
28
37
  "comment": "프로젝트 설명"
@@ -1,34 +1,48 @@
1
1
  {
2
2
  "name": "payment",
3
3
  "description": "webhook 훅 예제 — 결제 완료/환불 시 외부 정산 서비스 실시간 통보",
4
- "index": {
4
+ "fields": {
5
5
  "order_seq": {
6
+ "index": true,
6
7
  "comment": "주문 seq",
7
8
  "required": true
8
9
  },
9
10
  "user_seq": {
11
+ "index": true,
10
12
  "comment": "결제자 user seq",
11
13
  "required": true
12
14
  },
13
15
  "amount": {
16
+ "index": true,
14
17
  "comment": "결제 금액 (*_amount → DECIMAL(15,2) 자동 추론)",
15
18
  "required": true
16
19
  },
17
20
  "method": {
21
+ "index": true,
18
22
  "comment": "결제 수단",
19
- "type": ["card", "bank_transfer", "virtual_account", "point"],
23
+ "type": [
24
+ "card",
25
+ "bank_transfer",
26
+ "virtual_account",
27
+ "point"
28
+ ],
20
29
  "required": true
21
30
  },
22
31
  "status": {
32
+ "index": true,
23
33
  "comment": "결제 상태",
24
- "type": ["pending", "paid", "cancelled", "refunded"],
34
+ "type": [
35
+ "pending",
36
+ "paid",
37
+ "cancelled",
38
+ "refunded"
39
+ ],
25
40
  "default": "pending"
26
41
  },
27
42
  "paid_at": {
43
+ "index": true,
28
44
  "comment": "결제 완료일시 (*_at → DATETIME 자동 추론)"
29
- }
30
- },
31
- "fields": {
45
+ },
32
46
  "pg_transaction_id": {
33
47
  "type": "varchar(100)",
34
48
  "comment": "PG사 거래 ID"
@@ -1,25 +1,36 @@
1
1
  {
2
2
  "name": "delivery",
3
3
  "description": "push 훅 예제 — 배송 상태 변경 시 고객에게 푸시 알림 전송 (FCM / APNs 공통, 다단계 상태 알림)",
4
- "index": {
4
+ "fields": {
5
5
  "order_seq": {
6
+ "index": true,
6
7
  "comment": "주문 seq",
7
8
  "required": true
8
9
  },
9
10
  "customer_seq": {
11
+ "index": true,
10
12
  "comment": "고객 user seq",
11
13
  "required": true
12
14
  },
13
15
  "tracking_number": {
16
+ "index": true,
14
17
  "comment": "운송장 번호",
15
18
  "unique": true
16
19
  },
17
20
  "carrier": {
21
+ "index": true,
18
22
  "comment": "택배사",
19
- "type": ["cj", "lotte", "hanjin", "post", "direct"],
23
+ "type": [
24
+ "cj",
25
+ "lotte",
26
+ "hanjin",
27
+ "post",
28
+ "direct"
29
+ ],
20
30
  "default": "cj"
21
31
  },
22
32
  "status": {
33
+ "index": true,
23
34
  "comment": "배송 상태",
24
35
  "type": [
25
36
  "ready",
@@ -32,9 +43,11 @@
32
43
  "default": "ready"
33
44
  },
34
45
  "estimated_date": {
46
+ "index": true,
35
47
  "comment": "예상 도착일 (*_date → DATE 자동 추론)"
36
48
  },
37
49
  "delivered_at": {
50
+ "index": true,
38
51
  "comment": "배송 완료일시 (*_at → DATETIME 자동 추론)"
39
52
  }
40
53
  },
@@ -4,18 +4,22 @@
4
4
  "read_only": true,
5
5
  "hard_delete": true,
6
6
  "history_ttl": 0,
7
- "index": {
7
+ "fields": {
8
8
  "actor_seq": {
9
+ "index": true,
9
10
  "comment": "행위자 user seq (null = 시스템)"
10
11
  },
11
12
  "entity_name": {
13
+ "index": true,
12
14
  "comment": "대상 엔티티명",
13
15
  "required": true
14
16
  },
15
17
  "entity_seq": {
18
+ "index": true,
16
19
  "comment": "대상 레코드 seq"
17
20
  },
18
21
  "action": {
22
+ "index": true,
19
23
  "comment": "수행된 작업",
20
24
  "type": [
21
25
  "INSERT",
@@ -29,15 +33,15 @@
29
33
  "required": true
30
34
  },
31
35
  "ip_address": {
36
+ "index": true,
32
37
  "comment": "요청 IP",
33
38
  "type": "varchar(45)"
34
39
  },
35
40
  "result_code": {
41
+ "index": true,
36
42
  "comment": "결과 코드 (HTTP 상태)",
37
43
  "type": "int"
38
- }
39
- },
40
- "fields": {
44
+ },
41
45
  "before_snapshot": {
42
46
  "type": "json",
43
47
  "comment": "변경 전 데이터 스냅샷"
@@ -2,28 +2,39 @@
2
2
  "name": "inventory",
3
3
  "description": "optimistic_lock 예제 — 동시 수정 충돌 방지가 필요한 재고 엔티티",
4
4
  "optimistic_lock": true,
5
- "index": {
5
+ "fields": {
6
6
  "product_seq": {
7
+ "index": true,
7
8
  "comment": "상품 seq",
8
9
  "required": true,
9
10
  "unique": true
10
11
  },
11
12
  "warehouse_code": {
13
+ "index": true,
12
14
  "comment": "창고 코드",
13
15
  "required": true
14
16
  },
15
17
  "qty_available": {
18
+ "index": true,
16
19
  "comment": "가용 재고 수량 (*_qty → INT 자동 추론)"
17
20
  },
18
21
  "qty_reserved": {
22
+ "index": true,
19
23
  "comment": "예약된 재고 수량 (*_qty → INT 자동 추론)"
20
24
  },
21
25
  "location": {
26
+ "index": true,
22
27
  "comment": "창고 내 위치 (예: A-01-03)"
23
28
  },
24
29
  "last_stocked_at": {
30
+ "index": true,
25
31
  "comment": "마지막 입고일시 (*_at → DATETIME 자동 추론)"
26
32
  }
27
33
  },
28
- "unique": [["product_seq", "warehouse_code"]]
34
+ "unique": [
35
+ [
36
+ "product_seq",
37
+ "warehouse_code"
38
+ ]
39
+ ]
29
40
  }
@@ -1,19 +1,23 @@
1
1
  {
2
2
  "name": "country",
3
3
  "description": "reset_defaults 예제 — reset-all 실행 시 주요 국가 코드 자동 시딩",
4
- "index": {
4
+ "fields": {
5
5
  "code": {
6
+ "index": true,
6
7
  "comment": "ISO 3166-1 alpha-2 국가 코드 (예: KR, US)",
7
8
  "required": true,
8
9
  "unique": true
9
10
  },
10
11
  "name_ko": {
12
+ "index": true,
11
13
  "comment": "국가명 (한국어)"
12
14
  },
13
15
  "name_en": {
16
+ "index": true,
14
17
  "comment": "국가명 (영어)"
15
18
  },
16
19
  "region": {
20
+ "index": true,
17
21
  "comment": "대륙/지역",
18
22
  "type": [
19
23
  "asia",
@@ -25,9 +29,11 @@
25
29
  ]
26
30
  },
27
31
  "phone_code": {
32
+ "index": true,
28
33
  "comment": "국가 전화 코드 (예: +82)"
29
34
  },
30
35
  "is_active": {
36
+ "index": true,
31
37
  "comment": "서비스 지원 여부 (is_* → TINYINT(1) 자동 추론)",
32
38
  "default": true
33
39
  }
@@ -2,34 +2,43 @@
2
2
  "name": "organization",
3
3
  "description": "isolated: license 예제 — 이 엔티티가 테넌트(라이선스) 경계를 정의하는 루트 엔티티. license_seq FK 없이 license_seq 컬럼을 직접 소유",
4
4
  "isolated": "license",
5
- "index": {
5
+ "fields": {
6
6
  "name": {
7
+ "index": true,
7
8
  "comment": "조직명",
8
9
  "required": true,
9
10
  "unique": true
10
11
  },
11
12
  "plan": {
13
+ "index": true,
12
14
  "comment": "구독 플랜",
13
- "type": ["free", "starter", "pro", "enterprise"],
15
+ "type": [
16
+ "free",
17
+ "starter",
18
+ "pro",
19
+ "enterprise"
20
+ ],
14
21
  "default": "free"
15
22
  },
16
23
  "max_members": {
24
+ "index": true,
17
25
  "comment": "최대 팀원 수",
18
26
  "type": "uint",
19
27
  "default": 5
20
28
  },
21
29
  "owner_seq": {
30
+ "index": true,
22
31
  "comment": "소유자 account seq"
23
32
  },
24
33
  "is_active": {
34
+ "index": true,
25
35
  "comment": "활성 여부 (is_* → TINYINT(1) 자동 추론)",
26
36
  "default": true
27
37
  },
28
38
  "expires_at": {
39
+ "index": true,
29
40
  "comment": "구독 만료일시 (*_at → DATETIME 자동 추론)"
30
- }
31
- },
32
- "fields": {
41
+ },
33
42
  "billing_email": {
34
43
  "type": "varchar(255)",
35
44
  "comment": "청구용 이메일"
@@ -38,7 +47,11 @@
38
47
  "comment": "조직 설정 (중첩 fields 그룹)",
39
48
  "fields": {
40
49
  "theme": {
41
- "type": ["light", "dark", "system"],
50
+ "type": [
51
+ "light",
52
+ "dark",
53
+ "system"
54
+ ],
42
55
  "comment": "기본 UI 테마",
43
56
  "default": "system"
44
57
  },
@@ -1,15 +1,27 @@
1
1
  #!/bin/bash
2
2
 
3
+ set -e
4
+ set -o pipefail
5
+
3
6
  # Reset all entity tables and seed default data
4
7
 
5
8
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
9
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
10
+ RESET_LOG=""
11
+
12
+ cleanup() {
13
+ if [ -n "$RESET_LOG" ] && [ -f "$RESET_LOG" ]; then
14
+ rm -f "$RESET_LOG"
15
+ fi
16
+ }
17
+
18
+ trap cleanup EXIT
7
19
 
8
20
  cd "$PROJECT_ROOT"
9
21
 
10
22
  # Load language from .env
11
23
  if [ -f .env ]; then
12
- LANGUAGE=$(grep '^LANGUAGE=' .env | cut -d '=' -f2)
24
+ LANGUAGE=$(grep '^LANGUAGE=' .env | cut -d '=' -f2 || true)
13
25
  fi
14
26
  LANGUAGE=${LANGUAGE:-ko}
15
27
 
@@ -76,10 +88,52 @@ case "$1" in
76
88
  echo "⚙️ 필수 엔티티 확인 중..."
77
89
  fi
78
90
  LANGUAGE="$LANGUAGE" "$SCRIPT_DIR/normalize-entities.sh" --apply
91
+ RESET_LOG=$(mktemp)
79
92
  if [ "$1" = "--force" ]; then
80
- "$PROJECT_ROOT/bin/entity-cli" reset-all --apply --force
93
+ set +e
94
+ "$PROJECT_ROOT/bin/entity-cli" reset-all --apply --force 2>&1 | tee "$RESET_LOG"
95
+ RESET_STATUS=${PIPESTATUS[0]}
96
+ set -e
81
97
  else
82
- "$PROJECT_ROOT/bin/entity-cli" reset-all --apply
98
+ set +e
99
+ "$PROJECT_ROOT/bin/entity-cli" reset-all --apply 2>&1 | tee "$RESET_LOG"
100
+ RESET_STATUS=${PIPESTATUS[0]}
101
+ set -e
102
+ fi
103
+
104
+ if [ "$RESET_STATUS" -ne 0 ]; then
105
+ exit "$RESET_STATUS"
106
+ fi
107
+
108
+ if ! grep -q "Reset completed successfully!" "$RESET_LOG"; then
109
+ exit 0
110
+ fi
111
+
112
+ # 새로 생성된 API 키 조회 후 .env 업데이트
113
+ NEW_API_KEY=$("$PROJECT_ROOT/bin/entity-cli" api-key show --seq=1 --reveal-secret 2>/dev/null | grep -E '^key_value' | awk '{print $NF}')
114
+ NEW_HMAC=$("$PROJECT_ROOT/bin/entity-cli" api-key show --seq=1 --reveal-secret 2>/dev/null | grep -E '^hmac_secret' | awk '{print $NF}')
115
+
116
+ if [ -n "$NEW_API_KEY" ] && [ -n "$NEW_HMAC" ]; then
117
+ echo ""
118
+ if [ "$LANGUAGE" = "en" ]; then
119
+ echo "🔑 New API Key generated:"
120
+ else
121
+ echo "🔑 새 API 키가 생성되었습니다:"
122
+ fi
123
+ echo " ENTITY_API_KEY = $NEW_API_KEY"
124
+ echo " ENTITY_HMAC_SECRET = $NEW_HMAC"
125
+
126
+ # 게이트웨이 .env 자동 업데이트 (같은 저장소 내에 있는 경우)
127
+ GW_ENV="$PROJECT_ROOT/packages/entity-app-server/.env"
128
+ if [ -f "$GW_ENV" ]; then
129
+ sed -i "s|^ENTITY_API_KEY=.*|ENTITY_API_KEY=$NEW_API_KEY|" "$GW_ENV"
130
+ sed -i "s|^ENTITY_HMAC_SECRET=.*|ENTITY_HMAC_SECRET=$NEW_HMAC|" "$GW_ENV"
131
+ if [ "$LANGUAGE" = "en" ]; then
132
+ echo " ✓ Updated: packages/entity-app-server/.env"
133
+ else
134
+ echo " ✓ 업데이트 완료: packages/entity-app-server/.env"
135
+ fi
136
+ fi
83
137
  fi
84
138
  ;;
85
139
  *)
@@ -62,6 +62,9 @@ is_running() {
62
62
  if [ -n "$port_pid" ] && kill -0 "$port_pid" 2>/dev/null; then
63
63
  return 0
64
64
  fi
65
+ if is_port_in_use; then
66
+ return 0
67
+ fi
65
68
  return 1
66
69
  fi
67
70
  local pid
@@ -70,6 +73,9 @@ is_running() {
70
73
  if [ -n "$port_pid" ] && kill -0 "$port_pid" 2>/dev/null; then
71
74
  return 0
72
75
  fi
76
+ if is_port_in_use; then
77
+ return 0
78
+ fi
73
79
  return 1
74
80
  fi
75
81
  if kill -0 "$pid" 2>/dev/null; then
@@ -80,6 +86,10 @@ is_running() {
80
86
  return 0
81
87
  fi
82
88
 
89
+ if is_port_in_use; then
90
+ return 0
91
+ fi
92
+
83
93
  return 1
84
94
  }
85
95
 
@@ -89,6 +99,26 @@ find_pid_by_port() {
89
99
  ss -ltnp 2>/dev/null | grep ":$port " | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | head -n 1
90
100
  }
91
101
 
102
+ is_port_in_use() {
103
+ local port
104
+ port=$(get_server_value "port" "3400")
105
+ ss -ltn 2>/dev/null | grep -q ":$port "
106
+ }
107
+
108
+ show_port_in_use_message() {
109
+ local port
110
+ port=$(get_server_value "port" "3400")
111
+ if [ "$LANGUAGE" = "en" ]; then
112
+ echo "❌ Port $port is already in use, but the owning PID could not be identified."
113
+ echo "Check with: ss -ltnp | grep :$port"
114
+ echo "Or: lsof -iTCP:$port -sTCP:LISTEN -n -P"
115
+ else
116
+ echo "❌ 포트 $port 가 이미 사용 중이지만, 점유 PID를 식별할 수 없습니다."
117
+ echo "확인: ss -ltnp | grep :$port"
118
+ echo "또는: lsof -iTCP:$port -sTCP:LISTEN -n -P"
119
+ fi
120
+ }
121
+
92
122
  stop_pid_with_confirm() {
93
123
  local pid="$1"
94
124
  local reason="$2"
@@ -157,6 +187,10 @@ stop_server() {
157
187
  stop_pid_with_confirm "$port_pid" "port fallback"
158
188
  return $?
159
189
  fi
190
+ if is_port_in_use; then
191
+ show_port_in_use_message
192
+ return 1
193
+ fi
160
194
  if [ "$LANGUAGE" = "en" ]; then
161
195
  echo "ℹ️ Server is not running (pid file not found)."
162
196
  else
@@ -191,6 +225,10 @@ stop_server() {
191
225
  stop_pid_with_confirm "$port_pid" "port fallback"
192
226
  return $?
193
227
  fi
228
+ if is_port_in_use; then
229
+ show_port_in_use_message
230
+ return 1
231
+ fi
194
232
  if [ "$LANGUAGE" = "en" ]; then
195
233
  echo "ℹ️ Stale pid file removed (process not found)."
196
234
  else
@@ -305,10 +343,16 @@ fi
305
343
  case "$MODE" in
306
344
  dev|development)
307
345
  if is_running; then
308
- if [ "$LANGUAGE" = "en" ]; then
309
- echo "❌ Server already running (pid: $(cat "$PID_FILE")). Stop first: ./run.sh stop"
346
+ local running_pid
347
+ running_pid=$(find_pid_by_port)
348
+ if [ -n "$running_pid" ]; then
349
+ if [ "$LANGUAGE" = "en" ]; then
350
+ echo "❌ Server already running (pid: $running_pid). Stop first: ./run.sh stop"
351
+ else
352
+ echo "❌ 이미 서버가 실행 중입니다 (pid: $running_pid). 먼저 중지하세요: ./run.sh stop"
353
+ fi
310
354
  else
311
- echo "❌ 이미 서버가 실행 중입니다 (pid: $(cat "$PID_FILE")). 먼저 중지하세요: ./run.sh stop"
355
+ show_port_in_use_message
312
356
  fi
313
357
  exit 1
314
358
  fi
@@ -329,10 +373,16 @@ case "$MODE" in
329
373
 
330
374
  start)
331
375
  if is_running; then
332
- if [ "$LANGUAGE" = "en" ]; then
333
- echo "❌ Server already running (pid: $(cat "$PID_FILE")). Stop first: ./run.sh stop"
376
+ local running_pid
377
+ running_pid=$(find_pid_by_port)
378
+ if [ -n "$running_pid" ]; then
379
+ if [ "$LANGUAGE" = "en" ]; then
380
+ echo "❌ Server already running (pid: $running_pid). Stop first: ./run.sh stop"
381
+ else
382
+ echo "❌ 이미 서버가 실행 중입니다 (pid: $running_pid). 먼저 중지하세요: ./run.sh stop"
383
+ fi
334
384
  else
335
- echo "❌ 이미 서버가 실행 중입니다 (pid: $(cat "$PID_FILE")). 먼저 중지하세요: ./run.sh stop"
385
+ show_port_in_use_message
336
386
  fi
337
387
  exit 1
338
388
  fi