MAKITTDocs

Shop App HCS 검증 워크플로우

docs/knowhow/shop-app-hcs-verification.md

Shop App HCS 검증 워크플로우

HCS 에셋/템플릿을 수정한 후 Shop App에서 실제 동작을 검증하는 전체 과정을 정리한다.

개요

Builder 캔버스에서 HCS를 편집하는 것은 Claude에게 어렵다. 대신 JSON 직접 수정 → S3 업로드 → 컴파일 → Shop에서 검증하는 우회 경로가 훨씬 효율적이다.

JSON 수정 → S3 업로드 → compile-template API → ISR 갱신 → Chrome MCP 검증

1단계: HCS JSON 수정

파일 위치

파일용도
apps/builder/data/hcs-asset/{asset}.jsonHCS 에셋 원본 (재사용 가능한 컴포넌트)
apps/builder/data/templates/{template}/shop.json템플릿 (shop 전체 페이지 구조)

에셋 파일은 개발용 원본이고, **실제 Shop에 반영되는 건 shop.json**이다. 둘 다 동기화해서 수정한다.

JSON 수정 팁

HCS JSON은 수천 줄이므로 Node.js 스크립트로 프로그래밍 방식 수정이 안전하다:

// /tmp/fix-something.js const fs = require('fs'); const path = 'apps/builder/data/templates/checkout-flow-test/shop.json'; const data = JSON.parse(fs.readFileSync(path, 'utf8')); function findNode(node, id) { if (!node || typeof node !== 'object') return null; if (node.id === id) return node; if (Array.isArray(node.children)) { for (const child of node.children) { const found = findNode(child, id); if (found) return found; } } return null; } function removeChild(node, childId) { if (Array.isArray(node.children)) { const idx = node.children.findIndex(c => typeof c === 'object' && c.id === childId); if (idx !== -1) return node.children.splice(idx, 1)[0]; for (const child of node.children) { if (typeof child === 'object') { const removed = removeChild(child, childId); if (removed) return removed; } } } return null; } // 페이지 찾기 const checkoutPage = data.pages.find(p => p.pageId === 'checkout'); const tree = checkoutPage.content[0]; // 노드 조작 const targetNode = findNode(tree, 'co_some_node'); targetNode.visible = 'checkout.shippingAddress'; // visible 조건 변경 // 저장 + 검증 fs.writeFileSync(path, JSON.stringify(data, null, 2)); JSON.parse(fs.readFileSync(path, 'utf8')); // 파싱 검증 console.log('Done');
node /tmp/fix-something.js

수정 후 반드시 JSON 유효성 검증:

node -e "JSON.parse(require('fs').readFileSync('path/to/shop.json','utf8')); console.log('VALID')"

2단계: S3 업로드

aws s3 cp apps/builder/data/templates/{template}/shop.json \ s3://makitt-dev-storage/templates/{template}/shop.json \ --profile makitt-dev --region ap-northeast-2

tokens.json, presets.json 등 지원 파일도 변경했으면 함께 업로드:

for f in tokens.json presets.json node-defaults.json; do aws s3 cp "apps/builder/data/templates/{template}/$f" \ "s3://makitt-dev-storage/templates/{template}/$f" \ --profile makitt-dev --region ap-northeast-2 done

3단계: 컴파일

Compiler 서버(port 3004)에 compile-template API 호출:

curl -s -X POST http://localhost:3004/compile-template \ -H 'Content-Type: application/json' \ -d '{"templateId": "{template}"}' | python3 -m json.tool

성공 응답:

{ "success": true, "compiledUrl": "..." }

전제: Compiler 서버(port 3004)가 실행 중이어야 한다. curl -s http://localhost:3004/health로 확인하고, 응답이 없으면 pnpm dev --filter=compiler로 실행.

4단계: 브라우저 리로드

Shop App은 ISR(60초 revalidate)을 사용하지만, dev 모드(pnpm dev)에서는 ISR 캐시가 적용되지 않는다. 매 요청마다 fresh fetch하므로 컴파일 직후 브라우저 hard reload(Cmd+Shift+R)만 하면 즉시 반영된다.

production build(pnpm build && pnpm start)에서만 60초 ISR 캐시가 적용된다. 로컬 개발에서는 신경 쓸 필요 없다.

5단계: Chrome MCP로 검증

사전 준비

Chrome이 remote debugging 모드로 실행 중이어야 한다. MCP 연결을 시도해서 list_pages가 응답하면 이미 실행 중이므로 스킵. 연결이 안 되면:

makitt chrome

이 명령은 --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug-profile로 Chrome을 시작한다.

검증 워크플로우

1. list_pages      → 열린 탭 확인
2. select_page     → 대상 탭 선택
3. navigate_page   → URL 이동 또는 reload
4. take_screenshot → 현재 화면 캡처
5. take_snapshot   → DOM 구조 확인 (uid 포함)
6. click           → 버튼/요소 클릭 (uid 기반)
7. list_network_requests → API 호출 결과 확인
8. get_network_request   → 특정 요청의 body/response 확인
9. list_console_messages → JS 에러 확인

실전 예시: Checkout 결제 flow 검증

# 1. 페이지 리로드
navigate_page(type: "reload", ignoreCache: true)

# 2. 스크린샷으로 현재 상태 확인
take_screenshot(fullPage: true, filePath: "/tmp/checkout.png")

# 3. 스냅샷에서 "저장 후 계속하기" 버튼 uid 찾기
take_snapshot(filePath: "/tmp/snap.txt")
# → grep "저장 후 계속" /tmp/snap.txt → uid=5_53

# 4. 버튼 클릭
click(uid: "5_53")

# 5. API 결과 확인
list_network_requests(resourceTypes: ["fetch", "xhr"])
# → PUT /checkout/.../shipping [200]

# 6. 특정 요청의 request/response body 확인
get_network_request(reqid: 2778)

네트워크 디버깅 패턴

API 호출이 실패하면:

  1. list_network_requests — 실패한 요청 찾기 (status 400/403/500)
  2. get_network_request — request body가 올바른지, response error message 확인
  3. 서버 로그 확인tail -50 /tmp/makitt-shop-api.log | grep ERROR
  4. 직접 curl로 테스트 — 동일 요청을 서버에 직접 보내서 서버 문제인지 프록시 문제인지 분리

한방에 실행 (업로드 + 컴파일)

자주 쓰는 원라이너:

cd /Users/chanhyupkim/Desktop/MAKITT/makitt-root/makitt-client && \ aws s3 cp apps/builder/data/templates/{template}/shop.json \ s3://makitt-dev-storage/templates/{template}/shop.json \ --profile makitt-dev --region ap-northeast-2 && \ curl -s -X POST http://localhost:3004/compile-template \ -H 'Content-Type: application/json' \ -d '{"templateId": "{template}"}' | python3 -c " import json,sys d=json.load(sys.stdin) print('Compile:', 'OK' if d.get('success') else d.get('error','FAIL')) "

@makitt/api 변경 시

packages/api/src/ 하위 파일(action-registry, domain API 등)을 수정했으면 반드시 빌드 필요:

pnpm build --filter=@makitt/api

이유: Shop App의 @makitt/api 의존은 workspace:* + exports: "./dist/index.mjs"이고, next.config.mjstranspilePackages에 포함되어 있지 않다. 따라서 빌드된 dist/를 직접 참조하며, 소스 변경만으로는 반영되지 않는다.

빌드 후 Shop App 재시작은 불필요하다. Next.js dev 서버가 모듈 캐시를 적절히 갱신한다.

반면 apps/shop/src/ 하위 파일 변경은 HMR로 즉시 반영된다.

트러블슈팅

403 에러

  • API 경로 불일치: 서버 controller의 실제 경로와 클라이언트 API 경로 비교. 예) /shipping-address vs /shipping
  • shopId 불일치: 토큰의 shopId와 URL path의 shopId가 같은지 확인
  • CORS: shop app proxy rewrite 경로 확인 (next.config.mjs)

400 에러

  • 필수 필드 누락: request body에 서버가 기대하는 필드가 빠져있는지 확인
  • checkout 상태: DONE 상태면 수정 불가. 새 checkout 생성 필요
  • Zod validation: action-registry의 schema와 HCS target의 필드가 일치하는지 확인

500 에러

  • AWS SSO 만료: aws sts get-caller-identity --profile makitt-dev로 확인. 만료 시 aws sso login --profile makitt-dev
  • 서버 로그 확인: /tmp/makitt-shop-api.log

HCS form submit 데이터가 안 넘어감

HCS form submit에서 target이 정의되면 formData는 무시된다. form field 값을 전달하려면:

{ "submit": { "action": "checkout.updateAddress", "target": { "checkoutId": "{{checkout.checkoutId}}", "email": "{{form.values.email}}", "firstName": "{{form.values.firstName}}" } } }

target을 생략하면 formData 전체가 action params로 전달된다.

컴파일 후 변경이 반영되지 않음

  • dev 모드: hard reload(Cmd+Shift+R)만 하면 즉시 반영. 캐시 문제 아님
  • 반영 안 되면: compile-template 응답이 success인지 확인. S3에 올린 파일이 실제로 변경됐는지 확인
  • 그래도 안 되면: DynamoDB의 compiledShopHcsUrl이 올바른 S3 경로를 가리키는지 확인