구워진 index.html 이 미러 사진 61장(`/assets/mirror/*`)과 번들 css/js 를 루트 절대경로로 가리켰다. 그 파일들은 레포가 아니라 도커 볼륨에만 있어서, 레포만 받은 사람은 흰 화면을 본다. 번들은 더 나쁘다 — 파일명이 콘텐츠 해시라 그 기계에서 구운 해시가 다르면 404 다. 실측: 구운 html 이 /assets 를 63곳 가리키고 있었다. - img/mirror/ 61장 · vendor/ 2개를 레포로 끌어들였다 - patch_stay.py: 경로를 `/s/stay/img/mirror/` · `/s/stay/vendor/` 로 바꿔 박고, 레포에 없는 자산이 있으면 **빌드를 멈춘다**. 끝에 `/assets` 잔재가 남아도 멈춘다 - audit-all.mjs: 히어로 사진 검사를 새 경로로 - SECTIONS.md: 설명서에서 **작업 지시서**로 다시 씀. 프롬프트를 여기 다시 적지 않고 PROMPTS.md 의 절을 가리킨다(일정은 §3-1/3-2/3-3 세 단계다). 제품 쪽 짝 파일 표 추가 - README.md: "보고 규칙" 절 신설 — 실행하지 않은 것을 됐다고 쓰지 않는다 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
295 lines
20 KiB
Python
295 lines
20 KiB
Python
"""/s/stay 목업 index.html 을 다시 만든다 — payload 교체 + 주입분(css·js) 삽입.
|
|
|
|
★ 이 사이트는 payload 가 없다(목업). 프리렌더 재굽기 대상이 아니라서, 고칠 자리는
|
|
구워진 index.html 하나다. 손으로 열어 고치지 않고 이 스크립트로 만든다 —
|
|
두 번째로 고칠 때 무엇을 바꿨는지가 파일 대신 여기 남아야 한다.
|
|
|
|
입력: stay-orig/index.html (볼륨에서 꺼낸 원본) · stay-payload-new.json · inject.css · inject.js
|
|
출력: stay-work/index.html
|
|
"""
|
|
import json
|
|
import re
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
SP = Path(__file__).parent
|
|
|
|
# ── 캐치프레이즈 100개 ────────────────────────────────────────────────────────
|
|
# 사실이 아니라 문구다(출처가 붙는 값이 아니다). 근거는 payload 의 시설·위치 값 —
|
|
# 적산가옥 두 동 · 히로쓰 가옥 옆 · 마당과 정원 · 창고형 카페 · 매일 세탁하는 침구 · 최대 4인.
|
|
GENERAL = [
|
|
"백 년 된 집에서 하룻밤",
|
|
"담 너머는 히로쓰 가옥입니다",
|
|
"독채 두 동, 마당은 각자 씁니다",
|
|
"기와 아래에서 아침을 맞습니다",
|
|
"새소리로 눈이 떠지는 집",
|
|
"골목이 조용해지는 시간에 도착하세요",
|
|
"침구는 매일 새것처럼 나갑니다",
|
|
"마당 평상에 앉으면 항구 소리가 들립니다",
|
|
"1920년대에 지은 집, 지금 쓰는 집",
|
|
"대문을 닫으면 골목이 멀어집니다",
|
|
"창고를 고친 카페 공간이 딸려 있습니다",
|
|
"밤에는 정원 석등만 켜 둡니다",
|
|
"초원사진관까지 걸어서 3분",
|
|
"원도심 한가운데, 차는 세워 두세요",
|
|
"A동은 일본식, B동은 모던입니다",
|
|
"네 사람까지 묶는 독채입니다",
|
|
"오후 세 시부터 이 집은 손님 것입니다",
|
|
"이 골목에서는 서두를 일이 없습니다",
|
|
"백 년 된 기와가 비를 받습니다",
|
|
"여행이라기보다 하루의 이사입니다",
|
|
"군산에서 가장 조용한 담 안쪽",
|
|
"이 집의 시간은 조금 느립니다",
|
|
"마당, 정원, 그리고 평상 하나",
|
|
"짐을 풀면 골목부터 걸어 보세요",
|
|
"창을 열면 옆집 기와가 보입니다",
|
|
"담배 냄새가 없는 집입니다",
|
|
"이불에서 볕 냄새가 납니다",
|
|
"두 동 모두 남의 인기척이 없습니다",
|
|
"내항까지 걸어 십 분",
|
|
"오래된 것을 고쳐 쓰는 집",
|
|
"부엌이 있어 아침은 지어 드셔도 됩니다",
|
|
"밤에는 골목 등만 남습니다",
|
|
"백 년 된 집이 아직 손님을 받습니다",
|
|
"개항장 골목의 끝집입니다",
|
|
"문패보다 기와를 보고 찾아오세요",
|
|
"걸어서 도는 여행에 알맞은 자리",
|
|
"마루에 앉는 시간이 일정의 절반입니다",
|
|
"좁은 골목 끝, 대문 하나",
|
|
"여기서는 계획을 반쯤 접어도 됩니다",
|
|
"하루를 통째로 쓰는 숙소",
|
|
]
|
|
SEASON = {
|
|
"봄": ["정원에 꽃이 먼저 옵니다", "봄에는 대문을 열어 둡니다", "벚꽃 지는 골목을 걸어 오세요",
|
|
"마당에 앉는 계절이 시작됩니다", "새소리가 가장 많은 철입니다", "창을 열면 봄이 방까지 들어옵니다"],
|
|
"여름": ["처마 그늘이 가장 긴 계절입니다", "마당에 물을 뿌리면 시원해집니다", "여름 밤에는 평상이 제일 좋습니다",
|
|
"창고형 카페가 낮에는 서늘합니다", "소나기는 기와가 받아 줍니다", "해가 길어 저녁이 넉넉합니다"],
|
|
"가을": ["기와 위로 가을 볕이 마릅니다", "가을 바람이 담을 넘어옵니다", "가을 골목은 해가 빨리 눕습니다",
|
|
"창을 열어 두고 자도 되는 밤입니다", "가을에는 평상이 가장 좋은 자리입니다", "마당에 잎이 떨어지는 계절입니다"],
|
|
"겨울": ["기와에 눈이 앉으면 골목이 조용해집니다", "겨울 아침 마당은 서리 밭입니다", "이불 속에서 겨울 바람 소리를 듣습니다",
|
|
"눈 오면 정원 석등만 켜 둡니다", "항구 바람이 매운 계절입니다", "겨울에는 카페 공간이 가장 아늑합니다"],
|
|
}
|
|
MONTH = {
|
|
1: ["새해 첫 아침을 백 년 집에서", "겨울 골목이 가장 조용한 달입니다"],
|
|
2: ["아직 바람이 찬 원도심입니다", "겨울 끝을 마당에서 봅니다"],
|
|
3: ["정원에 첫 꽃이 오는 달입니다", "대문을 열어 두기 시작합니다"],
|
|
4: ["벚꽃 지는 골목을 걷는 달입니다", "마당에 앉는 시간이 길어집니다"],
|
|
5: ["새소리가 가장 많은 달입니다", "창을 열어 두고 자는 밤입니다"],
|
|
6: ["처마 그늘이 길어지는 달입니다", "여름 앞의 원도심은 한가합니다"],
|
|
7: ["소나기를 기와가 받는 달입니다", "여름 밤 평상이 제일 좋습니다"],
|
|
8: ["낮에는 카페 공간이 서늘합니다", "해가 길어 저녁이 넉넉한 달입니다"],
|
|
9: ["볕이 마르기 시작하는 달입니다", "가을이 담을 넘어오는 구월입니다"],
|
|
10: ["잎이 마당에 떨어지는 달입니다", "걷기에 가장 좋은 달입니다"],
|
|
11: ["해가 빨리 눕는 골목입니다", "이불이 가장 따뜻한 달입니다"],
|
|
12: ["기와에 눈이 앉는 달입니다", "한 해의 끝을 조용히 보내는 자리"],
|
|
}
|
|
# 키는 렌더러(use-live-weather.ts)의 분류와 같아야 한다 — 맑음·구름많음·비·눈·흐림.
|
|
WEATHER = {
|
|
"맑음": ["마당 평상에 앉으면 새소리만 들립니다", "정원 쪽으로 그늘이 길게 눕는 시간입니다", "기와가 마르는 냄새가 납니다"],
|
|
"구름많음": ["빛이 부드러운 날입니다", "골목 사진이 제일 잘 나오는 빛입니다"],
|
|
"흐림": ["빛이 낮게 깔리는 날입니다", "이런 날은 카페 공간이 가장 아늑합니다"],
|
|
"비": ["백 년 된 기와를 타고 내리는 소리가 다릅니다", "카페 공간에서 빗소리를 들으실 수 있습니다", "비 오는 골목에는 사람이 없습니다"],
|
|
"눈": ["기와에 눈이 앉으면 골목이 통째로 조용해집니다", "눈 밟는 소리가 담 안에서 크게 들립니다"],
|
|
}
|
|
|
|
items = [{"text": t, "kind": "general"} for t in GENERAL]
|
|
items += [{"text": t, "kind": "season", "season": s} for s, lst in SEASON.items() for t in lst]
|
|
items += [{"text": t, "kind": "month", "month": m} for m, lst in MONTH.items() for t in lst]
|
|
items += [{"text": t, "kind": "weather", "weather": w} for w, lst in WEATHER.items() for t in lst]
|
|
|
|
payload = json.loads((SP / "stay-payload-new.json").read_text(encoding="utf-8"))
|
|
payload["narrative"]["catchphrases"] = {"version": 1, "items": items}
|
|
(SP / "stay-payload-new.json").write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
|
|
|
|
# ── 머뭄이 만든 노래 5곡 ──────────────────────────────────────────────────────
|
|
# ★ 가요 다방 섹션에는 **넣지 않는다** (2026-09-10 사장님: "가요다방에 왜 우리 노래 넣음???").
|
|
# 그 섹션은 '이 도시를 노래한 곡' 을 모으는 자리다 — 숙소가 만든 곡을 거기 섞으면
|
|
# 군산 노래를 보러 온 사람에게 광고를 끼워 넣는 꼴이고, 출처·연도 칸도 채울 수 없다.
|
|
# 플레이어(헤더)만 이 목록을 읽는다. 자리는 narrative.ownSongs 다.
|
|
OWN_SONGS = [
|
|
("머뭄의 아침", "01-mumum-morning.mp3", "#c8873a"),
|
|
("CHECK IN · 머뭄", "02-check-in.mp3", "#2f6b8f"),
|
|
("Stay in 머뭄", "03-stay-in.mp3", "#6b7f5b"),
|
|
("오늘은 안 갈란다", "04-not-going.mp3", "#a34a2a"),
|
|
("군산에 두고 온 님", "05-gunsan-nim.mp3", "#4a3f6b"),
|
|
]
|
|
payload["narrative"]["ownSongs"] = {
|
|
"version": 1,
|
|
"items": [{"title": title, "artist": "스테이,머뭄", "labelColor": color, "own": True,
|
|
"audioUrl": "/s/stay/audio/" + filename}
|
|
for title, filename, color in OWN_SONGS],
|
|
}
|
|
|
|
# ── 객실 사진·내용을 야놀자 등록본으로 **교체**한다 ──────────────────────────────
|
|
# 출처: https://nol.yanolja.com/stay/domestic/10068088 (사장님이 직접 올린 등록본)
|
|
#
|
|
# ★ 배정을 눈으로 하지 않는다 (2026-09-10 사장님: "B동 10개 A동 12개로 파악이 되는데")
|
|
# 처음엔 사진을 열어 보고 A/B 를 갈랐다 — 11/5/공용 6 이 나왔고 실제는 **A동 12 · B동 10** 이다.
|
|
# 등록본 페이지의 Next.js 데이터에 `roomTypes[].photos` 가 객실별로 들어 있다.
|
|
# `scripts/mockup/rooms.json` 이 그걸 뽑아 둔 것이고, 여기 순서도 등록본 순서 그대로다.
|
|
#
|
|
# ★ 촬영본(h-*)과 객실 사진(a-*·b-*)은 다른 묶음이다.
|
|
# 등록본에는 업로드 배치가 둘 있다 — 2026/03/25(객실별 22장, 현장 사진)과
|
|
# 2026/04/03(촬영본 6장, 밝고 선명하다). **히어로·대표는 촬영본에서 고른다**
|
|
# (2026-09-10: "히어로 이미지 화질이 별론데" — 현장 사진을 대표로 세웠던 것).
|
|
# 촬영본은 객실 목록에 안 들어간다 — 등록본이 객실에 붙여 놓지 않았다.
|
|
#
|
|
# ★ alt 는 사진을 열어 보고 적는다. 렌더러는 alt 없는 사진을 객실에서 아예 뺀다(derive.ts:112).
|
|
# ★ 파일은 `/s/stay/img/` 에 둔다 — `out/assets` 는 프리렌더가 관리하는 자리다(음원과 같은 규약).
|
|
YANOLJA_URL = "https://nol.yanolja.com/stay/domestic/10068088"
|
|
|
|
# (파일, 객실, alt, 분류) — 객실은 A동·B동, None 이면 숙소 공용(갤러리에만 선다)
|
|
ROOM_PHOTOS = [
|
|
("h-01.jpg", None, "기와 지붕 아래로 넓은 우드 데크와 정원이 이어지는 외관", "외관"),
|
|
("h-02.jpg", None, "동백이 핀 마당과 우드 파사드 현관으로 이어지는 데크", "외관"),
|
|
("h-03.jpg", None, "흰 벽과 밝은 바닥에 올리브색 소파와 긴 벽난로를 둔 거실", "거실"),
|
|
("h-04.jpg", None, "옛 서까래를 드러낸 천장에 라탄 조명이 걸린 거실", "거실"),
|
|
("h-05.jpg", None, "문 사이로 보이는 흰 앤티크 침대와 검은 서랍장", "침실"),
|
|
("h-06.jpg", None, "캡슐 커피머신과 레트로 전자레인지가 놓인 주방 어메니티", "주방"),
|
|
|
|
("a-01.jpg", "A동", "긴 벽난로와 벽걸이 TV, 좌식 스툴을 둔 거실", "거실"),
|
|
("a-02.jpg", "A동", "베이지 소파와 원형 원목 테이블, 장지문 창이 있는 거실", "거실"),
|
|
("a-03.jpg", "A동", "옛 서까래 천장에 라탄 조명이 걸린 거실과 벽난로", "거실"),
|
|
("a-04.jpg", "A동", "어두운 원목 하부장에 냉장고와 전자레인지를 둔 주방", "주방"),
|
|
("a-05.jpg", "A동", "붉은 벽돌 벽에 벤치와 색색 의자를 둔 창고형 카페 공간", "카페"),
|
|
("a-06.jpg", "A동", "석재 담과 소나무가 둘러싼 마당의 야외 티테이블", "외관"),
|
|
("a-07.jpg", "A동", "원목 벽과 바닥을 지나 침실로 이어지는 복도", "실내"),
|
|
("a-08.jpg", "A동", "장지문 창 옆에 흰 침구를 깐 침대가 놓인 침실", "침실"),
|
|
("a-09.jpg", "A동", "정원이 보이는 큰 장지문 창과 벽걸이 TV", "거실"),
|
|
("a-10.jpg", "A동", "원형 거울과 욕조, 어메니티를 둔 욕실", "욕실"),
|
|
("a-11.jpg", "A동", "우드 외벽 건물과 담 사이 마당에 놓인 야외 테이블", "외관"),
|
|
("a-12.jpg", "A동", "흰 벽돌 벽과 우드 담에 걸린 스테이 머뭄 간판", "외관"),
|
|
|
|
("b-01.jpg", "B동", "초가 파라솔을 세운 데크와 검은 현관문이 있는 외관", "외관"),
|
|
("b-02.jpg", "B동", "유리문을 열어 둔 카페 공간과 데크의 파라솔 테이블", "외관"),
|
|
("b-03.jpg", "B동", "큰 창으로 빛이 드는 거실의 다크그린 소파와 샹들리에", "거실"),
|
|
("b-04.jpg", "B동", "흰 타일 벽에 민트색 냉장고와 토스터를 둔 주방", "주방"),
|
|
("b-05.jpg", "B동", "흰 앤티크 침대와 꽃 모양 조명이 있는 침실", "침실"),
|
|
("b-06.jpg", "B동", "초록 서랍장과 거울, 유리문이 있는 침실 앞 공간", "실내"),
|
|
("b-07.jpg", "B동", "커피 레터링 벽과 빨간 스툴을 둔 카페 공간", "카페"),
|
|
("b-08.jpg", "B동", "욕조와 세면대, 블라인드 창이 있는 욕실", "욕실"),
|
|
("b-09.jpg", "B동", "흰 벽돌 아치 현관에 우편함이 걸린 대문", "외관"),
|
|
("b-10.jpg", "B동", "흰 벽돌 벽을 따라 디딤돌이 놓인 통로", "외관"),
|
|
]
|
|
# ★ 히어로와 소개 사진은 **원래 것으로 되돌린다** (2026-09-10 사장님: "히어로랑 소개 이미지 원복해")
|
|
# 히어로는 대표 사진(isPrimary)을, 소개는 **대표가 아닌 첫 사진**을 쓴다
|
|
# (HeroSection.tsx:40 primaryImage · AboutSection.tsx:23 galleryImages().find(!isPrimary)).
|
|
# 그래서 원본 media 를 앞에 그대로 두고 등록본 사진을 뒤에 붙인다 — 순서가 곧 그 두 자리다.
|
|
# 객실 안내는 그대로 등록본 사진만 쓴다(units.mediaIds).
|
|
HERO_PHOTO = None # 대표는 원본 사진이 계속 갖는다
|
|
|
|
_NS = uuid.UUID("6f1f3f5e-0000-4000-8000-000000000000") # 재실행해도 같은 mediaId 가 나오게
|
|
|
|
new_media = []
|
|
per_unit = {"A동": [], "B동": []}
|
|
for filename, room, alt, category in ROOM_PHOTOS:
|
|
media_id = str(uuid.uuid5(_NS, filename))
|
|
new_media.append({
|
|
"mediaId": media_id,
|
|
"url": "/s/stay/img/" + filename,
|
|
"alt": alt,
|
|
"category": category,
|
|
"width": 1280,
|
|
"height": 800,
|
|
"isPrimary": filename == HERO_PHOTO,
|
|
"sourceType": 3,
|
|
"originUrl": YANOLJA_URL,
|
|
"unitId": None,
|
|
})
|
|
if room:
|
|
per_unit[room].append(media_id)
|
|
|
|
# 원본(네이버 예약본 미러) 사진을 앞에, 등록본 사진을 뒤에.
|
|
ORIGINAL_MEDIA = json.loads((SP / "stay-payload.json").read_text(encoding="utf-8"))["media"]
|
|
payload["media"] = ORIGINAL_MEDIA + new_media
|
|
|
|
# ★ 등록본에만 있던 값 — 침대 구성 · 면적 · 객실 시설.
|
|
# bed_type·room_size 는 렌더러가 칩으로 세우는 키다(derive.ts:87). 없으면 '기준 2인 / 최대 4인'
|
|
# 만 서고, 손님이 가장 먼저 묻는 "침대가 뭔가 · 몇 평인가" 가 화면에 없다.
|
|
NOW = "2026-09-10T00:00:00+00:00"
|
|
ROOM_FACTS = {
|
|
"A동": [("bed_type", "침대", "퀸 침대 1개"), ("room_size", "면적", "52")],
|
|
"B동": [("bed_type", "침대", "킹 침대 1개")],
|
|
}
|
|
ROOM_UNITS = {"room_size": "m²"}
|
|
FACILITIES = "욕조 · 개별 화장실 · 주방 · 다이닝룸 · 테라스 · 벽난로 · OTT · 어메니티 · 취사 가능"
|
|
|
|
for unit in payload["units"]:
|
|
room = unit["name"][:2]
|
|
extra = list(ROOM_FACTS.get(room, [])) + [("room_facilities", "객실 시설", FACILITIES)]
|
|
have = {f["key"] for f in unit["facts"]}
|
|
for key, label, value in extra:
|
|
if key in have:
|
|
for fact in unit["facts"]:
|
|
if fact["key"] == key:
|
|
fact["value"] = value
|
|
continue
|
|
unit["facts"].append({
|
|
"key": key, "label": label, "value": value, "unit": ROOM_UNITS.get(key),
|
|
"type": "number" if key == "room_size" else "text",
|
|
"scope": "unit", "status": 3, "sourceType": 3, "sourceUrl": YANOLJA_URL,
|
|
"critical": False, "required": False, "unitId": unit["unitId"],
|
|
"collectedAt": NOW, "verifiedAt": NOW,
|
|
})
|
|
# 옛 사진은 남기지 않는다 — 등록본이 그 객실에 붙여 놓은 사진만 그 순서로 세운다.
|
|
unit["mediaIds"] = per_unit.get(room, [])
|
|
|
|
# ── index.html 조립 ──────────────────────────────────────────────────────────
|
|
html = (SP / "orig" / "index.html").read_text(encoding="utf-8")
|
|
|
|
# payload 교체. </script> 를 만들지 않도록 문자열 안의 '<' 를 이스케이프한다(구운 것과 같은 규약).
|
|
blob = json.dumps(payload, ensure_ascii=False).replace("</", "<\\/")
|
|
html, n = re.subn(r"<script>window\.__SITE_PAYLOAD__=.*?</script>",
|
|
lambda _m: "<script>window.__SITE_PAYLOAD__=" + blob + "</script>",
|
|
html, count=1, flags=re.S)
|
|
assert n == 1, "payload 스크립트를 못 찾았다"
|
|
|
|
# ★ 이용 정보 **맨 아래 구분선**을 지운다 (2026-09-10 사장님 지시).
|
|
# 항목이 끝난 자리에 선이 한 줄 더 그어져 있어, 빈 칸이 하나 더 있는 것처럼 보인다.
|
|
# 항목(바비큐 이용) 자체는 그대로 둔다 — 지우라고 한 것은 선이다.
|
|
# ★ 마크업을 고쳐 지우지 않는다 — React 가 payload 로 다시 그리면서 원래 class 를 되돌린다
|
|
# (실측 2026-09-10: 치환은 됐는데 화면에는 선이 그대로 있었다). 지우는 자리는 주입 CSS 다.
|
|
|
|
css = (SP / "inject.css").read_text(encoding="utf-8")
|
|
js = (SP / "inject.js").read_text(encoding="utf-8")
|
|
assert "</style>" not in css and "</script>" not in js
|
|
|
|
block = ("\n <style id=\"w4d-css\">\n" + css + "\n </style>\n"
|
|
" <script id=\"w4d-js\">\n" + js + "\n </script>\n ")
|
|
# 치환문(block)에 JS 의 정규식(\s 등)이 섞여 있어 그대로 넘기면 파이썬이 이스케이프로 읽는다 — 람다로 넘긴다.
|
|
html, n = re.subn(r"\n?\s*</body>", lambda _m: block + "</body>", html, count=1)
|
|
assert n == 1, "</body> 를 못 찾았다"
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# 레포 안으로 끌어들이기 — 다른 사람 기계에서도 그대로 떠야 한다.
|
|
#
|
|
# ★ 왜 (2026-09-11, 사장님: "다른 환경에서 local/s/stay 치면 나오냐고")
|
|
# 구워진 index.html 은 미러 사진 61장(`/assets/mirror/*`)과 번들 css/js 를 **루트 절대경로**로
|
|
# 가리킨다. 그 파일들은 레포가 아니라 도커 볼륨에만 있다 — 레포만 받은 사람은 흰 화면을 본다.
|
|
# 번들은 더 나쁘다: 파일명이 콘텐츠 해시라 그 기계에서 구운 해시가 다르면 404 다.
|
|
# 그래서 둘 다 `img/mirror/` · `vendor/` 로 복사해 두고, 경로를 `/s/stay/…` 로 바꿔 박는다.
|
|
# 이러면 nginx 가 `/s/` 를 파일로 주기만 하면 되고 번들 해시와 무관해진다.
|
|
# (대신 렌더러를 고쳐도 이 목업은 안 따라간다 — 목업은 원래 재굽기 대상이 아니라 그게 맞다.)
|
|
missing = []
|
|
for name in sorted(set(re.findall(r"/assets/mirror/[0-9a-f]+\.\w+", html))):
|
|
if not (SP / "img" / "mirror" / name.rsplit("/", 1)[1]).exists():
|
|
missing.append(name)
|
|
for name in sorted(set(re.findall(r"/assets/index-[\w.-]+\.(?:css|js)", html))):
|
|
if not (SP / "vendor" / name.rsplit("/", 1)[1]).exists():
|
|
missing.append(name)
|
|
assert not missing, f"레포에 없는 자산 {len(missing)}개: {missing[:3]} … `vendor.sh` 를 돌려라"
|
|
|
|
html = re.sub(r"/assets/mirror/", "/s/stay/img/mirror/", html)
|
|
html = re.sub(r"/assets/(index-[\w.-]+\.(?:css|js))", r"/s/stay/vendor/\1", html)
|
|
left = re.findall(r"/assets/[^\"\\')\s]+", html)
|
|
assert not left, f"아직 /assets 를 가리키는 자리가 있다: {left[:5]}"
|
|
|
|
out = SP / "build"
|
|
out.mkdir(exist_ok=True)
|
|
(out / "index.html").write_text(html, encoding="utf-8")
|
|
|
|
env = json.loads({s["id"]: s for s in payload["theme"]["sections"]}["itinerary"]["data"])
|
|
print(f"index.html {len(html):,}자 · 일정 {len(env['items'])}개 · 캐치프레이즈 {len(items)}개")
|
|
print(" " + " · ".join(f"{k} {sum(1 for i in items if i['kind'] == k)}"
|
|
for k in ("general", "season", "month", "weather")))
|