시드 스크립트는 페이지 · 텀 · 설정 같은 기본 구조를 코드로 만들어 두는 스크립트입니다. 그런데 시드는 한 번만 도는 스크립트가 아닙니다. 개발하면서 열 번, 스테이징에서 한 번, 새 환경을 세울 때 또 한 번 돕니다. 그때마다 결과가 같아야 합니다 — 그것이 멱등입니다.
구조는 단순합니다. 만들기 전에 찾고, 있으면 건너뜁니다. 함정은 전부 “찾는” 쪽에 있습니다.
⚠ post_status => any 는 draft 를 놓친다
가장 흔한 형태부터 봅니다. 슬러그로 페이지가 있는지 확인하는 코드입니다.
// 이 조회는 초안 페이지를 찾지 못한다
$found = get_posts( [
'name' => 'pricing',
'post_type' => 'page',
'post_status' => 'any',
] );
이름으로 하는 조회는 단일 글 조회 경로로 잡혀 공개 상태만 봅니다. 그래서 초안으로 만들어 둔 페이지가 있어도 결과는 0건입니다.
증상이 고약합니다. “없으니 만들자” 로 이어져 삽입이 실행되고, 워드프레스는 슬러그 충돌을 피하려고 조용히 -2 를 붙입니다. 에러도 경고도 없고, 스크립트 로그에는 “생성 완료” 가 찍힙니다. 나중에 /pricing/ 이 /pricing-2/ 로 넘어가는 것을 보고서야 알게 됩니다.
해결은 상태를 전부 명시하는 것입니다.
'post_status' => [ 'publish', 'future', 'draft', 'pending', 'private', 'trash' ],
목록에서 빠뜨리기 쉬운 둘이 특히 중요합니다. future 를 빼면 예약된 글마다 사본이 생기고, trash 를 빼면 운영자가 일부러 버린 글을 재실행이 되살립니다. 휴지통에 있다는 것은 “없다” 가 아니라 “치웠다” 는 뜻입니다.
안정 키를 심고, 저장된 결과를 되읽는다
슬러그 조회에는 근본적인 약점이 있습니다. 슬러그가 한 번이라도 밀리면 다음 실행부터는 영영 못 찾습니다. 그래서 1차 조회는 우리가 직접 심은 키로 합니다.
update_post_meta( $id, '_seed_key', 'pricing:ko' );
이 키는 제목이 바뀌어도, 슬러그가 밀려도, 상태가 바뀌어도 그대로입니다. 슬러그 조회는 2차 안전망으로만 둡니다.
그리고 삽입 직후에 실제로 저장된 슬러그를 되읽습니다. 요청한 것과 다르면 경고를 냅니다 — 조용한 -2 를 소리나게 만드는 유일한 방법입니다.
if ( get_post_field( 'post_name', $id ) !== $slug ) {
WP_CLI::warning( "슬러그가 밀렸다: {$slug}" );
}
kses — 성공 로그와 함께 삽화가 사라진다
앞 회차에서 예고한 문제입니다. WP-CLI 는 사용자 0 으로 돌고, 그 상태에는 unfiltered_html 권한이 없습니다. 그래서 저장 경로의 kses 필터가 허용 태그 목록에 없는 태그를 걷어냅니다.
본문에 인라인 <svg> 삽화가 들어 있다면 그림만 통째로 사라진 채 저장됩니다. 삽입 자체는 성공하고, 반환값도 정상 ID 이고, 로그도 “생성 완료” 입니다. 화면을 열어 보기 전까지 아무도 모릅니다.
저장소가 소유한 신뢰 가능한 콘텐츠에 한해 필터를 내립니다.
kses_remove_filters();
사용자 입력에는 절대 쓰지 않습니다. 이 한 줄이 정당한 경우는 “우리가 저장소에 커밋해 둔 마크업을 그대로 넣는다” 뿐입니다.
날짜 — 갱신에서 GMT 가 남는다
글에는 로컬 날짜(post_date)와 GMT 날짜(post_date_gmt) 두 컬럼이 있습니다. 삽입에서는 로컬 날짜만 줘도 코어가 GMT 를 유도해 채웁니다. 갱신에서는 그렇지 않습니다 — 넘기지 않으면 옛 GMT 값이 그대로 남습니다.
이것이 왜 문제인가 하면, 예약 발행을 실제로 집행하는 크론은 GMT 컬럼을 기준으로 시각을 판단하기 때문입니다. 로컬 날짜만 고쳐 놓으면 목록에는 새 날짜가 보이는데 발행은 옛 시각에 일어납니다. 그래서 삽입에서도 습관적으로 명시하는 편이 낫습니다.
'post_date' => $date,
'post_date_gmt' => get_gmt_from_date( $date ),
이 크론이 왜 정시에 안 도는지는 7회차에서 따로 다룹니다.
시드는 지우지 않는다
마지막 원칙입니다. 시드는 만들거나 건너뛸 뿐이고, 삭제는 별도 인자나 별도 명령으로 분리합니다. 공유 데이터베이스에서 파괴적 동작이 기본값이 되는 순간, 그 스크립트는 아무도 안심하고 돌릴 수 없게 됩니다.
같은 원칙이 적용된 실제 절차는 작업 과정에 공개돼 있고, 관련 글은 개발 워크플로우 아카이브에 있습니다.
다음 회차
스크립트가 인자를 셋 넘게 받기 시작하면 승격할 때입니다. 다음 회차는 플러그인에 자체 WP-CLI 명령을 붙이는 방법이고, 핵심은 log · warning · error 를 구별해서 쓰는 일입니다.