# Lab เสริม — Export และ Deploy เว็บไซต์หลักสูตรไป Cloudflare

Lab นี้ต่อจาก [สร้างเว็บไซต์หลักสูตรด้วย Codex แบบ step by step](build-course-website-step-by-step.md) เมื่อผู้เรียนมีหน้า `index.html`, `style.css` และ `main.js` ที่ตรวจแล้ว เป้าหมายคือแยกหน้าเว็บออกเป็นแพ็กเกจ **Cloudflare Static Assets** ที่ตรวจง่าย แล้ว deploy ไปยัง Cloudflare account และ Worker name ของผู้เรียนเอง

ใช้เวลาประมาณ **45–60 นาทีสำหรับเรียนต่อด้วยตัวเอง** ไม่เพิ่มเวลา workshop ซึ่งยังคง **2 วัน × 4 ชั่วโมง รวม 8 ชั่วโมงและพักวันละ 10 นาที** เว็บไซต์นี้เป็นสื่อประกอบ **AI Web Studio — ออกแบบและพัฒนาเว็บไซต์ด้วย AI** ใน **977-121 Module: Website Design and Development**, มหาวิทยาลัยสงขลานครินทร์ วิทยาเขตภูเก็ต ไม่ใช่ระบบมหาวิทยาลัยอย่างเป็นทางการ

## สิ่งที่ Lab นี้ deploy

แพ็กเกจที่ export มีโครงสร้างดังนี้:

```text
artifacts/course-website-cloudflare/
├── wrangler.jsonc
├── README.md
└── public/
    ├── index.html
    ├── style.css
    ├── main.js
    ├── 404.html
    ├── _headers
    └── fonts/
```

`wrangler.jsonc` ใช้ `assets.directory: "./public"` โดยไม่มี `main`, D1 binding หรือ API route แพ็กเกจนี้จึงเป็น static site หน้าเดียว: theme, search และ filters ทำงานใน browser แต่ไม่มีฐานข้อมูล, login, LMS, form backend หรือ API ของหลักสูตรเต็ม Cloudflare รองรับ static assets โดยไม่ต้องมี Worker script และคืน 404 เมื่อไม่มี asset/script จัดการ request ตาม config ที่เลือก [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/) และ [Workers best practices](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/)

ไฟล์ต้นฉบับใน `examples/course-website/` หรือ `practice/psu-course/` จะไม่ถูกแก้โดย exporter ลิงก์แบบ root-relative ที่ชี้ไป lesson, handbook และ resources ของ course shell จะถูกเปลี่ยนเป็น absolute URL ของเว็บไซต์หลักสูตรที่มีอยู่ เพื่อให้ลิงก์ยังเปิดแหล่งเดิมได้หลังแยก deploy ส่วน font files และ license ที่เกี่ยวข้องถูกคัดลอกมาอยู่ในแพ็กเกจและ CSS ยังโหลดจาก local deployment

## ก่อนเริ่ม

- ทำ Step-by-step Lab จนได้ source ที่ต้องการเผยแพร่
- เปิด Terminal ที่ root `ai-web-studio/` ซึ่งมี `package.json` และ lockfile
- ติดตั้ง dependency แล้วด้วย `npm ci`
- เลือก Worker name ของตน เช่น `psu-course-nok-2026` ใช้ตัวพิมพ์เล็ก ตัวเลขและขีดกลาง โดยอักขระแรกและสุดท้ายต้องเป็นตัวอักษรหรือตัวเลข และต้องไม่ใช้ชื่อ `psu-course-website-demo` ของ worked example
- อ่าน [Setup Wrangler, login Cloudflare และ API token](cloudflare-wrangler-auth.md) แล้วเลือก OAuth หรือ token ของบัญชีตนเอง
- ตรวจ shared-infra ก่อนเปิด local service; Lab นี้ไม่เปิดพอร์ตใหม่และใช้ preview 3320/inspector 3322 ที่ project จองไว้เท่านั้น

Exporter ปฏิเสธชื่อ Worker ของ course shell และชื่อ `vibe-to-production-academy` แต่ผู้เรียนยังต้องไม่แก้ config หรือใช้ `--name` ไปชี้ target นั้นเอง การใช้ชื่อเฉพาะช่วยลดโอกาส deploy ทับ Worker ของเพื่อน แต่ผู้เรียนยังต้องตรวจ account และชื่อ target ก่อนคำสั่งจริงทุกครั้ง

## ก่อนนำ prompt จริงไปใช้กับงานของผู้เรียน

Prompt 08–10 ด้านล่างเป็นข้อความที่ใช้จริงกับ worked example ให้ปรับบริบทก่อนส่ง:

- รอบ 08: เปลี่ยน source เป็น `practice/psu-course` และใช้ชื่อ Worker ของตน
- รอบ 09: แทนชื่อ `psu-course-website-demo` ทุกตำแหน่งด้วย target ของตน และยืนยันบัญชีจากผล `whoami` จริง ลบข้อความที่บอกว่า parent ตรวจ auth/target หรือได้รับ code10007 แล้ว หากผู้เรียนยังไม่ได้ตรวจ อย่าส่งข้อมูลยืนยันที่ไม่จริงให้ AI
- รอบ 10: ใช้ URL จากผล deploy ของตนและรายงานที่ตนรันเสร็จแล้ว ถ้ายังไม่มี `deployment.json`/`09-handoff.md` ให้บันทึก URL/version จาก CLI ก่อน หรือปรับ prompt ให้อ่าน log จริงที่มีแทน

ชื่อบัญชีและชื่อ Worker เป็นบริบทให้ AI ตรวจเป้าหมายได้ ส่วน token/password ไม่ต้องใส่ใน prompt ทุกครั้งที่แก้ชื่อหรือ source ให้ใช้ค่าเดียวกันตั้งแต่ export จนถึง deploy และ verify

## ภาพรวมสิทธิ์แต่ละช่วง

| ช่วง | สิ่งที่ AI/ผู้เรียนทำได้ | สิ่งที่ยังไม่อนุญาต |
| --- | --- | --- |
| Prepare | อ่าน source, export ลง `artifacts/`, ตรวจไฟล์, preview และ dry-run | login, deploy จริง, สร้าง/แก้ cloud resource |
| Deploy | deploy static package ไปยัง **account + Worker name ที่ระบุ** | D1, KV, R2, routes, custom domain, secrets, deletion |
| Verify | เปิด public URL แบบ read-only และตรวจ UI/links/404 | ส่ง form, เปลี่ยน deployment หรือสร้างข้อมูล |

Dry-run เป็น local packaging/config check ไม่ใช่การอนุญาต deploy การ login สำเร็จก็ไม่ใช่การอนุญาตให้ AI เลือก target แทนผู้เรียน

## 1. ตรวจ source และ export — 10 นาที

ตรวจ source ก่อน ถ้าเป็นงานที่ทำตามตัวอย่าง ให้ใช้ `practice/psu-course`; ถ้าต้องการทำซ้ำ worked example ของผู้สอนจึงใช้ `examples/course-website`

```bash
pwd
find practice/psu-course -maxdepth 1 -type f -print
npm run export:course-site -- \
  --source practice/psu-course \
  --name psu-course-yourname
```

ผู้สอนที่ต้องการสร้างแพ็กเกจจาก worked example ใช้ source อีก path แต่ยังต้องส่งชื่อ target ที่ตั้งใจ:

```bash
npm run export:course-site -- \
  --source examples/course-website \
  --name psu-course-yourname
```

แทน `psu-course-yourname` ด้วยชื่อเฉพาะของตน ห้ามคัดลอกชื่อ deploy ของผู้สอน เมื่อ command จบ ให้ตรวจ output โดยยังไม่ login/deploy:

```bash
rg --files artifacts/course-website-cloudflare
sed -n '1,160p' artifacts/course-website-cloudflare/wrangler.jsonc
sed -n '1,200p' artifacts/course-website-cloudflare/README.md
```

จุดตรวจ:

- `wrangler.jsonc` มีชื่อที่ผู้เรียนระบุและ `assets.directory` เป็น `./public`
- ไม่มี `main`, `d1_databases`, API binding, route หรือ custom domain
- `public/` มี HTML/CSS/JS, `404.html`, `_headers`, fonts และ license ตาม manifest/README
- HTML ไม่อ้างไฟล์ใน `practice/` หรือ `examples/` ซึ่งจะไม่มีบน deployment
- ลิงก์ lesson/handbook/resources เป็น absolute URL ของ course origin ที่ระบุใน README
- source directory เดิมยังมีเนื้อหาเหมือนก่อน export

หาก config ใช้ `not_found_handling: "404-page"` ให้ยืนยันว่ามี `public/404.html`; Cloudflare ระบุว่าโหมดนี้ส่ง custom page พร้อมสถานะ 404 ส่วน `single-page-application` จะตอบ `index.html` สำหรับ path ที่ไม่ตรง asset จึงไม่เหมาะกับเป้าหมาย 404 ของ Lab นี้ [Static Assets routing](https://developers.cloudflare.com/workers/static-assets/routing/)

### Prompt 08 — เตรียมและตรวจ export โดยยังไม่ deploy

```text
เตรียม deploy เว็บไซต์ที่สร้างใน Lab ก่อนหน้าเป็น Cloudflare Workers Static Assets แบบแยก
อ่าน scripts/export-course-site.mjs, examples/course-website/index.html, style.css, main.js และ artifacts/course-deploy/research.md
ทำงานจาก repository นี้ รัน npm run export:course-site -- --source examples/course-website --name psu-course-yourname
ตรวจ artifacts/course-website-cloudflare/wrangler.jsonc ว่ามี assets.directory ./public, 404-page พร้อม404.html และไม่มี main, binding, D1, routes, secrets หรือcron
ตรวจว่าลิงก์บทเรียนไปยัง origin ของเว็บไซต์หลักสูตร ส่วนCSS/JS/fontsอยู่ในชุดexport และเก็บlicenseฟอนต์ครบ
รัน npx wrangler deploy --config artifacts/course-website-cloudflare/wrangler.jsonc --dry-run โดยบันทึกผลใน artifacts/course-deploy/site-dry-run.txt
บันทึกผลตรวจแบบสั้นใน artifacts/course-deploy/08-check.md รวมไฟล์ที่ได้และสิ่งที่dry-runพิสูจน์/ยังไม่พิสูจน์
อนุญาตสร้างเฉพาะชุดexportและหลักฐานตามpathข้างต้น ไม่แก้sourceต้นฉบับหรือconfigหลัก ไม่อ่าน/แสดงtokenหรือauth-private.txt ไม่login/logoutและยังไม่deployสาธารณะ
```

Prompt นี้ต้องอนุญาตเฉพาะ local read/write ในแพ็กเกจและ `--dry-run` พร้อมสั่งให้หยุดก่อน login หรือ deploy จริง รายงาน expected output แยกจากสิ่งที่ command พิสูจน์ได้

### ภาพหลักฐานการ export

![รอบ 08: prompt จริงที่ให้ Codexเตรียมแพ็กเกจและตรวจขอบเขต](/images/course-deploy/08-terminal-prompt.png)

*รอบ 08: prompt จริงที่ให้ Codexเตรียมแพ็กเกจและตรวจขอบเขต*

[เปิดภาพขนาดเต็ม](/images/course-deploy/08-terminal-prompt.png)

![รอบ 08: คำตอบจริงหลัง export และ dry-run ผ่าน](/images/course-deploy/08-terminal-result.png)

*รอบ 08: คำตอบจริงหลัง export และ dry-run ผ่าน*

[เปิดภาพขนาดเต็ม](/images/course-deploy/08-terminal-result.png)


ภาพควรแสดง command, รายชื่อไฟล์และส่วนสำคัญของ config จาก terminal replay ที่มาจาก log จริง ปิดบัง username/path ที่ไม่จำเป็น และห้ามสร้างภาพ OAuth, Dashboard หรือ token ปลอม ภาพหนึ่งภาพไม่พิสูจน์ว่า assets ทุกไฟล์เปิดได้

## 2. Preview แพ็กเกจเดียวกับที่จะ deploy — 5–10 นาที

หยุด preview เดิมของตนเองด้วย `Ctrl+C` ก่อน เพราะแพ็กเกจนี้ใช้พอร์ตคู่เดียวกัน ถ้าพอร์ตมีเจ้าของอื่นให้ตรวจ shared-infra และแก้การชนก่อน ไม่เลือกพอร์ตอื่นโดยเดา รันจาก root ของ source เต็ม:

```bash
npx wrangler dev \
  --config artifacts/course-website-cloudflare/wrangler.jsonc \
  --ip 127.0.0.1 --port 3320 \
  --inspector-ip 127.0.0.1 --inspector-port 3322
```

เปิด `http://127.0.0.1:3320/` นี่คือ public/ ในแพ็กเกจที่จะ deploy โดยตรง ไม่ใช่หน้า `/examples/course-website/` ของ course shell หยุดด้วย `Ctrl+C` เมื่อเสร็จ เพื่อให้พอร์ตกลับมาใช้กับ preview ของหลักสูตรได้

ตรวจอย่างน้อย:

- desktop และ 390px เปิดหน้าแรกได้
- first visit เป็น Light; toggle ไป Dark แล้ว reload ยังจำค่าได้
- search, Day 1/Day 2, empty state และ reset ทำงาน
- lesson links ออกไปยัง course origin ที่ถูกต้อง
- ไม่มีปุ่มหรือข้อความที่อ้างว่ามี API/login/backend
- เปิด path ที่ไม่มีจริงแล้วได้ `404.html` ที่ช่วยกลับหน้าแรก

บันทึกสิ่งที่เห็นเป็น local preview evidence อย่าเรียกว่า public deployment

## 3. Login และยืนยัน account — 5 นาที

ใช้วิธีใน [คู่มือ Cloudflare authentication](cloudflare-wrangler-auth.md) ไม่บันทึกภาพหน้าจอที่มี token, authorization code, cookies หรือข้อมูล account ที่ไม่จำเป็น

```bash
npx wrangler --version
npx wrangler login
npx wrangler whoami
```

บนเครื่องที่ browser callback ไม่เหมาะ ให้ใช้ device flow หรือ API token ตามคู่มือเดิม ตรวจ account ที่ `whoami` แสดงกับบัญชีสำหรับแบบฝึกหัด หากไม่ตรงให้หยุดก่อน dry-run/deploy

หลักฐานที่เก็บได้คือ Wrangler version, วิธี auth ที่เลือกโดยไม่เปิดค่า secret และ account label/ID ที่ redact ตามความจำเป็น `whoami` ไม่พิสูจน์ว่าสามารถ deploy Worker ชื่อนั้นได้

## 4. Dry-run ด้วย config ที่ export — 5 นาที

รันจาก root `ai-web-studio/` ด้วย path เดิมทุกครั้ง:

```bash
npx wrangler deploy \
  --config artifacts/course-website-cloudflare/wrangler.jsonc \
  --dry-run
```

ตรวจ output ว่า Wrangler อ่าน config และ asset directory ที่ตั้งใจ ไม่มี Worker code หรือ binding ที่ไม่ได้ขอ Dry-run อาจตรวจพบ config/ไฟล์ผิด แต่ไม่เขียน Worker และไม่พิสูจน์ credential, Worker name availability หรือ public URL ตัวเลือก `--config` และ `--dry-run` เป็นส่วนของ Wrangler deploy workflow; ตรวจ syntax กับ CLI รุ่นที่ติดตั้งและ [Wrangler commands](https://developers.cloudflare.com/workers/wrangler/commands/workers/) ก่อนใช้ในรุ่นอบรมใหม่

### ภาพหลักฐาน dry-run

![Wrangler dry-run จาก log จริง: อ่าน assets และไม่มี bindings โดยยังไม่ deploy](/images/course-deploy/dry-run-terminal.png)

*Wrangler dry-run จาก log จริง: อ่าน assets และไม่มี bindings โดยยังไม่ deploy*

[เปิดภาพขนาดเต็ม](/images/course-deploy/dry-run-terminal.png)


ภาพควรเห็นคำสั่ง `--config` และ `--dry-run` ครบ พร้อม summary ที่ไม่เปิด secret ภาพนี้ใช้ยืนยันว่ารัน dry-run กับ artifact ใด ไม่ใช้ยืนยันว่า deployment จริงสำเร็จ

## 5. อนุญาตและ deploy ไป target ของผู้เรียน — 10 นาที

ก่อนคำสั่งจริง เขียน target ลง worksheet ให้ครบ:

```text
Cloudflare account ที่ whoami แสดง: ____________________
Worker name ใน wrangler.jsonc: _________________________
Config: artifacts/course-website-cloudflare/wrangler.jsonc
Resource ที่อนุญาต: static Worker/Assets target ใหม่นี้เท่านั้น
Resource ที่ไม่อนุญาต: D1, KV, R2, routes, domain, secrets, deletion
```

เมื่อ account และชื่อถูกต้องแล้ว จึงรันคำสั่งจริงด้วย config เดียวกับ dry-run:

```bash
npx wrangler deploy \
  --config artifacts/course-website-cloudflare/wrangler.jsonc
```

คัดลอก public URL จาก output ที่ Wrangler แสดง อย่าคำนวณหรือเดา URL จาก Worker name เพราะ account อาจใช้ subdomain/config ต่างกัน Worked example ของผู้สอนอาจ override CLI ด้วย `--name psu-course-website-demo`; ชื่อนั้นสงวนสำหรับหลักฐานของผู้สอน ผู้เรียนต้องใช้ชื่อของตนที่อยู่ใน exported config

### Prompt 09 — อนุญาต deploy target ใหม่ที่ระบุเท่านั้น

```text
Deploy เว็บไซต์ Static Assets ที่เตรียมไว้ไป Cloudflare ด้วย target ที่ระบุเท่านั้น
ผู้ใช้อนุญาตให้ deploy เว็บตัวอย่างนี้แล้ว parent ตรวจ existing CLI authentication พร้อมและ Worker psu-course-website-demo ยังไม่มีในบัญชี (API code10007)
อ่าน artifacts/course-website-cloudflare/wrangler.jsonc, artifacts/course-deploy/08-check.md และ artifacts/course-deploy/site-dry-run-final.txt
Parent เพิ่มการป้องกันชื่อcourseหลักและชื่อผิดรูปแบบใน exporter หลังรอบ08 และทดสอบผ่านแล้ว; ชุดpublicและconfigdefaultยังเหมือนเดิม
อนุญาตให้สร้าง/อัปเดต Worker ชื่อ psu-course-website-demo ในบัญชีที่ CLI ใช้อยู่ สำหรับเว็บสาธิตนี้เท่านั้น
รัน npx wrangler deploy --config artifacts/course-website-cloudflare/wrangler.jsonc --name psu-course-website-demo และบันทึก output จริงไว้ artifacts/course-deploy/site-deploy.txt
ห้ามใช้ config หลักที่ root ห้าม deploy ชื่อ vibe-to-production-academy ห้ามเปลี่ยน D1, KV, R2, routes, cron, secrets, custom domain หรือทรัพยากรอื่น
ไม่ login/logout และไม่อ่าน/แสดง auth-private.txt, target-check-private.txt หรือ token
บันทึก artifacts/course-deploy/09-handoff.md โดยนำ public URL และ Current Version ID จากผล CLI จริง แยก deploy สำเร็จออกจากการตรวจเว็บซึ่งยังต้องทำรอบ10
ถ้าคำสั่งล้มเหลว ให้รายงานerrorและหยุดโดยไม่เปลี่ยนtargetเอง
```

Prompt deploy ต้องระบุ account label/ID ที่ตรวจแล้ว, Worker name, config path และอนุญาต command เดียวอย่างชัดเจน พร้อมห้ามฐานข้อมูล/bindings/routes/domains/secrets/deletion หาก target ไม่ตรงหรือ CLI ขอการเปลี่ยนแปลงเกินขอบเขต ให้หยุดและรายงานแทนการเลือกเอง

### กรณีจริงที่พบ: คำสั่งใน sandbox ไม่สำเร็จ

รอบ 09 ของ Codex ส่งคำสั่ง deploy จริง แต่จบด้วย `fetch failed` และมี error เขียน Wrangler debug log เพราะ filesystem ใน sandbox เป็น read-only จึงไม่มี URL/version ให้ยืนยันจากรอบนั้น ผู้รวมงานรันคำสั่งเดิมด้วย config และชื่อ Worker เดิมจาก terminal ที่มีสิทธิ์ แล้วเก็บ log retry แยกไว้ การ retry นี้เป็นคำสั่งของผู้รวมงาน ไม่ใช่ผลสำเร็จของ Codex รอบ 09

ถ้าผู้เรียนพบเช่นเดียวกัน ให้แยกตรวจ network และสิทธิ์ของ terminal/sandbox อ่าน error ก่อนแก้ แล้วใช้ terminal ที่ได้รับสิทธิ์กับ target เดิม ไม่แก้ด้วยการเลือกบัญชี/Worker อื่นหรือใส่ token ลง prompt และไม่ถือว่าเปลี่ยน timeout แล้วจะสำเร็จเสมอ

![รอบ 09: prompt ระบุ target และอนุญาต deploy เฉพาะเว็บตัวอย่าง](/images/course-deploy/09-terminal-prompt.png)

*รอบ 09: prompt ระบุ target และอนุญาต deploy เฉพาะเว็บตัวอย่าง*

[เปิดภาพขนาดเต็ม](/images/course-deploy/09-terminal-prompt.png)

![รอบ 09: Codex รายงานdeploy ล้มเหลว จึงยังไม่มี URL/version จากรอบนี้](/images/course-deploy/09-terminal-result.png)

*รอบ 09: Codex รายงานdeploy ล้มเหลว จึงยังไม่มี URL/version จากรอบนี้*

[เปิดภาพขนาดเต็ม](/images/course-deploy/09-terminal-result.png)

![รอบ retry จาก parent terminal: คำสั่งเดิมสำเร็จและได้ URL/version จริง](/images/course-deploy/deploy-retry-terminal.png)

*รอบ retry จาก parent terminal: คำสั่งเดิมสำเร็จและได้ URL/version จริง*

[เปิดภาพขนาดเต็ม](/images/course-deploy/deploy-retry-terminal.png)


ภาพควรเป็น terminal replay จาก deploy log จริง เห็น target, upload/deploy status และ URL ที่ Wrangler คืน โดย redact account ID เมื่อไม่จำเป็น ไม่ใช้ภาพ Dashboard ที่จัดฉากหรือ OAuth flow จำลอง และอย่าใส่ URL หาก command ยังไม่คืน URL จริง

## 6. Verify public URL — 10 นาที

เก็บ URL ที่ Wrangler คืนไว้ในตัวแปร Bash เพื่อหลีกเลี่ยงการพิมพ์หลายครั้ง:

```bash
COURSE_SITE_URL='https://URL-FROM-WRANGLER'
curl -fsSI "$COURSE_SITE_URL/"
curl -sS -o /dev/null -w '%{http_code}\n' "$COURSE_SITE_URL/path-that-does-not-exist"
```

แทนค่า placeholder ด้วย URL จริงก่อนรัน จากนั้นเปิด URL ใน browser และตรวจ:

| สถานะ | สิ่งที่ต้องสังเกต | หลักฐานที่เก็บ |
| --- | --- | --- |
| Desktop Light | หน้าแรก, hierarchy, fonts และ first-visit theme | URL + viewport + screenshot |
| Desktop Dark | toggle, readable text และ reload persistence | screenshot ก่อน/หลัง reload |
| Mobile 390px | ไม่มี horizontal overflow; controls ใช้ได้ | viewport + screenshot |
| Search/filter | ผล, จำนวน, empty state และ reset | คำค้น/ตัวกรองที่ใช้ |
| Lesson links | เปิด course origin ที่ export ระบุ | destination URL อย่างน้อย 2 ลิงก์ |
| Unknown path | ตอบด้วย 404 behavior ของแพ็กเกจ | status + หน้า 404 |
| Backend/API | ไม่มีฟังก์ชันปลอมและไม่อ้างว่ามี API | source/UI observation |

ถ้า origin ส่ง fallback HTML พร้อม HTTP status ที่ต่างจากที่คาด ให้บันทึก status จริงและเทียบ `_headers`/platform behavior ก่อนสรุป อย่าเปลี่ยนผลให้เป็น 404 เพียงเพราะเห็นหน้า 404

### รันตัวตรวจจาก terminal ที่มี browser tooling

เมื่อใช้ source เต็ม ให้รันคำสั่งนี้จาก root หลังใส่ URL จริงในตัวแปรข้างต้น:

```bash
node scripts/check-course-site.mjs --url "$COURSE_SITE_URL"
```

ตัวตรวจเขียน `artifacts/course-deploy/site-checks.json` และภาพใน `public/images/course-deploy/` ตรวจasset hashes รวมlicense, 404, controls, 1440/390pxทั้งสองธีม, accessibilityระดับserious/critical และตัวอย่างการเปิดบทเรียนจริง ถ้าเปลี่ยนcourse originตอนexport ให้เพิ่ม `--course-origin https://YOUR-COURSE-ORIGIN` ด้วย ข้อมูลนี้เป็นผลของbrowser runner; Codex รอบ 10อ่านผลที่รันเสร็จแล้วเพื่อสรุปเท่านั้น ผู้ใช้ZIPอย่างเดียวตรวจด้วยbrowserและworksheetแทน ไม่จำเป็นต้องติดตั้งtest dependencies

### Prompt 10 — ให้ AI ตรวจรายงาน public site แบบ read-only

```text
ตรวจหลักฐานหลัง deploy เว็บไซต์ตัวอย่างขึ้น Cloudflare โดยไม่เปลี่ยน deployment
อ่าน artifacts/course-deploy/09-handoff.md, deployment.json, site-checks.json และ scripts/check-course-site.mjs
ผลbrowserมาจาก parent ที่รัน node scripts/check-course-site.mjs --url https://psu-course-website-demo.rawinnipa-soft.workers.dev แล้ว พร้อมภาพใน public/images/course-deploy/
ตรวจว่าURL/versionมาจากCLIretryจริง; Codexรอบ09ล้มเหลวและparentเป็นผู้retry ไม่อ้างว่าCodex09deployสำเร็จ
ตรวจหลักฐาน asset hashes รวมfontlicenses, HTTP404สำหรับpathหายและ/api/health, Lightdefault, search/filter/reset, Light/Darkที่1440/390, theme persistence และลิงก์บทเรียนไปoriginที่ตั้งใจพร้อมHTTPสำเร็จ
เขียน artifacts/course-deploy/10-verification.md แยกสิ่งที่reportยืนยันและข้อจำกัด เช่นไม่ได้ทดสอบcustom domain, API/LMSหรือทุกbrowser
ไม่รันbrowser/deploy/loginเพิ่มเติมในรอบนี้ ไม่แก้sourceหรือconfig ไม่อ่าน/แสดงtokenหรือไฟล์private
สรุปชื่อWorker, publicURL, version, จำนวนchecks และขั้นตอนที่ผู้เรียนควรทำเมื่อแก้sourceแล้วจะdeployอีกครั้ง
```

Promptรอบนี้อ่านรายงานและsourceของตัวตรวจ ไม่รันbrowserหรือdeployเพิ่ม รายงานต้องแยกสิ่งที่ตรวจผ่านกับสิ่งที่ไม่ได้ทดสอบ และไม่อ้างว่าทุกปลายทางผ่านจากการเปิดเพียงลิงก์ตัวแทน

### ภาพหลักฐาน public site

![รอบ 10: ให้ Codexอ่านหลักฐานการตรวจจาก parent browser runner](/images/course-deploy/10-terminal-prompt.png)

*รอบ 10: ให้ Codexอ่านหลักฐานการตรวจจาก parent browser runner*

[เปิดภาพขนาดเต็ม](/images/course-deploy/10-terminal-prompt.png)

![รอบ 10: คำตอบตรวจหลักฐาน โดยไม่อ้างว่ารัน browser เอง](/images/course-deploy/10-terminal-result.png)

*รอบ 10: คำตอบตรวจหลักฐาน โดยไม่อ้างว่ารัน browser เอง*

[เปิดภาพขนาดเต็ม](/images/course-deploy/10-terminal-result.png)


ภาพควรมี URL จริง, viewport/theme และสิ่งที่กำลังพิสูจน์ ภาพหน้าจอ browser ไม่พิสูจน์ HTTP status, persistence ทุกกรณี หรือ source integrity จึงต้องอยู่คู่กับ command log และ worksheet

![เว็บไซต์ Cloudflare ที่เปิดจริง: 1440px Light](/images/course-deploy/live-1440-light.png)

*เว็บไซต์ Cloudflare ที่เปิดจริง: 1440px Light*

[เปิดภาพขนาดเต็ม](/images/course-deploy/live-1440-light.png)

![เว็บไซต์ Cloudflare ที่เปิดจริง: 1440px Dark](/images/course-deploy/live-1440-dark.png)

*เว็บไซต์ Cloudflare ที่เปิดจริง: 1440px Dark*

[เปิดภาพขนาดเต็ม](/images/course-deploy/live-1440-dark.png)

![เว็บไซต์ Cloudflare ที่เปิดจริง: 390px Light](/images/course-deploy/live-390-light.png)

*เว็บไซต์ Cloudflare ที่เปิดจริง: 390px Light*

[เปิดภาพขนาดเต็ม](/images/course-deploy/live-390-light.png)

![เว็บไซต์ Cloudflare ที่เปิดจริง: 390px Dark](/images/course-deploy/live-390-dark.png)

*เว็บไซต์ Cloudflare ที่เปิดจริง: 390px Dark*

[เปิดภาพขนาดเต็ม](/images/course-deploy/live-390-dark.png)

## ทางเลือกเมื่อมีเฉพาะแพ็กเกจ ZIP

หากผู้เรียนดาวน์โหลดเฉพาะแพ็กเกจ Cloudflare จาก Resources และไม่ได้ใช้ source repository ให้แตก ZIP แล้วเปิด Terminal ในโฟลเดอร์ที่มี `wrangler.jsonc` กับ `public/` ต้องมี Node.js/npm เพื่อใช้ `npx` แต่ไม่ต้องมี Python หรือ Node project เพิ่ม

```bash
cd /path/to/psu-course-website
npx --yes wrangler@4.130.0 --version
npx --yes wrangler@4.130.0 login
npx --yes wrangler@4.130.0 whoami
npx --yes wrangler@4.130.0 deploy --config wrangler.jsonc --dry-run
```

หลังตรวจ account, Worker name ใน config และ dry-run แล้ว คำสั่ง deploy จริงคือ:

```bash
npx --yes wrangler@4.130.0 deploy --config wrangler.jsonc
```

ใช้ Wrangler รุ่นเดียวกันใน login, dry-run และ deploy เพื่อให้หลักฐานเทียบกันได้ `npx --yes` อนุญาตให้ npm ดาวน์โหลด package ที่ pin ไว้ จึงต้องใช้อินเทอร์เน็ตและตรวจ package/version ก่อนรัน ในห้องเรียนที่มี source เต็มให้ใช้ dependency จาก lockfile เป็นทางหลัก

ปลายทาง `workers.dev` เป็น URL สาธารณะของ account ให้เผยแพร่เฉพาะข้อมูลสมมติหรือข้อมูลหลักสูตรที่อนุญาตแล้ว และไม่เพิ่ม custom domain/routes ใน Lab นี้ [workers.dev routing](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/)

## แก้ไขและ redeploy อย่างปลอดภัย

หลังพบปัญหา อย่าแก้ไฟล์ใน `artifacts/course-website-cloudflare/public/` แล้วลืม source เพราะ export รอบถัดไปจะทับการแก้นั้น ให้แก้ `practice/psu-course/` หรือ source copy ที่ตรวจแล้ว จากนั้น:

1. ตรวจ diff และรัน local QA ของ Step-by-step Lab
2. export ใหม่ด้วย `--source` และ `--name` เดิม
3. ตรวจ `wrangler.jsonc` และไฟล์ output อีกครั้ง
4. รัน dry-run ด้วย config เดิม
5. deploy ไป Worker name เดิมหลังยืนยัน account/target
6. verify public URL ใหม่และบันทึก version/time ที่ CLI แสดงจริง

หาก release ใหม่เสีย ให้คืน source จากสำเนาหรือ revision ที่เคย verify แล้ว ทำ export ใหม่ด้วยชื่อเดิม และ deploy config เดิมไป target เดิม จากนั้นตรวจ public URL ซ้ำ การกู้คืน source ไม่ได้ย้อน deployment จนกว่าจะ deploy artifact ที่คืนแล้วสำเร็จ และ static rollback นี้ไม่เกี่ยวกับ D1 เพราะแพ็กเกจไม่มี database

## Troubleshooting

| อาการ | ตรวจ | วิธีแก้ที่ขอบเขตเล็กที่สุด |
| --- | --- | --- |
| exporter หา source ไม่เจอ | อยู่ project root หรือไม่; path มีสามไฟล์หรือไม่ | ใช้ `--source practice/psu-course` หรือ path จริง |
| font/JS/CSS 404 | เปิด HTML/CSS ดู relative path และตรวจไฟล์ใน `public/` | export ใหม่; อย่าแก้ URL ให้ชี้กลับ local disk |
| lesson links เปิด 404 ใน Worker ใหม่ | link ถูก rewrite เป็น course origin หรือยัง | ตรวจ exporter input/origin แล้ว export ใหม่ |
| `whoami` แสดง account ผิด | OAuth/token environment และ account selection | หยุด deploy; กลับไป auth lab และเลือก account ใหม่ |
| dry-run พบ `main`/D1 | ใช้ config ผิดหรือ exporter output ผิด | หยุด; ตรวจ exact `--config` และ export ใหม่ |
| Worker name ชน | ชื่อใน config เป็นชื่อส่วนตัวหรือไม่ | export ใหม่ด้วยชื่อเฉพาะ; อย่า deploy ทับชื่อคนอื่น |
| deploy ผ่านแต่ไม่รู้ URL | อ่าน output/log ของ deploy เดิม | ใช้ URL ที่ CLI คืน; อย่าเดาจากชื่อ |
| path ที่ไม่มีจริงไม่เป็น 404 ตามคาด | ตรวจ status จริง, `404.html`, `_headers` และ platform behavior | บันทึก finding แล้วแก้ package/sourceก่อน redeploy |
| หน้าเปิดได้แต่ search ไม่ทำงาน | console, `main.js`, selectors และ CSP/header | แก้ source, QA, export และ deploy ใหม่ตามลำดับ |

## Worksheet ส่งงาน

```text
วันที่/เวลา:
Source path:
Source revision หรือ hash ที่ตรวจได้:
Export command:
Worker name:
Cloudflare account ที่ยืนยัน (redact ได้):

ไฟล์ใน export package:
ข้อยืนยันว่าไม่มี main/D1/API:
ผล local preview:
ผล dry-run และเวลาที่รัน:

คำอนุญาต deploy ระบุ target ว่า:
Deploy command:
Public URL จาก Wrangler:
Version/deployment identifier จาก CLI (ถ้ามี):

Desktop Light/Dark:
Mobile 390px:
Search/filter/reset:
Lesson links ที่เปิดจริง:
Unknown-path status/behavior:
สิ่งที่ยัง Not tested:

ถ้าต้องกู้คืน จะใช้ source รุ่นใด:
ผู้ตรวจและเวลาตรวจซ้ำ:
```

## Definition of Done

- source ต้นฉบับยังอยู่ครบและแก้ที่ source ก่อน export เสมอ
- exported config ชี้ `./public` และไม่มี Worker code, D1 หรือ API binding
- Worker name เป็นชื่อเฉพาะของผู้เรียนและ account ตรงกับ `whoami`
- dry-run กับ exact config ถูกบันทึกแยกจาก deploy จริง
- public URL มาจาก Wrangler output ไม่ได้คำนวณขึ้นเอง
- ตรวจ desktop/mobile, Light/Dark, search/filter/reset, lesson links และ unknown path บน public URL
- ไม่อ้างว่า static site เป็น LMS, backend หรือเว็บไซต์มหาวิทยาลัยอย่างเป็นทางการ
- screenshots มาจาก terminal/browser จริงและไม่เปิด credential
- worksheet แยก Verified, Finding และ Not tested พร้อม recovery source

เมื่อครบรายการนี้ ผู้เรียนมีทั้ง static deployment และหลักฐานว่ารู้ว่า deploy อะไร ไป account/target ใด ตรวจอะไรแล้ว และจะคืนรุ่นเดิมอย่างไร โดยไม่ดึง D1/API ของ course repository เข้ามาในงานที่ไม่ต้องใช้


## ผลที่รันจริงและไฟล์หลักฐาน

[เปิดเว็บที่deployจริง](https://psu-course-website-demo.rawinnipa-soft.workers.dev/) · [ดูprompt/transcript/log/ภาพแต่ละรอบ](/labs/course-website-deploy/) · [ดาวน์โหลดPDFรวมขั้นสร้างเว็บและdeploy พร้อมsource/ZIPstatic](/resources/)

รอบที่บันทึกใช้ Codex CLI 0.154.0 และ Wrangler 4.130.0 วันที่ 11 กันยายน 2026 ชื่อ Worker คือ `psu-course-website-demo` และversionที่CLIretryรายงานคือ `21ef7990-e844-4e57-93ab-53224712b04c` ตรวจpublic assets9 รายการ (รวมlicense 2 ไฟล์), 4 ขนาดหน้าจอ/ธีม และไม่พบfailures การตรวจนี้ไม่ใช่การรับรองทุกbrowserหรือระบบมหาวิทยาลัย ภาพTerminalเป็นread-only replayจากเหตุการณ์จริง ส่วนภาพbrowserเปิดจากpublicURLที่CLIรายงาน

หลังรอบ 08 ผู้รวมงานเพิ่มguard ป้องกันชื่อ Worker หลักและชื่อที่ลงท้ายด้วยขีดจากข้อพบของreviewer แล้วรันregression checks 3 รายการและdry-runซ้ำ ผลstaticpackageของชื่อที่ถูกต้องไม่เปลี่ยน ส่วนCodex 09 ที่ล้มเหลวกับparent retryเก็บคนละlogอย่างชัดเจน
