
    Ri&;                        d Z ddlZddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZ ej                  j                  d e e
e      j#                         j$                  j$                              Z e e
e      dz  dz        Z e e
e      d	z  d
z        ZdZ G d d      ZdedefdZdej6                  fdZddeee      ddfdZedk(  r e        yy)ux   
preview_manager.py - 개발 서버 프리뷰 관리 도구

Thor (개발2팀 백엔드 담당) 작성
TDD: GREEN phase
    N)datetime)Path)OptionalWORKSPACE_ROOTconfigzpreview-ports.jsonmemoryzpreview-state.jsonz/home/jay/projectsc            	           e Zd ZdZeeefdedededdfdZde	fdZ
dd	Zde	fd
Zde	ddfdZdedee   fdZdedee   fdZdedefdZdedefdZdedefdZdede	fdZdede	fdZdee	   fdZdee   fdZy)PreviewManageru?   개발 서버 프리뷰 프로세스를 관리하는 클래스.config_path
state_pathprojects_dirreturnNc                     t        |      | _        t        |      | _        t        |      | _        | j	                         | _        y N)r   r   r   r   _load_configr   )selfr   r   r   s       H/home/jay/workspace/.worktrees/task-2117-dev1/scripts/preview_manager.py__init__zPreviewManager.__init__   s:      ,z* .'')    c                     | j                   j                         st        d| j                          | j                   j                  dd      5 }t	        j
                  |      cddd       S # 1 sw Y   yxY w)u5   설정 파일(preview-ports.json)을 로드합니다.u*   설정 파일을 찾을 수 없습니다: rutf-8encodingN)r   existsFileNotFoundErroropenjsonloadr   fs     r   r   zPreviewManager._load_config+   sj    &&(#&PQUQaQaPb$cdd""3"9 	 Q99Q<	  	  	 s   A//A8c                     | j                   j                  dd      5 }t        j                  | j                  |dd       |j                  d       ddd       y# 1 sw Y   yxY w)	u=   변경된 설정(next_port 등)을 파일에 저장합니다.wr   r      Findentensure_ascii
N)r   r   r   dumpr   writer    s     r   _save_configzPreviewManager._save_config2   sS    ""3"9 	QIIdkk1QUCGGDM	 	 	s   5AA%c                     | j                   j                         sdi iS | j                   j                  dd      5 }t        j                  |      cddd       S # 1 sw Y   yxY w)u\   상태 파일(preview-state.json)을 로드합니다. 파일이 없으면 빈 상태 반환.previewsr   r   r   N)r   r   r   r   r   r    s     r   
load_statezPreviewManager.load_state8   sV    %%'##__!!#!8 	 A99Q<	  	  	 s   AA$statec                 
   | j                   j                  j                  dd       | j                   j                  dd      5 }t	        j
                  ||dd       |j                  d	       d
d
d
       y
# 1 sw Y   y
xY w)u)   상태를 JSON 파일에 저장합니다.Tparentsexist_okr#   r   r   r$   Fr%   r(   N)r   parentmkdirr   r   r)   r*   )r   r/   r!   s      r   
save_statezPreviewManager.save_state?   sj    $$TD$A__!!#!8 	AIIeQqu=GGDM	 	 	s   +A99Bproject_dirc                 r   t        |      }|dz  j                         ry|dz  }|j                         r	 t        j                  |j	                  d            }i }|j                  |j                  di              |j                  |j                  di              |j                  |j                  di              d	|v ry
d|v ry	 |dz  }|j                         r%|j	                  d      j                         }d|v ry|dz  j                         ryy# t        j                  t        f$ r Y jw xY w)uJ  프로젝트 디렉토리를 분석하여 프로젝트 타입을 반환합니다.

        우선순위:
          1. manage.py → django
          2. package.json에 next → nextjs
          3. package.json에 vite → vite
          4. requirements.txt에 flask 또는 app.py → flask
          5. 미감지 → None
        	manage.pydjangozpackage.jsonr   r   dependenciesdevDependenciespeerDependenciesnextnextjsvitezrequirements.txtflaskapp.pyN)
r   r   r   loads	read_textupdategetJSONDecodeErrorOSErrorlower)r   r7   dir_pathpackage_json_pathpkgall_depsreq_pathcontents           r   detect_project_typez"PreviewManager.detect_project_typeJ   s<    $ {"**, %~5##%jj!2!<!<g!<!NO!# ;<(92 >?(:B ?@X%#X%! & 00??(('(:@@BG'! x'') (('2 s   BD D D65D6project_typec                 N    g dg dddgg dd}||vrt        d|       ||   S )uP   프로젝트 타입에 따른 개발 서버 시작 명령어를 반환합니다.)npmrundevpython3rB   )rV   r9   	runserver)r?   r@   rA   r:   u)   지원하지 않는 프로젝트 타입: )
ValueError)r   rQ   commandss      r   get_start_commandz PreviewManager.get_start_command|   sD     ,)*;	*
 x'HWXX%%r   project_namec                     | j                   j                  di       }||v r||   S | j                   d   }|| j                   d   |<   |dz   | j                   d<   | j                          |S )u   프로젝트에 할당된 포트를 반환합니다.

        설정에 있으면 설정 포트, 없으면 next_port를 할당하고 증가 후 저장.
        port_assignments	next_port   )r   rF   r+   )r   r[   assignmentsnew_ports       r   get_portzPreviewManager.get_port   ss    
 '+kkoo6H"&M;&|,, K08@&'5#+a<K r   portc                     t        j                   t         j                  t         j                        5 }|j                  d       |j	                  d|f      }|dk7  cddd       S # 1 sw Y   yxY w)u`   포트 사용 가능 여부를 확인합니다. True = 사용 가능, False = 이미 사용 중.r_   z	127.0.0.1r   N)socketAF_INETSOCK_STREAM
settimeout
connect_ex)r   rc   sockresults       r   check_port_availablez#PreviewManager.check_port_available   sW    ]]6>>6+=+=> 	$OOA__k4%89FQ;	 	 	s   )A&&A/c                 L    | j                   j                  dd      }d| d| dS )u?   프리뷰 URL을 반환합니다: http://<tailscale_ip>:<port>/tailscale_ip	localhostzhttp://:/)r   rF   )r   rc   rn   s      r   get_preview_urlzPreviewManager.get_preview_url   s+     KKOONKHavQ//r   c           	      <   | j                   |z  }|j                         st        d|       | j                  t	        |            }|t        d|       | j                  |      }| j                  |      st        d| d      | j                  |      }|dk(  r	|d| gz   }t        j                  j                         }|dv rt	        |      |d	<   n!|d
k(  rt	        |      |d<   t	        |      |d	<   t        t              dz  }|j                  dd       |d| dz  }|j!                  d      5 }	t#        j$                  |t	        |      ||	|	d      }
ddd       | j'                  |      }t)        j*                         j-                  d      }| j/                         }|
j0                  |||t	        |      d|d   |<   | j3                  |       |||
j0                  |||d}t5        d| d| d|
j0                   d       |S # 1 sw Y   xY w)uP   개발 서버를 시작하고 상태를 저장한 후 결과를 반환합니다.u6   프로젝트 디렉토리를 찾을 수 없습니다: Nu3   프로젝트 타입을 감지할 수 없습니다: u   포트 u   가 이미 사용 중입니다.r:   z0.0.0.0:)r?   r@   PORTrA   FLASK_RUN_PORTlogsTr1   zpreview-z.loga)cwdenvstdoutstderrstart_new_sessionseconds)timespec)rc   pidrQ   url
started_atr7   r-   )r[   rc   r   rQ   r   r   z[START] u    →  (PID: ))r   r   r   rP   strrX   rb   rl   RuntimeErrorrZ   osenvironcopyr   _WORKSPACE_ROOTr5   r   
subprocessPopenrr   r   now	isoformatr.   r   r6   print)r   r[   r7   rQ   rc   cmdry   log_dirlog_filelog_fprocr   r   r/   rk   s                  r   startzPreviewManager.start   sC   '',6!!##&\]h\i$jkk//K0@ARS^R_`aa}}\*((..MNOO$$\2 8#8D6*++C jjoo--d)CKW$$'IC !d)CK'&0dT2x~T::]]3 	5##$"&D	 ""4(\\^--y-A
!88(${++
j,' 	 )88($
 	eC5zCDC	 	s   9%HHc                 b   | j                         }|j                  di       }||vrt        d|       ||   }|d   }	 t        j                  |t
        j                         t        d| d| d       |d   |= | j                  |       ||d	d
S # t        $ r t        d| d       Y 7w xY w)u;   개발 서버를 중지하고 상태에서 제거합니다.r-   u)   실행 중인 프리뷰가 없습니다: r   [STOP] r   u   ) 종료 신호 전송z[WARN] PID u%   가 이미 종료된 상태입니다.stopped)r[   r   status)
r.   rF   KeyErrorr   killsignalSIGTERMr   ProcessLookupErrorr6   )r   r[   r/   r-   infor   s         r   stopzPreviewManager.stop   s    !:r2x'F|nUVV%;	LGGC(GL>5KLM *l+ ,SINN " 	LKu$IJK	Ls    6B B.-B.c                 <   | j                         }|j                  di       }g }g }|j                         D ]B  \  }}|d   }t        |      }|r|j	                  d|i|ddi       2|j	                  |       D |r|D ]  }|d   |= 
 | j                  |       |S )uQ   활성 프리뷰 목록을 반환합니다. PID 생존 여부를 확인합니다.r-   r   r[   aliveT)r.   rF   items_is_pid_aliveappendr6   )	r   r/   r-   activedead_projectsnamer   r   r   s	            r   r   zPreviewManager.status  s    !:r2#%"..* 	+JD$E{C!#&E~tKtKWdKL$$T*	+ % ,*%d+,OOE"r   c                     | j                   j                         sg S t        d | j                   j                         D              S )u>   projects_dir 아래의 디렉토리 목록을 반환합니다.c              3   V   K   | ]!  }|j                         s|j                   # y wr   )is_dirr   ).0entrys     r   	<genexpr>z/PreviewManager.list_projects.<locals>.<genexpr>!  s     \UU\\^ejj\s   )))r   r   sortediterdir)r   s    r   list_projectszPreviewManager.list_projects  s9      '')I\d.?.?.G.G.I\\\r   )r   N)__name__
__module____qualname____doc__DEFAULT_CONFIG_PATHDEFAULT_STATE_PATHDEFAULT_PROJECTS_DIRr   r   dictr   r+   r.   r6   r   rP   listrZ   intrb   boolrl   rr   r   r   r   r    r   r   r
   r
      s"   I /,0		*	* 	* 		*
 
	* d   D    ,s ,x} ,d
&c 
&d3i 
& S S    0C 0C 0B# B$ BHO O O,T
 .]tCy ]r   r
   r   r   c                 \    	 t        j                  | d       y# t        t        f$ r Y yw xY w)u'   PID가 살아있는지 확인합니다.r   TF)r   r   r   PermissionError)r   s    r   r   r   )  s.    
Q0 s    ++c                  >   t        j                  dd      } | j                  dd      }|j                  dd	      }|j	                  d
d	       |j                  dd	      }|j	                  d
d	       |j                  dd	       |j                  dd	       | S )Npreview_manageru%   개발 서버 프리뷰 관리 도구)progdescriptioncommandT)destrequiredr   u   프리뷰 서버 시작)helpr   u   프로젝트 이름r   u   프리뷰 서버 중지r   u   활성 프리뷰 목록 표시r   u'   프로젝트 디렉토리 목록 표시)argparseArgumentParseradd_subparsers
add_parseradd_argument)parser
subparsersstart_pstop_ps       r   build_parserr   7  s    $$;F &&I&EJ ##G2K#LG&;< ""60I"JF
%:; ()IJ &'PQMr   argvc                    t               }|j                  |       }t               }|j                  dk(  r.	 |j	                  |j
                        }t        d|d           y |j                  dk(  r/	 |j                  |j
                        }t        d|d	    d
       y |j                  dk(  rx|j                         }|st        d       y t        dddddddddddd       t        d       |D ].  }t        |d	   dd|d   dd|d   dd|d   dd|d    	       0 y |j                  dk(  r1|j                         }|st        d       y |D ]  }	t        |	        y y # t        $ r=}t        d| t        j                         t        j                  d       Y d }~y d }~ww xY w# t        $ r=}t        d| t        j                         t        j                  d       Y d }~y d }~ww xY w)Nr   zURL: r   u   [ERROR] start 실패: )filer_   r   r   r[   u    종료 완료u   [ERROR] stop 실패: r   u(   실행 중인 프리뷰가 없습니다.u   프로젝트z<20 u   포트z>6PIDz>8u   타입z<10z URLzF----------------------------------------------------------------------rc   r   rQ   r   u   프로젝트가 없습니다.)r   
parse_argsr
   r   r   r   r   	Exceptionsysr{   exitr   r   r   )
r   r   argsmanagerrk   er   r   projectsps
             r   mainr   O  s   ^FT"DG||w	]]499-FE&-)*
 
		\\$)),FGF>23>BC
 
	!!<=^C((2abz8C.PTUV(O N+C0F|B'qE{2&aN+C0E{m	% 
	((*12 a 
 9  	*1#.SZZ@HHQKK	  	)!-CJJ?HHQKK	s/   ,E) 3-F2 )	F/23F**F/2	G8;3G33G8__main__r   ) r   r   r   r   r   re   r   r   r   pathlibr   typingr   r   rF   r   __file__resolver4   r   r   r   r   r
   r   r   r   r   r   r   r   r   r   r   r   <module>r      s      	    
   **..!13tH~7M7M7O7V7V7]7]3^_$/(:=QQR o.9<PPQ + H] H]`s t h-- 0,xS	" ,d ,^ zF r   