บทที่ 4 Build และ dependency: npm ci, lock file และ SemVer
แผนบริหารการสอนประจำบทที่ 4
หัวข้อเนื้อหา
- 4.1 build lifecycle: จากซอร์สโค้ดสู่สิ่งที่นำไปรันได้
- 4.2 dependency และ Dependency Hell
- 4.3 lock file และการติดตั้งแบบทำซ้ำได้ด้วย
npm ci - 4.4 npm scripts: คำสั่งเดียวสำหรับ build และ test
- 4.5 Semantic Versioning (SemVer) และช่วงเวอร์ชันของ dependency
- 4.6 immutable artifact และการแยก build, release, run
- 4.7 ทำซ้ำได้ต้องควบคุมมากกว่าไฟล์ซอร์ส
- 4.8 แยกการตรวจไวยากรณ์ การทดสอบ และการสร้างผลผลิต
- 4.9 วิเคราะห์การเปลี่ยนเวอร์ชันจากสัญญาที่ผู้ใช้พึ่งพา
- 4.10 build ครั้งเดียวและตรวจย้อนถึงต้นทาง
- ปฏิบัติการที่ 4 Build และ dependency ของแอปทีม (Lab A–D)
วัตถุประสงค์เชิงพฤติกรรม
เมื่อศึกษาบทนี้และทำปฏิบัติการครบถ้วนแล้ว ผู้เรียนสามารถปฏิบัติได้ตามผลลัพธ์การเรียนรู้ต่อไปนี้ โดยแต่ละข้อมีงานที่ใช้วัดผลกำกับไว้
| รหัส | ผลลัพธ์การเรียนรู้ | วัดจาก | CLO ของรายวิชา |
|---|---|---|---|
| 4.1 | ติดตั้ง dependency ด้วย npm ci จาก repository ที่ clone ใหม่ และระบุความต่างจาก npm install จากเอกสาร npm ทางการ | งานข้อ 1 และ 2 | CLO3 |
| 4.2 | กำหนดเลขเวอร์ชัน SemVer สำหรับการเปลี่ยนแปลงที่กำหนด 3 กรณี | งานข้อ 3 | CLO3 |
CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):
- CLO3 ใช้เครื่องมือดิจิทัลอย่างเหมาะสมเพื่อพัฒนาระบบ และสามารถเรียนรู้และปรับตัวด้วยตนเองจากแหล่งข้อมูลที่เชื่อถือได้
บทนี้ฝึกใช้ npm ในฐานะเครื่องมือ build ร่วมกับการค้นคำตอบจากเอกสารทางการ จึงจับคู่กับ CLO3 ทั้งสองส่วน บทนี้ยังไม่จับคู่กับ CLO2 เพราะยังไม่มี workflow อัตโนมัติ แต่คำสั่ง npm ci และ npm test ที่กำหนดค่าในบทนี้จะกลายเป็นขั้นตอนของ CI ในบทที่ 5
วิธีสอนและกิจกรรมการเรียนการสอน
- บรรยายแนวคิดตามหัวข้อ 4.1–4.6 ประกอบตัวอย่างที่ซับซ้อนขึ้นตามลำดับ และใช้คำถามตรวจความเข้าใจท้ายหัวข้อเป็นกิจกรรมอภิปรายเป็นคู่
- สาธิตการ clone repository ใหม่แล้วสั่ง
npm ciทั้งก่อนและหลังมีpackage-lock.jsonเพื่อให้เห็นข้อความผิดพลาดจริง - ฝึกตัดสินเลขเวอร์ชัน SemVer จากรายการเปลี่ยนแปลงตัวอย่างเป็นกลุ่ม
- ปฏิบัติการ Lab A–D บน repository ทีม โดยสมาชิกแต่ละคนรับผิดชอบ pull request ของตนเองคนละหนึ่งรายการ
- มอบหมายแบบฝึกหัดท้ายบท งานที่ต้องส่ง Learning Log ครั้งที่ 4 และการศึกษาค้นคว้าเพิ่มเติม
สื่อการเรียนการสอน
- เอกสารประกอบการสอนบทที่ 4 (บทความนี้) และหน้ารวมรายวิชา 10306343
- เครื่อง Ubuntu 24.04 ที่ติดตั้ง Git, Node.js 22 และ npm แล้ว
- repository ทีม
devops-appบน GitHub ที่ตั้ง branch protection แล้วจากบทที่ 3 - เอกสารทางการ npm Docs หัวข้อ npm ci และ npm install, ข้อกำหนด Semantic Versioning 2.0.0 และ The Twelve-Factor App หัวข้อ Build, release, run
การวัดผลและการประเมินผล
กำหนดส่ง: ก่อนเริ่มเรียนบทที่ 6 เพื่อเว้นระยะหนึ่งบท
| ข้อ | งาน | หลักฐาน | ผลลัพธ์ | CLO |
|---|---|---|---|---|
| 1 | M3 build จาก clone ใหม่ | ลิงก์ pull request ของตนเองที่ merge แล้ว (Lab A, B หรือ D) และผลรัน npm ci && npm test ของตนเองบนโฟลเดอร์ที่ clone ใหม่จาก main หลัง tag v0.2.0 | 4.1 | CLO3 |
| 2 | บันทึกความต่างระหว่าง npm ci กับ npm install | ข้อความรายบุคคลไม่เกินครึ่งหน้า ระบุความต่างอย่างน้อย 2 ข้อ พร้อมลิงก์เอกสาร npm ทางการที่ใช้ | 4.1 | CLO3 |
| 3 | ใบงาน SemVer | คำตอบรายบุคคล 3 กรณีในหัวข้อแบบฝึกหัดท้ายบทกลุ่ม ข (ข1–ข3) พร้อมเหตุผลข้อละ 1–2 บรรทัด | 4.2 | CLO3 |
| 4 | Learning Log ครั้งที่ 4 | ตอบ 4 หัวข้อ: สิ่งที่เรียนในบทนี้ · ข้อความ error ใดที่พบและแก้อย่างไร · แหล่งที่ใช้ค้นหาคำตอบ (ระบุลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง) · สิ่งที่ยังไม่เข้าใจเรื่อง dependency หรือเวอร์ชัน | — | บันทึกสะท้อนการเรียนรู้ ไม่มีหมวดคะแนน portfolio แยก |
เกณฑ์ให้คะแนน
| ข้อ | ผ่าน | ยังไม่ผ่าน |
|---|---|---|
| 1 | pull request ของตนผ่าน review และ merge ผ่าน pull request · ผลรันแสดง npm ci สำเร็จและ npm test ผ่านครบทุก test บนโฟลเดอร์ที่ clone ใหม่ และ package.json เป็นเวอร์ชัน 0.2.0 | ไม่มี pull request ของตนเอง หรือผลรันมาจากโฟลเดอร์เดิมที่มี node_modules อยู่แล้ว |
| 2 | ระบุความต่างถูกต้องอย่างน้อย 2 ข้อ และอ้างลิงก์เอกสาร npm ทางการ | ระบุถูกน้อยกว่า 2 ข้อ หรือไม่มีแหล่งอ้างอิง |
| 3 | ตอบถูกอย่างน้อย 2 จาก 3 กรณี พร้อมเหตุผล | ตอบถูกน้อยกว่า 2 กรณี |
| 4 | ตอบครบ 4 หัวข้อ และมีลิงก์เอกสารทางการ | ขาดหัวข้อ หรือไม่มีแหล่งอ้างอิง |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
การคิดคะแนนฉบับปัจจุบัน
มติผู้สอน 28 ต.ค. 2569: เข้าเรียน 10% · ใบงานบทที่ 1–13 รวม 40% · FP 20% · FW 20% · โครงงาน 10% ส่วนบทที่ 14–15 ฝึกเพื่อรับข้อเสนอแนะ ไม่นับคะแนน
ใบงาน C04-WS01 มีคะแนนดิบเต็ม 20 คะแนน แยกตามผลลัพธ์ด้านล่าง ระบบนำไปรวมในหมวดใบงาน 40% โดยไม่เพิ่มคะแนน milestone, portfolio หรือจุดตรวจเป็นหมวดแยก
| ผลลัพธ์ | หลักฐานตามงานส่ง | คะแนนดิบเต็ม |
|---|---|---|
| LLO4.1 | งานข้อ 1 และ 2 | 10 |
| LLO4.2 | งานข้อ 3 | 10 |
ประเมินงานแต่ละข้อจากหลักฐานของข้อนั้นโดยตรง ไม่ตัดสิทธิ์รับคะแนนของอีกข้อเพราะ milestone หรือจุดตรวจอื่นยังไม่ผ่าน งานที่ยังทำไม่ได้ให้บันทึกปัญหาและหลักฐานที่มีตามจริง
ใช้เกณฑ์ความถูกต้อง เหตุผล และหลักฐานที่ระบุในงานแต่ละข้อประกอบการประเมินราย LLO งานเตรียมพื้นและ Learning Log ที่ไม่มีรหัส LLO ใช้ให้ข้อเสนอแนะ ไม่เพิ่มคะแนนดิบให้ใบงาน
บทนำ
บทนี้เปลี่ยนมุมมองต่อแอปของทีมจาก "โค้ดที่รันได้บนเครื่องของผู้พัฒนา" เป็น "สิ่งที่สร้างซ้ำได้เหมือนเดิมบนเครื่องใดก็ได้" ซึ่งเป็นเงื่อนไขพื้นฐานของระบบอัตโนมัติทุกขั้นในบทต่อไป เนื้อหาเริ่มจากแนวคิด build lifecycle ที่แบ่งการสร้างซอฟต์แวร์เป็นขั้นตอนที่ชัดเจน ปัญหาของ dependency ที่เปลี่ยนรุ่นได้เองเมื่อเวลาผ่านไป การใช้ lock file และคำสั่ง npm ci เพื่อให้ติดตั้งได้ผลเหมือนเดิมทุกครั้ง การรวมคำสั่ง build และ test ไว้ใน npm scripts การกำหนดเลขเวอร์ชันตาม Semantic Versioning และแนวคิด immutable artifact ที่แยกขั้น build ออกจากขั้น run
ความสำคัญ: ระบบ CI ในบทที่ 5 รันบนเครื่องใหม่ทุกครั้ง เครื่องนั้นไม่มี node_modules และไม่รู้ว่าผู้พัฒนาเคยติดตั้งอะไรไว้ หากการติดตั้ง dependency ให้ผลต่างกันในแต่ละครั้ง test ที่ผ่านบนเครื่องผู้พัฒนาอาจล้มบน CI หรือกลับกัน การกำหนดคำสั่งติดตั้งที่ทำซ้ำได้และคำสั่ง test เพียงคำสั่งเดียวในบทนี้จึงเป็นสิ่งที่ทำให้ CI ตัดสินผลได้น่าเชื่อถือ เลขเวอร์ชันที่กำหนดในบทนี้ยังใช้ต่อเป็น tag ของ container image ในบทที่ 8
ความเชื่อมโยงกับบทอื่น: บทนี้ใช้ exit status จากบทที่ 1 การทำงานบน branch และ tag จากบทที่ 2 และ GitHub flow พร้อม branch protection จากบทที่ 3 ผลผลิตของบทนี้ถูกใช้ต่อตามตารางต่อไปนี้
| สิ่งที่ได้จากบทนี้ | ใช้ต่อในบท |
|---|---|
package-lock.json ใน repository ทีม | 5 (ขั้น npm ci ใน workflow) และ 6 (ติดตั้ง dependency ใน Dockerfile) |
npm script test | 5 (ขั้น npm test ใน workflow) |
เวอร์ชัน 0.2.0 และ tag v0.2.0 | 8 (tag ของ container image) และ 12 (rollback ไปเวอร์ชันก่อนหน้า) |
| แนวคิด immutable artifact | 6 (image) และ 8 (registry) |
ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์
ความรู้พื้นฐาน: คำสั่ง Git และ GitHub flow จากบทที่ 2–3 JavaScript จากรายวิชา 10306215 และแนวคิด unit test จากรายวิชา 10306103 หรือ 10306222 บทนี้ไม่ต้องเขียนโค้ดแอปเพิ่ม แต่ต้องอ่านโค้ด test ที่มีอยู่ออก
เครื่องมือ: เครื่อง Ubuntu ที่ติดตั้ง Git, Node.js และ npm แล้ว โฟลเดอร์ ~/devops-app ที่ clone จาก repository ทีมในบทที่ 3 และโปรแกรมแก้ไขข้อความ เช่น nano
รุ่นซอฟต์แวร์ที่ใช้ทดสอบ:
| ซอฟต์แวร์ | รุ่นที่ทดสอบ |
|---|---|
| Node.js | 22.22.0 |
| npm | 10.9.4 |
| Git | 2.43.0 บน Ubuntu 24.04.5 (ทดสอบเมื่อ 19 ต.ค. 2569 โดยใช้ bare repository ในเครื่องแทน GitHub) |
| express (dependency ของแอป) | 5.3.0 |
| GitHub (เว็บ) | ยังไม่ได้ทดสอบขั้นตอนบนหน้าเว็บในรอบนี้ |
ผลลัพธ์ในบทนี้ได้จากการรันจริงบนรุ่นข้างต้น ข้อความของ npm รุ่นอื่นอาจต่างไปเล็กน้อย จำนวน package ที่ติดตั้ง เวลาที่ใช้ และบรรทัด duration_ms จะต่างกันในแต่ละเครื่อง วิธีติดตั้ง Node.js บน Ubuntu ให้ใช้วิธีที่ผู้สอนกำหนดสำหรับห้องปฏิบัติการ เพราะรุ่นที่มากับ apt ของ Ubuntu อาจเก่ากว่ารุ่นที่ทดสอบ
ค่าใช้จ่ายและข้อมูลลับ: npm registry สาธารณะไม่มีค่าใช้จ่ายสำหรับการติดตั้ง package และบทนี้ไม่ต้องสมัครบัญชี npm ประเด็นที่ต้องระวังคือ node_modules ต้องไม่ถูก commit ซึ่ง .gitignore ของแอปตั้งต้นกันไว้แล้ว และหากสภาพแวดล้อมใดต้องใช้ token สำหรับ registry ส่วนตัว token นั้นต้องไม่อยู่ในไฟล์ใดของ repository
4.1 build lifecycle: จากซอร์สโค้ดสู่สิ่งที่นำไปรันได้
build คือกระบวนการแปลงซอร์สโค้ดใน repository ให้เป็นสิ่งที่นำไปรันหรือส่งมอบได้ build lifecycle คือการแบ่งกระบวนการนั้นเป็นขั้นตอนที่มีชื่อและลำดับแน่นอน เพื่อให้ทุกคนในทีมและระบบอัตโนมัติสั่ง build ได้ด้วยวิธีเดียวกัน Apache Maven ซึ่งเป็นเครื่องมือ build ของภาษา Java กำหนด default lifecycle เป็นลำดับขั้น validate → compile → test → package → verify → install → deploy เมื่อสั่งขั้นใด Maven จะทำทุกขั้นก่อนหน้าให้ก่อนเสมอ (Apache Maven: Introduction to the Build Lifecycle)
แอปของรายวิชาเขียนด้วย JavaScript ซึ่งรันได้โดยไม่ต้อง compile ขั้นตอน build จึงสั้นกว่า แต่ยังมีขั้นที่ต้องทำให้เป็นแบบแผนเดียวกัน
| ขั้น (แนวคิด) | Maven | npm (แอปของรายวิชา) |
|---|---|---|
| เตรียม dependency | ดาวน์โหลดตามที่ระบุใน pom.xml ระหว่าง build | npm ci ตาม package-lock.json |
| compile | compile | ไม่มี (JavaScript รันได้ทันที) |
| test | test | npm test |
| package | package สร้างไฟล์ .jar | ยังไม่มีในบทนี้ บทที่ 6 ใช้ container image เป็นสิ่งที่ส่งมอบ |
| ส่งขึ้นคลัง | deploy ส่งไปยังคลัง artifact | บทที่ 8 ส่ง image ขึ้น ghcr.io |
ระดับที่ 1: สิ่งที่ build lifecycle ให้คือ "คำสั่งเดียวที่ทุกคนใช้เหมือนกัน" สมาชิกที่เพิ่งเข้าทีมไม่ต้องถามว่าต้องติดตั้งอะไรหรือรัน test อย่างไร เพียงสั่ง npm ci แล้วตามด้วย npm test
ระดับที่ 2: ลำดับของขั้นมีความหมาย การ package โดยไม่ผ่านขั้น test เท่ากับส่งมอบสิ่งที่ยังไม่ได้ตรวจ Maven จึงบังคับลำดับไว้ในตัวเครื่องมือ ส่วนรายวิชานี้บังคับลำดับเดียวกันด้วย workflow ของ CI ในบทที่ 5 ซึ่งหยุดทันทีเมื่อขั้นใดจบด้วย exit status ที่ไม่ใช่ 0
ระดับที่ 3: สิ่งที่ได้จากขั้น package เรียกว่า artifact เช่น ไฟล์ .jar หรือ container image แผนเดิมของรายวิชากล่าวถึง Nexus ซึ่งเป็นคลังเก็บ artifact รายวิชานี้ใช้ GitHub Container registry (ghcr.io) ทำหน้าที่นั้นแทนในบทที่ 8 ส่วน Maven และ Nexus เป็นหัวข้อค้นคว้าเพิ่มเติมในภาคผนวก
คำถามตรวจความเข้าใจ 4.1: เหตุใดแอป JavaScript ของรายวิชาจึงไม่มีขั้น compile แต่ยังต้องมีขั้น "เตรียม dependency" และขั้น test ก่อนส่งมอบ
4.2 dependency และ Dependency Hell
dependency คือ package จากภายนอกที่แอปเรียกใช้ แอปของรายวิชาเรียกใช้ Express ซึ่งเป็น framework สำหรับเว็บ ไฟล์ package.json ระบุ dependency ไว้ดังนี้
"dependencies": {
"express": "^5.3.0"
}Express เองก็มี dependency ของตนเอง และ dependency เหล่านั้นก็มี dependency ต่อไปอีก ผลการทดสอบกับแอปของรายวิชาพบว่าการระบุ dependency เพียงตัวเดียวทำให้ npm ติดตั้ง package รวม 65 ตัว (added 65 packages) dependency ที่แอประบุเองเรียกว่า direct dependency ส่วนที่ติดมาเรียกว่า transitive dependency
ข้อกำหนด Semantic Versioning อธิบายปัญหาที่เกิดเมื่อระบบมี dependency จำนวนมากว่า "dependency hell" กล่าวคือ ถ้ากำหนดเวอร์ชันของ dependency แน่นเกินไป จะปรับรุ่นของ package ใดไม่ได้หากไม่ปรับ package อื่นตามทั้งหมด แต่ถ้ากำหนดหลวมเกินไป dependency อาจเปลี่ยนเป็นรุ่นที่ไม่เข้ากันได้โดยไม่รู้ตัว (Semantic Versioning 2.0.0)
ระดับที่ 1: สัญลักษณ์ ^ หน้าเลขเวอร์ชันใน package.json หมายถึง "รับรุ่นที่ใหม่กว่าได้ ตราบที่ยังเข้ากันได้" ดังนั้น npm install ในวันนี้กับอีกสามเดือนข้างหน้าอาจได้ Express คนละรุ่น แม้ package.json ไม่เปลี่ยนแม้แต่ตัวอักษรเดียว
ระดับที่ 2: ปัญหาเดียวกันเกิดกับ transitive dependency ทั้ง 64 ตัวที่แอปไม่ได้ระบุเอง ผู้พัฒนาสองคนที่ติดตั้งคนละวันจึงอาจมี node_modules ไม่เหมือนกัน และเป็นที่มาของปัญหา "test ผ่านบนเครื่องหนึ่ง แต่ล้มบนอีกเครื่อง"
ระดับที่ 3: ทางแก้ต้องตอบสองความต้องการพร้อมกัน คือ (1) ต้องรู้ว่าเวอร์ชันใดเข้ากันได้ ซึ่งเป็นหน้าที่ของกติกา SemVer ในหัวข้อ 4.5 และ (2) ต้องติดตั้งรุ่นเดิมทุกครั้งจนกว่าทีมจะตัดสินใจปรับ ซึ่งเป็นหน้าที่ของ lock file ในหัวข้อ 4.3
คำถามตรวจความเข้าใจ 4.2: package.json ของแอประบุ dependency เพียง 1 ตัว เหตุใดโฟลเดอร์ node_modules จึงมี package หลายสิบตัว และ package กลุ่มใดที่ผู้พัฒนามักลืมว่าอาจเปลี่ยนรุ่นได้
4.3 lock file และการติดตั้งแบบทำซ้ำได้ด้วย npm ci
lock file คือไฟล์ที่บันทึกเวอร์ชันที่ติดตั้งจริงของ dependency ทุกตัว ทั้ง direct และ transitive สำหรับ npm คือไฟล์ package-lock.json ซึ่ง npm สร้างหรือปรับให้เมื่อสั่ง npm install ตัวอย่างบางส่วนของ lock file ที่สร้างจากแอปของรายวิชา
"node_modules/express": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz",
"integrity": "sha512-qCqy1xSo...",แต่ละรายการบันทึกเวอร์ชันที่แน่นอน (version) แหล่งที่ดาวน์โหลด (resolved) และค่าตรวจสอบความถูกต้องของไฟล์ (integrity) เมื่อทีม commit lock file เข้า repository ทุกคนจึงอ้างอิงชุด dependency ชุดเดียวกัน
คำสั่ง npm ci ออกแบบมาสำหรับการติดตั้งจาก lock file ในสภาพแวดล้อมอัตโนมัติ เอกสาร npm ระบุพฤติกรรมของคำสั่งนี้ไว้ดังนี้ (npm Docs: npm ci)
| ประเด็น | npm install | npm ci |
|---|---|---|
| ต้องมี lock file | ไม่ต้อง ถ้าไม่มีจะสร้างให้ | ต้องมี package-lock.json หรือ npm-shrinkwrap.json |
lock file ไม่ตรงกับ package.json | ปรับ lock file ให้ตรง | หยุดพร้อม error |
node_modules เดิม | ติดตั้งเพิ่มหรือปรับจากของเดิม | ลบทิ้งก่อนติดตั้งใหม่ทั้งหมด |
เขียนไฟล์ package.json หรือ lock file | อาจเขียน | ไม่เขียนเลย |
| ใช้เมื่อ | เพิ่มหรือปรับ dependency ระหว่างพัฒนา | ติดตั้งบนเครื่องใหม่ บน CI และใน Dockerfile |
ระดับที่ 1: ผลทดสอบเมื่อสั่ง npm ci บน clone ใหม่ของแอปตั้งต้นซึ่งยังไม่มี lock file
npm error code EUSAGE
npm error
npm error The `npm ci` command can only install with an existing package-lock.json or
npm error npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or
npm error later to generate a package-lock.json file, then try again.ระดับที่ 2: ผลทดสอบเมื่อมีผู้เพิ่ม dependency ใน package.json ด้วยมือโดยไม่สั่ง npm install เพื่อปรับ lock file (ตัวอย่างนี้เพิ่ม dayjs)
npm error code EUSAGE
npm error
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error
npm error Missing: [email protected] from lock fileข้อความนี้แสดงว่า npm ci ปฏิเสธที่จะเดาแทนทีม ความไม่ตรงกันจึงถูกพบทันทีแทนที่จะกลายเป็นความต่างที่ซ่อนอยู่
ระดับที่ 3: การทดสอบสร้างไฟล์ทดลองไว้ใน node_modules แล้วสั่ง npm ci อีกครั้ง พบว่าไฟล์นั้นหายไป ยืนยันว่า npm ci ลบ node_modules เดิมทั้งหมดก่อนติดตั้ง สิ่งที่อยู่ใน node_modules หลัง npm ci จึงมาจาก lock file เท่านั้น พฤติกรรมข้อนี้ทำให้ผลการติดตั้งไม่ขึ้นกับประวัติของเครื่อง
lock file ไม่ได้ทำให้ dependency หยุดอยู่กับที่ตลอดไป เมื่อทีมต้องการปรับรุ่น ผู้รับผิดชอบสั่ง npm install หรือ npm update บน branch ของตน ตรวจว่า test ยังผ่าน แล้วส่ง lock file ที่เปลี่ยนผ่าน pull request ให้สมาชิกตรวจทานเหมือนการเปลี่ยนโค้ด
คำถามตรวจความเข้าใจ 4.3: สมาชิกคนหนึ่งเสนอให้ใส่ package-lock.json ใน .gitignore เพราะไฟล์ยาวหลายร้อยบรรทัดและทำให้ pull request อ่านยาก อธิบายผลเสียของข้อเสนอนี้ต่อ CI ในบทที่ 5
4.4 npm scripts: คำสั่งเดียวสำหรับ build และ test
ส่วน scripts ใน package.json เก็บคำสั่งที่ใช้บ่อยของโครงการไว้ภายใต้ชื่อสั้น ๆ แอปตั้งต้นมี script เดียวคือ start คำสั่ง npm run (ไม่ระบุชื่อ) แสดงรายการ script ทั้งหมดที่มี
Lifecycle scripts included in [email protected]:
start
node src/server.js| คำสั่ง | ผล |
|---|---|
npm run | แสดงรายการ script |
npm start | รัน script start |
npm test | รัน script test |
npm run ชื่อscript | รัน script ชื่อใดก็ได้ที่กำหนดไว้ |
ระดับที่ 1: แอปตั้งต้นยังไม่มี script test ผลทดสอบเมื่อสั่ง npm test
npm error Missing script: "test"
npm error
npm error To see a list of scripts, run:
npm error npm runคำสั่งนี้จบด้วย exit status 1 ซึ่งระบบ CI จะตีความว่าล้ม
ระดับที่ 2: test ของแอปเขียนด้วยโมดูล node:test ที่มากับ Node.js และวางไว้ในโฟลเดอร์ test/ คำสั่ง node --test ค้นหาและรันไฟล์ test ให้เอง script ที่ต้องเพิ่มจึงมีเพียงบรรทัดเดียว
"scripts": {
"start": "node src/server.js",
"test": "node --test"
}ระดับที่ 3: ประโยชน์ของการผ่าน npm script แทนการพิมพ์ node --test ตรง ๆ คือการแยก "เจตนา" ออกจาก "วิธีทำ" ระบบ CI และสมาชิกทุกคนสั่ง npm test เหมือนเดิมเสมอ หากวันหนึ่งทีมเปลี่ยนเครื่องมือ test ก็แก้เพียงบรรทัดเดียวใน package.json โดยไม่ต้องแก้ workflow หรือเอกสารของทีม
คำถามตรวจความเข้าใจ 4.4: ระบบ CI สั่ง npm test บน repository ที่ยังไม่มี script test อธิบายว่า CI จะรายงานผลเป็นผ่านหรือล้ม และเชื่อมโยงกับเรื่อง exit status ในบทที่ 1
4.5 Semantic Versioning (SemVer) และช่วงเวอร์ชันของ dependency
Semantic Versioning 2.0.0 กำหนดเลขเวอร์ชันเป็นสามส่วน MAJOR.MINOR.PATCH และกติกาการเพิ่มเลขดังนี้ (Semantic Versioning 2.0.0)
| ส่วน | เพิ่มเมื่อ | ตัวอย่างจาก 1.4.2 |
|---|---|---|
| MAJOR | เปลี่ยน API แบบไม่เข้ากันกับของเดิม (incompatible API changes) | 2.0.0 |
| MINOR | เพิ่มความสามารถแบบยังเข้ากันกับของเดิม (backward compatible) | 1.5.0 |
| PATCH | แก้ bug แบบยังเข้ากันกับของเดิม | 1.4.3 |
เมื่อเพิ่มส่วนใด ส่วนที่อยู่ทางขวาจะกลับเป็น 0 ข้อกำหนดยังระบุว่า MAJOR เป็น 0 (0.y.z) ใช้สำหรับช่วงพัฒนาเริ่มต้นซึ่งทุกอย่างเปลี่ยนได้ตลอดเวลา และเวอร์ชัน 1.0.0 คือจุดที่กำหนด public API ของซอฟต์แวร์ แอปของรายวิชาจึงเริ่มที่ 0.1.0 และจะยังไม่ถึง 1.0.0 ในภาคการศึกษานี้
ระดับที่ 1 การตัดสินเลขเวอร์ชัน: สิ่งที่ใช้ตัดสินคือผลต่อผู้ใช้ API ไม่ใช่ปริมาณโค้ดที่แก้ เช่น แอปเวอร์ชัน 0.2.0 เพิ่ม endpoint ใหม่ /api/stats โดย endpoint เดิมทำงานเหมือนเดิม คือการเพิ่มความสามารถแบบเข้ากันได้ เวอร์ชันถัดไปคือ 0.3.0
ระดับที่ 2 ช่วงเวอร์ชันใน package.json: npm ใช้สัญลักษณ์หน้าเลขเวอร์ชันเพื่อกำหนดช่วงที่ยอมรับ ผลต่อไปนี้ทดสอบด้วยไลบรารี semver ที่มากับ npm 10.9.4
| ที่ระบุ | ช่วงที่ยอมรับ | 5.3.9 | 5.9.0 | 6.0.0 |
|---|---|---|---|---|
5.3.0 | 5.3.0 เท่านั้น | ไม่รับ | ไม่รับ | ไม่รับ |
~5.3.0 | ≥ 5.3.0 และ < 5.4.0 | รับ | ไม่รับ | ไม่รับ |
^5.3.0 | ≥ 5.3.0 และ < 6.0.0 | รับ | รับ | ไม่รับ |
^ รับ MINOR และ PATCH ที่ใหม่กว่า เพราะตามกติกา SemVer สองส่วนนี้ต้องเข้ากันได้กับของเดิม แต่ไม่รับ MAJOR ใหม่ซึ่งอาจไม่เข้ากัน ส่วน ~ รับเฉพาะ PATCH
ระดับที่ 3 ข้อยกเว้นของเวอร์ชัน 0: เมื่อ MAJOR เป็น 0 ช่วง ^0.2.3 ยอมรับเพียง ≥ 0.2.3 และ < 0.3.0 (ทดสอบแล้ว) เพราะในช่วงพัฒนาเริ่มต้น การเพิ่ม MINOR อาจเปลี่ยน API ได้ ข้อนี้สอดคล้องกับข้อกำหนดที่ว่าเวอร์ชัน 0.y.z เปลี่ยนได้ตลอด อีกประเด็นหนึ่งคือกติกา SemVer เป็นข้อตกลงที่ผู้พัฒนา package ต้องปฏิบัติเอง หากผู้พัฒนาออก MINOR ที่ทำให้ API เดิมพัง ช่วง ^ ก็จะรับรุ่นนั้นเข้ามา lock file และ test จึงยังจำเป็นแม้ใช้ SemVer
คำถามตรวจความเข้าใจ 4.5: แอปเวอร์ชัน 0.2.0 แก้ bug ที่ /api/items/abc ตอบ HTTP 500 ให้ตอบ 404 แทน โดยไม่เปลี่ยน endpoint อื่น เวอร์ชันถัดไปคืออะไร และถ้า package.json ของอีกโครงการระบุ "devops-app": "^0.2.0" โครงการนั้นจะรับเวอร์ชันใหม่นี้หรือไม่
4.6 immutable artifact และการแยก build, release, run
The Twelve-Factor App แบ่งการนำ codebase ไปใช้งานเป็นสามขั้นที่แยกจากกันอย่างเคร่งครัด (The Twelve-Factor App: V. Build, release, run)
| ขั้น | ความหมาย | ในรายวิชานี้ |
|---|---|---|
| build | แปลง codebase เป็นสิ่งที่รันได้ (build) โดยรวม dependency เข้าไปด้วย | npm ci และในบทที่ 6 คือ docker build |
| release | นำ build มารวมกับค่าตั้ง (config) ของสภาพแวดล้อมที่จะใช้ | บทที่ 8–9 นำ image มารวมกับตัวแปรสภาพแวดล้อมของเครื่องปลายทาง |
| run | รันแอปจาก release ที่เลือก | บทที่ 8 รัน container บนเครื่องปลายทาง |
แหล่งเดียวกันระบุว่า release แต่ละรุ่นต้องมี ID เฉพาะและแก้ไขไม่ได้ การเปลี่ยนแปลงใดต้องสร้าง release ใหม่ แนวคิดนี้เรียกว่า immutable artifact คือสิ่งที่ build แล้วจะไม่ถูกแก้ภายหลัง
ระดับที่ 1: เลขเวอร์ชัน 0.2.0 และ tag v0.2.0 ที่สร้างในปฏิบัติการของบทนี้คือ ID ของรุ่น ทีมสามารถชี้ได้ว่า "เครื่องปลายทางกำลังรันรุ่นใด"
ระดับที่ 2: การแก้โค้ดบนเครื่องที่กำลังให้บริการโดยตรงขัดกับหลักนี้ เพราะทำให้สิ่งที่รันอยู่ไม่ตรงกับรุ่นใดใน repository และไม่มีใครสร้างซ้ำได้
ระดับที่ 3: เมื่อทุก release แก้ไขไม่ได้และมี ID การย้อนกลับ (rollback) จึงทำได้ด้วยการรัน release ก่อนหน้าซ้ำ แทนการแก้ของที่พังให้กลับเป็นเหมือนเดิม บทที่ 12 จะใช้หลักนี้ rollback ด้วย image tag ของเวอร์ชันก่อนหน้า
คำถามตรวจความเข้าใจ 4.6: สมาชิกคนหนึ่งพบคำผิดในข้อความของแอปที่ deploy แล้ว จึงเสนอให้เข้าไปแก้ไฟล์บนเครื่องปลายทางโดยตรงเพราะเร็วกว่า อธิบายด้วยหลัก build, release, run ว่าวิธีนี้มีปัญหาอย่างไร และควรทำอย่างไรแทน
4.7 ทำซ้ำได้ต้องควบคุมมากกว่าไฟล์ซอร์ส
การได้ซอร์สโค้ดเดียวกันยังไม่ยืนยันว่าจะได้ผล build เดียวกัน สภาพแวดล้อมยังประกอบด้วยรุ่น runtime รุ่น package manager ระบบปฏิบัติการ สถาปัตยกรรมเครื่อง ค่าตั้ง และทรัพยากรภายนอก ตัวอย่างต่อไปนี้เป็นสถานการณ์สมมติสำหรับวิเคราะห์ devops-app ไม่ใช่ผล benchmark ของเครื่องจริง
สมมติว่าเครื่อง A ใช้ Node.js รุ่นหนึ่งและติดตั้ง dependency ไว้เมื่อเดือนก่อน ส่วนเครื่อง B clone โค้ดวันนี้แล้วติดตั้งใหม่ ถ้าไฟล์ package.json ระบุช่วงเวอร์ชันและไม่มี lock file ทั้งสองเครื่องอาจได้ dependency ต่างกัน การที่ A ทดสอบผ่านจึงยังไม่พิสูจน์ว่า B จะผ่าน วิธีที่เหมาะสมคือตรวจ runtime และ package manager ร่วมกับ commit ของ manifest และ lock file แล้วทดลองจากสภาพเริ่มต้นที่ไม่มี dependency ค้าง
npm ci ออกแบบสำหรับติดตั้งทั้งโครงการตาม lock file โดยไม่ปรับ package.json หรือ lock file ถ้าสองไฟล์ระบุ dependency ไม่สอดคล้องกันจะหยุดด้วยข้อผิดพลาด แทนการแก้ lock file ให้เอง อีกทั้งจะนำ node_modules เดิมออกก่อนติดตั้ง จึงช่วยลดผลของแพ็กเกจค้าง แต่ไม่ได้ทำให้ระบบปฏิบัติการหรือบริการภายนอกเหมือนกันโดยอัตโนมัติ (npm Docs: npm ci)
ตัวอย่างที่ไม่ถูกต้องคือเมื่อ CI แจ้งว่า manifest กับ lock file ไม่ตรงกันแล้วเปลี่ยนคำสั่งเป็น npm install เพื่อให้ผ่านทันที วิธีนี้เปลี่ยนคำถามจาก “snapshot นี้มี dependency ที่ตกลงกันแล้วหรือไม่” เป็น “วันนี้ตัวแก้ dependency เลือกอะไรได้บ้าง” แนวแก้ควรเกิดบน branch งาน ตรวจว่าการเปลี่ยน dependency เป็นความตั้งใจหรือไม่ สร้าง lock file ให้สอดคล้องในสภาพแวดล้อมที่กำหนด ตรวจ diff และ test แล้วส่งทั้งสองไฟล์ผ่าน review
อีกกรณีหนึ่งคือทีมสร้าง lock file ด้วย flag ที่เปลี่ยนรูปแบบ dependency tree แต่ CI ไม่ใช้ flag เดียวกัน npm ci อาจล้มแม้ไฟล์ครบ เอกสาร npm ระบุให้รักษา flags ที่เกี่ยวข้องให้สอดคล้องกัน เช่น ผ่านค่าตั้งระดับโครงการที่ตรวจสอบได้ การลบ lock file แล้วเริ่มใหม่โดยไม่อ่าน error จะทำให้หลักฐานของปัญหาเดิมหายและอาจเพิ่มการเปลี่ยน dependency ที่ไม่เกี่ยวข้อง
4.8 แยกการตรวจไวยากรณ์ การทดสอบ และการสร้างผลผลิต
คำว่า build ถูกใช้ต่างกันตามภาษาและโครงการ ใน Node.js บางโครงการไม่มีขั้นแปลงซอร์ส ขณะที่บางโครงการต้องแปลง TypeScript หรือรวมไฟล์ การมี script ชื่อ build จึงไม่บอกเองว่าตรวจพฤติกรรมอะไร ต้องเปิดอ่านคำสั่งจริงและระบุผลผลิตที่ได้ การใช้ชื่อขั้นเหมือนกันช่วยให้เรียกง่าย แต่ไม่ทำให้ความหมายเหมือนกันโดยอัตโนมัติ
พิจารณาสามผลตรวจสมมติ: การตรวจไวยากรณ์ผ่าน การทดสอบ /health ล้ม และขั้นสร้างไฟล์แจกจ่ายผ่าน ทั้งสามผลอยู่ร่วมกันได้ เพราะโค้ดที่เขียนถูกไวยากรณ์อาจคืนสถานะผิด และเครื่องมือสร้างผลผลิตอาจไม่เรียก test เลย ข้อสรุป “build ผ่านจึงใช้งานถูกต้อง” จึงไม่สมเหตุผล ต้องเชื่อมผลแต่ละขั้นกับข้อกำหนดที่ตรวจจริง
ตัวอย่าง test ที่อ่อนเกินไปคือเรียกฟังก์ชันแล้วตรวจเพียงว่าไม่ throw ทั้งที่ข้อกำหนดต้องคืน status: ok เมื่อเปลี่ยนโค้ดให้คืนข้อความผิด test ยังผ่าน การปรับที่ถูกต้องคือตรวจค่าที่ผู้ใช้หรือระบบถัดไปพึ่งพา และมีกรณีที่จงใจให้โค้ดผิดเพื่อยืนยันว่า test จับได้ การทดลองดังกล่าวควรทำบน branch ทดลองและคืนค่าก่อนส่งงาน
อีกตัวอย่างคือ script เขียนเป็น node --test; echo done ใน shell ที่ไม่ได้หยุดเมื่อคำสั่งแรกล้ม ขั้นสุดท้ายอาจคืน 0 แล้วกลบผล test การใช้ตัวดำเนินการต่อคำสั่งหรือกำหนดการส่งสถานะอย่างถูกต้องจึงเป็นส่วนหนึ่งของความน่าเชื่อถือของ build ไม่ใช่เพียงรายละเอียดของ shell หลักนี้ต่อจาก exit status ในบทที่ 1 และจะมีผลโดยตรงต่อ CI ในบทที่ 5
การทำตาราง “ข้อกำหนด → คำสั่งตรวจ → ผลที่คาด → ผลผลิต” ช่วยให้ไม่ฝากความหมายทั้งหมดไว้กับชื่อ script เช่น ข้อกำหนด endpoint ตอบถูกตรวจด้วย test ส่วนความพร้อมส่งมอบตรวจจากผลผลิตที่สร้างและวิธีเรียกใช้ หากขั้นใดไม่มีผลที่ใช้ตัดสินได้ ควรระบุข้อจำกัดนั้นแทนการเรียกรวมว่าผ่านทุกอย่าง
4.9 วิเคราะห์การเปลี่ยนเวอร์ชันจากสัญญาที่ผู้ใช้พึ่งพา
การตัดสิน MAJOR, MINOR หรือ PATCH ต้องรู้ก่อนว่าส่วนใดคือ public API ที่ประกาศไว้ ตัวอย่างการเพิ่มฟิลด์แบบไม่ทำให้ผู้ใช้เดิมเสียอาจเป็นการเพิ่มความสามารถที่เข้ากันได้ แต่ถ้า client ใช้ schema แบบปฏิเสธฟิลด์ส่วนเกิน การเพิ่มนั้นอาจมีผลต่างออกไป จึงต้องตรวจสัญญาและผู้ใช้จริง ไม่ตัดสินจากจำนวนบรรทัดที่แก้ ตัวอย่างในส่วนนี้ใช้ระบบรุ่นสมมติที่ประกาศสัญญาชัดเจนแล้ว ส่วนแอปในรายวิชายังคงใช้เลขรุ่นตามแผนเดิม
สมมติว่า API รุ่น 1.2.3 รับ name เป็นข้อความและคืน id เป็นตัวเลข การแก้ bug ที่ทำให้ชื่อมีช่องว่างถูกตัดผิดโดยยังคงสัญญาเดิมเป็นตัวอย่างของ PATCH การเพิ่ม endpoint ใหม่ที่ไม่เปลี่ยน endpoint เดิมเป็นตัวอย่างของ MINOR ส่วนการเปลี่ยน id จากตัวเลขเป็นข้อความโดยไม่มีช่วงรองรับของเดิมเป็นตัวอย่างที่ต้องพิจารณา MAJOR ภายใต้สัญญานี้
ตัวอย่างที่ไม่ถูกต้องคือเพิ่ม MAJOR เพราะแก้ไฟล์มาก หรือเพิ่ม PATCH เพราะแก้เพียงหนึ่งบรรทัด การเปลี่ยนหนึ่งบรรทัดที่ลบฟิลด์ซึ่ง client ต้องใช้มีผลต่อความเข้ากันได้มากกว่าการจัดรูปแบบหลายร้อยบรรทัด เลขเวอร์ชันจึงสื่อสารผลต่อผู้ใช้ ไม่ใช่ระดับความเหนื่อยของผู้พัฒนา
ช่วงเวอร์ชันของ dependency ก็ไม่ใช่หลักฐานว่าทุกเวอร์ชันในช่วงไม่มี bug การอัปเดตที่ตาม SemVer คาดว่าจะเข้ากันได้ยังต้องทดสอบกับพฤติกรรมที่แอปใช้ การคงเวอร์ชันเก่าตลอดไปก็ไม่ใช่คำตอบ เพราะอาจพลาดการแก้ช่องโหว่ กระบวนการที่สมดุลคือเสนอการอัปเดตเป็นการเปลี่ยนที่ตรวจสอบได้ อ่าน release notes ทดสอบ และมีแผนกลับไปใช้ผลผลิตก่อนหน้า
4.10 build ครั้งเดียวและตรวจย้อนถึงต้นทาง
สมมติว่า commit A ผ่าน test แล้วถูก build เป็น artifact X หาก staging ใช้ X แต่ production ดึงซอร์ส A ไป build ใหม่เป็น Y จะยังสรุปไม่ได้ว่าได้สิ่งเดียวกัน เพราะขั้น build อาจอ่าน dependency หรือค่าจากภายนอกที่เปลี่ยนไป หลัก build once จึงให้สร้างผลผลิตที่อ้างอิงได้แล้วเลื่อนผลผลิตนั้นผ่านแต่ละสภาพแวดล้อม พร้อมแยกค่าตั้งที่ควรเปลี่ยนภายหลังอย่างชัดเจน
ตัวอย่างที่ผิดคือ deploy ด้วยการแก้ไฟล์ใน container ของ production แล้วปล่อยให้บริการทำงานต่อ แม้หน้าเว็บกลับมาปกติ ผลที่รันจะไม่ตรงกับ source และ artifact ที่บันทึกไว้ เมื่อสร้าง container ใหม่การแก้เฉพาะหน้าจะหาย วิธีที่ตรวจสอบได้คือบันทึกการแก้ใน Git ทดสอบ สร้าง artifact ใหม่ และ deploy ผ่านขั้นตอนที่ทำซ้ำได้ ส่วนการแก้ฉุกเฉินใด ๆ ต้องตามกลับเป็นการเปลี่ยนที่ทีมตรวจสอบได้
หลักฐาน release ควรเชื่อม commit, รุ่น runtime, lock file, ผล test และตัวระบุ artifact หากส่วนใดขาด เช่น มีเพียงชื่อ latest ที่เปลี่ยนปลายทางได้ จะย้อนตรวจว่าสิ่งที่รันคือสิ่งที่ผ่าน test หรือไม่ได้ยาก ในบทที่ 6 และ 8 จะใช้ image กับ digest ทำหน้าที่นี้ ขณะที่เลขเวอร์ชันยังมีประโยชน์ต่อการสื่อสารกับคน
กรณีอธิบายต่อเนื่อง: clone ใหม่ทำงานต่างจากเครื่องเจ้าของโครงการ
กำหนดข้อมูลสมมติว่าเครื่องเจ้าของมี node_modules ซึ่งประกอบด้วยแพ็กเกจที่เคยติดตั้งด้วยมือ แต่ไฟล์ manifest ปัจจุบันไม่ได้ประกาศแพ็กเกจนั้น เครื่องเจ้าของรันแอปผ่าน ขณะที่สมาชิกใหม่ clone แล้ว npm ci สำเร็จแต่แอปแจ้งหา module ไม่พบ การติดตั้งสำเร็จไม่ขัดกับการรันล้ม เพราะเครื่องมือติดตั้งทำตาม dependency ที่ประกาศ ส่วนโค้ดกำลังเรียกสิ่งที่ไม่ได้ประกาศครบ
คำแนะนำที่ผิดคือคัดลอก node_modules ของเจ้าของให้ทุกคน วิธีนี้อาจบรรเทาอาการในเครื่องที่คล้ายกัน แต่ซ่อนความไม่ครบของ manifest และอาจมีไฟล์ native ที่ผูกกับเครื่องเดิม แนวแก้คือตรวจจุด import ตรวจว่า dependency จำเป็นต่อ runtime หรือเฉพาะการพัฒนา เพิ่มให้ถูกหมวดด้วย package manager แล้ว commit manifest กับ lock file พร้อมกัน
การตรวจหลังแก้ต้องเริ่มจาก clone หรือสภาพที่ไม่มี dependency ค้าง ติดตั้งด้วยคำสั่งที่กำหนดและรันทดสอบทุกขั้นที่สัญญาไว้ หากผ่านเฉพาะเครื่องที่เคยมี package อยู่ยังไม่พิสูจน์ว่าแก้เหตุแล้ว และหากติดตั้ง production dependencies แบบตัด dev dependencies ต้องตรวจ runtime ในรูปแบบนั้นด้วย
พิจารณาความผิดอีกชนิดที่คล้ายกันคือ manifest ครบ แต่ขั้น build ต้องใช้เครื่องมือใน dev dependencies ขณะที่ตั้ง NODE_ENV=production ก่อน npm ci จนเครื่องมือไม่ได้ติดตั้ง การเพิ่มแพ็กเกจ build ไปเป็น runtime dependency เพื่อให้ผ่านทันทีอาจทำให้ขอบเขตผิด ควรแยกขั้น build ที่มีเครื่องมือครบออกจาก runtime ที่ต้องการเฉพาะสิ่งจำเป็น แล้วตรวจแต่ละขั้นตามบทบาท
หลักฐานการแก้จึงต้องมี error แรกที่พบ ความต่างของ manifest กับการเรียกใช้ การเปลี่ยน lock file ที่เกี่ยวข้อง และผลจากสภาพเริ่มต้นใหม่ การอ้างเพียง “ลองติดตั้งใหม่แล้วผ่าน” ยังไม่บอกว่าปัญหาเกิดจากไฟล์ที่ขาด ค่าตั้ง หรือ dependency ที่ค้างอยู่
ปฏิบัติการที่ 4 Build และ dependency ของแอปทีม
ปฏิบัติการนี้ประกอบด้วย Lab A–D ทำบน repository ทีม devops-app จากบทที่ 3 ทีมต้องตกลงกันก่อนเริ่มว่าสมาชิกคนใดรับผิดชอบ pull request ใด เพื่อให้ทุกคนมี pull request ของตนเองหนึ่งรายการใน milestone M3
| pull request | Lab | ผู้รับผิดชอบ | ผู้ตรวจทาน |
|---|---|---|---|
เพิ่ม package-lock.json | A | สมาชิกคนที่ 1 | สมาชิกคนที่ 2 |
เพิ่ม npm script test | B | สมาชิกคนที่ 2 | สมาชิกคนที่ 3 |
ปรับเวอร์ชันเป็น 0.2.0 | D | สมาชิกคนที่ 3 | สมาชิกคนที่ 1 |
ทีมที่มี 2 คน ให้สมาชิกคนที่ 1 รับผิดชอบ pull request ของ Lab D เพิ่ม แต่ละ Lab แบ่งเป็นสองส่วน ส่วน ฝึกตามตัวอย่าง ให้พิมพ์คำสั่งตามและเทียบผลกับตัวอย่าง ส่วน ตัดสินใจเอง (A.4, B.4, C.3 และ D.4) ไม่มีคำสั่งกำกับ ผู้เรียนต้องเลือกคำสั่งหรือตัดสินใจด้วยตนเองจากเอกสารทางการ
ผลลัพธ์ในบทนี้ได้จากการรันจริงบน Node.js 22.22.0 และ npm 10.9.4 โดยใช้ bare repository ในเครื่องแทน GitHub จำนวน package เวลาที่ใช้ (in 556ms) ค่า duration_ms และเลข hash จะต่างกันในแต่ละเครื่อง เส้นทางไฟล์ในข้อความ error จะเป็นโฟลเดอร์ของผู้เรียนเอง
Lab A: clone ใหม่แล้ว npm ci
สถานการณ์: สมาชิกใหม่ของทีม clone repository แล้วต้องการติดตั้ง dependency ด้วยคำสั่งเดียวกับที่ระบบ CI จะใช้ในบทที่ 5
เป้าหมาย: พิสูจน์ว่า repository ยังติดตั้งแบบทำซ้ำได้ไม่ได้ แล้วเพิ่ม lock file ผ่าน pull request จนติดตั้งจาก clone ใหม่ได้ (LLO 4.1)
ฝึกตามตัวอย่าง
A.1 clone ใหม่แล้วลอง npm ci (สมาชิกทุกคน)
ใช้โฟลเดอร์แยก ~/build-check สำหรับตรวจจาก clone ใหม่โดยเฉพาะ เพื่อไม่ให้ปนกับโฟลเดอร์ทำงาน ~/devops-app
mkdir -p ~/build-check
cd ~/build-check
git clone https://github.com/ชื่อบัญชีหัวหน้าทีม/devops-app.git
cd devops-app
npm ci
echo $?npm error code EUSAGE
npm error
npm error The `npm ci` command can only install with an existing package-lock.json or
npm error npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or
npm error later to generate a package-lock.json file, then try again.
...
1ผลนี้เป็นสิ่งที่คาดหวัง เพราะแอปตั้งต้นยังไม่มี package-lock.json บรรทัดสุดท้ายคือ exit status 1 จาก echo $?
A.2 สร้าง lock file และเปิด pull request (สมาชิกคนที่ 1)
ทำในโฟลเดอร์ทำงาน โดยเริ่ม branch จาก main ล่าสุดตามกติกาของบทที่ 3
cd ~/devops-app
git switch main
git pull
git switch -c build/add-lockfile
npm install
git status --shortadded 65 packages, and audited 66 packages in 1s
28 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
?? package-lock.jsongit status แสดงเพียง package-lock.json เป็นไฟล์ใหม่ ส่วน node_modules ไม่ปรากฏเพราะ .gitignore กันไว้ ตรวจบรรทัดของ Express ใน lock file แล้ว commit และ push
grep -A2 '"node_modules/express"' package-lock.json
git add package-lock.json
git commit -m "เพิ่ม package-lock.json เพื่อให้ติดตั้งแบบทำซ้ำได้"
git push -u origin build/add-lockfile "node_modules/express": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz",เปิด pull request จาก build/add-lockfile เข้า main เขียนคำอธิบายว่าเพิ่มไฟล์ใดและเพราะเหตุใด สมาชิกคนที่ 2 ตรวจทานในแท็บ Files changed โดยยืนยันว่ามีเพียง package-lock.json และไม่มี node_modules ติดมา แล้วกด Approve จากนั้นผู้เปิด merge ด้วย Create a merge commit และลบ branch
A.3 ตรวจจาก clone ใหม่อีกครั้ง (สมาชิกทุกคน หลัง A.2 merge แล้ว)
cd ~/build-check
rm -rf devops-app
git clone https://github.com/ชื่อบัญชีหัวหน้าทีม/devops-app.git
cd devops-app
npm ci
npm lsadded 65 packages, and audited 66 packages in 556ms
28 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
[email protected] /home/ชื่อผู้ใช้/build-check/devops-app
`-- [email protected]npm ls แสดงเฉพาะ direct dependency และยืนยันว่าได้ Express รุ่น 5.3.0 ตรงกับ lock file บรรทัด found 0 vulnerabilities มาจากการตรวจช่องโหว่ที่ npm ทำให้อัตโนมัติ ซึ่งจะกล่าวถึงอีกครั้งในบทที่ 10
ตัดสินใจเอง A.4 (LLO 4.1): เอกสาร npm ci ระบุว่า npm ci ลบ node_modules เดิมก่อนติดตั้ง ออกแบบการทดลองสั้น ๆ ในโฟลเดอร์ ~/build-check/devops-app เพื่อพิสูจน์ข้อความนี้ด้วยตนเอง โดยเลือกคำสั่งเอง บันทึกคำสั่งและผลไว้ใช้ในงานข้อ 2
ผลลัพธ์ที่คาดหวัง A.4: หลักฐานที่แสดงว่าสิ่งที่เพิ่มไว้ใน node_modules ก่อนสั่ง npm ci ไม่อยู่แล้วหลังสั่ง
จุดตรวจ A: main ของ repository ทีมมี package-lock.json ที่ merge ผ่าน pull request · สมาชิกทุกคนสั่ง npm ci สำเร็จบน clone ใหม่ใน ~/build-check
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
npm: command not found | ยังไม่ได้ติดตั้ง Node.js และ npm | ติดตั้งตามวิธีที่ผู้สอนกำหนด แล้วตรวจด้วย node -v และ npm -v |
npm ci บน clone ใหม่ยังขึ้น EUSAGE หลัง A.2 | pull request ยังไม่ merge หรือ clone มาก่อน merge | ตรวจหน้า pull request ว่า merge แล้ว ลบโฟลเดอร์แล้ว clone ใหม่ |
pull request มีไฟล์ใน node_modules หลายพันไฟล์ | .gitignore ถูกลบหรือแก้ | ปิด pull request นั้น ตรวจ .gitignore ให้มี node_modules/ แล้วสร้าง branch ใหม่ |
npm install ช้ามากหรือหมดเวลา | เครือข่ายของห้องปฏิบัติการ | รอแล้วสั่งใหม่ หรือแจ้งผู้สอนเพื่อตรวจ proxy |
สมาชิกคนอื่นเปิด pull request ที่มี package-lock.json ซ้ำ | สั่ง npm install ในโฟลเดอร์ทำงานแล้ว commit ทุกไฟล์ | ปิด pull request ที่ซ้ำ ให้มีเพียง pull request ของผู้รับผิดชอบ |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab B: อ่านและรัน npm scripts
สถานการณ์: ระบบ CI ในบทที่ 5 จะสั่ง npm test แต่ repository ยังไม่มี script นี้
เป้าหมาย: อ่าน script ที่มีอยู่ รันแอปและตรวจ endpoint แล้วเพิ่ม script test ผ่าน pull request (เตรียมพื้นสำหรับบทที่ 5)
ฝึกตามตัวอย่าง
B.1 รันแอปด้วย npm script (สมาชิกทุกคน)
ทำต่อในโฟลเดอร์ ~/build-check/devops-app ที่สั่ง npm ci แล้ว เปิด terminal ที่ 1 แล้วรันแอป
npm run
npm startLifecycle scripts included in [email protected]:
start
node src/server.js
> [email protected] start
> node src/server.js
listening on port 3000เปิด terminal ที่ 2 แล้วเรียก endpoint ทั้งสามแบบ
curl http://localhost:3000/
curl http://localhost:3000/health
curl -i http://localhost:3000/api/items/99DevOps App v0.1.0
{"status":"ok","version":"0.1.0"}
HTTP/1.1 404 Not Found
...
{"error":"not found"}กด Ctrl+C ใน terminal ที่ 1 เพื่อหยุดแอป แล้วลองเปลี่ยนพอร์ตด้วยตัวแปรสภาพแวดล้อม PORT ซึ่งโค้ดใน src/server.js อ่านไว้แล้ว
PORT=4000 npm startlistening on port 4000การกำหนดพอร์ตจากตัวแปรสภาพแวดล้อมแทนการเขียนตายตัวในโค้ดคือการแยกค่าตั้งออกจากโค้ด ซึ่งบทที่ 9 จะขยายเป็นหลักการเต็ม กด Ctrl+C เพื่อหยุดแอปอีกครั้ง
B.2 ลอง npm test ก่อนมี script (สมาชิกทุกคน)
npm test
echo $?npm error Missing script: "test"
npm error
npm error To see a list of scripts, run:
npm error npm run
...
1B.3 เพิ่ม script test และเปิด pull request (สมาชิกคนที่ 2)
cd ~/devops-app
git switch main
git pull
git switch -c build/add-test-script
npm ci
npm pkg set scripts.test="node --test"
git diff@@ -4,7 +4,8 @@
"description": "แอปตั้งต้นของรายวิชา 10306343 DevOps",
"main": "src/server.js",
"scripts": {
- "start": "node src/server.js"
+ "start": "node src/server.js",
+ "test": "node --test"
},คำสั่ง npm pkg set แก้ package.json โดยไม่ต้องเปิดโปรแกรมแก้ไขข้อความ และรักษารูปแบบ JSON ให้ถูกต้อง ผู้ที่ถนัดแก้ด้วย nano ทำได้เช่นกันแต่ต้องระวังเครื่องหมายจุลภาคท้ายบรรทัด start จากนั้นรัน test
npm test> [email protected] test
> node --test
TAP version 13
# Subtest: GET /health returns status ok
ok 1 - GET /health returns status ok
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 410.106093git add package.json
git commit -m "เพิ่ม npm script test"
git push -u origin build/add-test-scriptเปิด pull request เข้า main สมาชิกคนที่ 3 ตรวจทานโดย checkout branch นี้ในเครื่องของตนแล้วสั่ง npm test ให้เห็นผลเอง ก่อนกด Approve แล้ว merge ด้วย Create a merge commit
ตัดสินใจเอง B.4: ในเครื่องของตนเอง (ไม่ commit) เพิ่ม script ชื่อ hello ที่รัน echo hello แล้วลองสั่ง npm hello และ npm run hello ค้นจากเอกสาร npm run-script และ npm test ว่าเหตุใด npm test จึงสั่งได้โดยไม่ต้องมีคำว่า run แต่ script ชื่ออื่นต้องมี จากนั้นคืนค่า package.json ด้วย git restore package.json
ผลลัพธ์ที่คาดหวัง B.4: npm hello ล้มพร้อมคำแนะนำ ส่วน npm run hello พิมพ์ hello และคำอธิบายอ้างอิงเอกสาร npm
จุดตรวจ B: main มี script test และ npm test บน main ผ่าน 3 test
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
EADDRINUSE เมื่อสั่ง npm start | แอปจากครั้งก่อนยังรันอยู่ในอีก terminal | หยุดแอปเดิมด้วย Ctrl+C หรือใช้พอร์ตอื่นด้วย PORT=4000 |
curl: (7) Failed to connect | แอปไม่ได้รันอยู่ หรือเรียกผิดพอร์ต | ตรวจข้อความ listening on port ใน terminal ที่ 1 |
npm test ขึ้น Unexpected token หรือ JSON error | แก้ package.json ด้วยมือแล้วลืมเครื่องหมายจุลภาค | ตรวจด้วย git diff หรือใช้ npm pkg set แทน |
npm test ขึ้น Cannot find module 'express' | ยังไม่ได้สั่ง npm ci หลังสลับ branch หรือ clone ใหม่ | สั่ง npm ci แล้วรัน test ใหม่ |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab C: ทำ test ให้พังแล้วแก้ในเครื่อง
สถานการณ์: test ที่ผ่านตลอดไม่ได้พิสูจน์ว่า test ตรวจอะไรได้จริง ทีมต้องเห็นว่า test ล้มอย่างไรและอ่านผลออก ก่อนที่ CI จะเป็นผู้รายงานผลในบทที่ 5
เป้าหมาย: ทำให้ test ล้มโดยตั้งใจ อ่านผลเพื่อหาตำแหน่งและสาเหตุ แล้วตัดสินว่าควรแก้โค้ดหรือแก้ test งานใน Lab นี้ทำในเครื่องเท่านั้นและไม่ push
ฝึกตามตัวอย่าง
C.1 เปลี่ยนข้อมูลในแอปแล้วรัน test (สมาชิกทุกคน)
cd ~/devops-app
git switch main
git pull
npm ci
git switch -c try/break-test
nano src/app.jsเพิ่มรายการที่ 4 ในอาร์เรย์ items ให้เป็นดังนี้ แล้วบันทึกไฟล์
const items = [
{ id: 1, name: 'Linux' },
{ id: 2, name: 'Git' },
{ id: 3, name: 'GitHub Actions' },
{ id: 4, name: 'Docker' },
];npm test
echo $?# Subtest: GET /api/items returns 3 items
not ok 2 - GET /api/items returns 3 items
---
duration_ms: 6.431878
type: 'test'
location: '/home/ชื่อผู้ใช้/devops-app/test/app.test.js:23:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
4 !== 3
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 3
actual: 4
operator: 'strictEqual'
...
# tests 3
# suites 0
# pass 2
# fail 1
...
1C.2 อ่านผลของ test ที่ล้ม
| บรรทัดในผล | ความหมาย |
|---|---|
not ok 2 - GET /api/items returns 3 items | test ลำดับที่ 2 ล้ม ชื่อ test บอกสิ่งที่คาดหวัง |
location: '.../test/app.test.js:23:1' | test นี้เริ่มที่บรรทัด 23 ของไฟล์ test |
expected: 3 และ actual: 4 | test คาดหวัง 3 รายการ แต่แอปส่งกลับ 4 รายการ |
# pass 2 และ # fail 1 | สรุปผลรวม |
1 จาก echo $? | npm test จบด้วย exit status 1 ซึ่ง CI จะตีความว่าล้ม |
sed -n 23,27p test/app.test.jstest('GET /api/items returns 3 items', async () => {
const res = await fetch(`${base}/api/items`);
const body = await res.json();
assert.strictEqual(body.length, 3);
});ตัดสินใจเอง C.3: test ที่ล้มบอกเพียงว่าโค้ดกับ test ไม่ตรงกัน ไม่ได้บอกว่าฝ่ายใดผิด ตัดสินใจและแก้ให้ test ผ่านในแต่ละสถานการณ์ต่อไปนี้ โดยระบุว่าแก้ไฟล์ใดเพราะเหตุใด และรัน npm test ยืนยันทุกครั้ง
- สถานการณ์ ก ผู้สอนกำหนดความต้องการใหม่ว่า API ต้องมีรายการ Docker เป็นรายการที่ 4
- สถานการณ์ ข การเพิ่มรายการที่ 4 เกิดจากการแก้ไฟล์ผิดโดยไม่ตั้งใจ ความต้องการยังเป็น 3 รายการเหมือนเดิม
ผลลัพธ์ที่คาดหวัง C.3: ทั้งสองสถานการณ์ได้ # pass 3 และ # fail 0 แต่แก้คนละไฟล์ พร้อมเหตุผลที่โยงกับความต้องการ
C.4 เก็บกวาด branch ทดลอง
git restore src/app.js test/app.test.js
git switch main
git branch -D try/break-testใช้ -D (ตัวใหญ่) เพราะ branch ทดลองยังไม่ได้ merge และตั้งใจทิ้ง
จุดตรวจ C: ผู้เรียนอธิบายได้ว่าบรรทัดใดในผลของ test บอกตำแหน่ง ค่าที่คาดหวัง และค่าที่ได้จริง และ branch try/break-test ถูกลบแล้ว
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
SyntaxError แทน test ล้ม | ลืมเครื่องหมายจุลภาคหลังรายการที่ 3 | ตรวจไวยากรณ์ของอาร์เรย์ให้ทุกรายการคั่นด้วยจุลภาค |
| test ยังผ่านทั้งที่เพิ่มรายการแล้ว | บันทึกไฟล์ไม่สำเร็จ หรือแก้ไฟล์ในโฟลเดอร์อื่น | ตรวจด้วย git diff ว่ามีการเปลี่ยน src/app.js จริง |
git switch main ไม่ได้เพราะมีงานค้าง | ยังไม่ได้ restore ไฟล์ที่แก้ | สั่ง git restore ก่อนสลับ branch |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab D: ปรับเวอร์ชัน เปิด pull request และตรวจจาก clone ใหม่
สถานการณ์: repository ทีมมี lock file และ script test แล้ว ซึ่งถือเป็นความสามารถใหม่ของการ build ที่ยังเข้ากันกับของเดิม ทีมต้องประกาศรุ่นใหม่ที่ระบุได้ด้วยเลขเวอร์ชัน
เป้าหมาย: ปรับเวอร์ชันเป็น 0.2.0 ผ่าน pull request สร้าง tag v0.2.0 บน main และให้ทุกคนตรวจ M3 จาก clone ใหม่ (LLO 4.1 และ 4.2)
ฝึกตามตัวอย่าง
D.1 ปรับเวอร์ชันและเปิด pull request (สมาชิกคนที่ 3 หลัง Lab A และ B merge แล้ว)
cd ~/devops-app
git switch main
git pull
npm ci
git switch -c chore/bump-0.2.0
npm version minor --no-git-tag-version
git diff --stat
npm testv0.2.0
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
...
# pass 3
# fail 0npm version minor เพิ่มส่วน MINOR จาก 0.1.0 เป็น 0.2.0 และปรับเลขเวอร์ชันทั้งใน package.json และ package-lock.json ให้ตรงกัน ตัวเลือก --no-git-tag-version สั่งไม่ให้ npm สร้าง commit และ tag เอง เหตุผลของตัวเลือกนี้เป็นคำถามใน D.4
git add package.json package-lock.json
git commit -m "ปรับเวอร์ชันเป็น 0.2.0"
git push -u origin chore/bump-0.2.0เปิด pull request เข้า main โดยระบุในคำอธิบายว่าเพิ่ม MINOR เพราะเหตุใด สมาชิกคนที่ 1 ตรวจทานว่าเลขเวอร์ชันในทั้งสองไฟล์ตรงกัน แล้วกด Approve และ merge
D.2 สร้าง tag บน main (หัวหน้าทีม หลัง D.1 merge แล้ว)
tag ต้องชี้ไปที่ commit บน main หลัง merge เพราะ commit นั้นคือสิ่งที่ทีมยอมรับแล้วผ่าน review
cd ~/devops-app
git switch main
git pull
git tag -a v0.2.0 -m "เวอร์ชัน 0.2.0: เพิ่ม lock file และ npm script test"
git push origin v0.2.0branch protection ของบทที่ 3 ป้องกันเฉพาะ branch main จึงไม่ขัดขวางการ push tag
D.3 ตรวจ M3 จาก clone ใหม่ (สมาชิกทุกคน)
cd ~/build-check
rm -rf devops-app
git clone https://github.com/ชื่อบัญชีหัวหน้าทีม/devops-app.git
cd devops-app
npm ci && npm test
npm pkg get version
git describe --tagsadded 65 packages, and audited 66 packages in 667ms
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 410.072546
"0.2.0"
v0.2.0เครื่องหมาย && สั่งให้ npm test ทำงานเฉพาะเมื่อ npm ci สำเร็จ ซึ่งเป็นลำดับเดียวกับขั้นตอนของ CI ในบทที่ 5 คัดลอกผลทั้งหมดตั้งแต่ git clone จนถึง git describe เป็นหลักฐานงานข้อ 1 สุดท้ายรันแอปแล้วยืนยันว่าแอปรายงานเวอร์ชันใหม่
npm startcurl http://localhost:3000/health{"status":"ok","version":"0.2.0"}ตัดสินใจเอง D.4 (LLO 4.2): ทดลองใน clone แยกที่สร้างขึ้นเพื่อทดลองโดยเฉพาะ (ห้าม push) ว่าถ้าสั่ง npm version minor โดยไม่ใส่ --no-git-tag-version ใน repository Git จะเกิดอะไรขึ้นบ้าง ทั้งเมื่อมีไฟล์ที่ยังไม่ commit และเมื่อ working tree สะอาด ใช้ผลการทดลองร่วมกับเอกสาร npm version อธิบายว่าเหตุใดรายวิชาจึงให้ปรับเวอร์ชันโดยไม่สร้าง tag บน branch แล้วสร้าง tag บน main หลัง merge แทน
ผลลัพธ์ที่คาดหวัง D.4: ผลการทดลองทั้งสองกรณี และคำอธิบายที่โยงกับตำแหน่งของ tag เทียบกับ merge commit บน main
จุดตรวจ D: main มี "version": "0.2.0" · หน้า Tags ของ repository มี v0.2.0 · สมาชิกทุกคนมีผลรัน npm ci && npm test จาก clone ใหม่
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
pull request ของ D.1 มี conflict ที่ package.json | สร้าง branch ก่อน pull request ของ Lab B merge | git pull origin main ใน branch ของตน แก้ conflict ให้มีทั้ง script test และเวอร์ชัน 0.2.0 แล้ว push |
npm version ขึ้น Git working directory not clean. | ไม่ได้ใส่ --no-git-tag-version และมีไฟล์ที่ยังไม่ commit | ใส่ตัวเลือก --no-git-tag-version ตามขั้น D.1 |
เวอร์ชันกลายเป็น 1.0.0 | สั่ง npm version major | git restore package.json package-lock.json แล้วสั่ง minor ใหม่ |
git push origin v0.2.0 ขึ้น src refspec v0.2.0 does not match any | ยังไม่ได้สร้าง tag หรือสร้างในโฟลเดอร์อื่น | ตรวจด้วย git tag ในโฟลเดอร์ ~/devops-app |
git describe --tags ขึ้น fatal: No names found | clone ก่อนหัวหน้าทีม push tag | clone ใหม่ หรือสั่ง git fetch --tags |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
เก็บกวาดเมื่อจบปฏิบัติการ
- หยุดแอปที่ยังรันอยู่ด้วย Ctrl+C ในทุก terminal
- ลบ branch ที่ merge แล้วในเครื่องด้วย
git branch -d ชื่อbranchและสั่งgit fetch --pruneตามบทที่ 3 - โฟลเดอร์
~/build-checkเก็บไว้ใช้ตรวจจาก clone ใหม่ในบทถัดไปได้ หรือลบด้วยrm -rf ~/build-checkเมื่อส่งงานแล้ว - ตรวจด้วย
git statusในโฟลเดอร์~/devops-appว่าไม่มีไฟล์ค้างก่อนเริ่มบทที่ 5
สรุป
build lifecycle แบ่งการสร้างซอฟต์แวร์เป็นขั้นที่มีลำดับแน่นอน สำหรับแอปของรายวิชาขั้นที่สำคัญคือการเตรียม dependency ด้วย npm ci และการทดสอบด้วย npm test คำสั่งทั้งสองต้องให้ผลเหมือนกันทุกครั้งบนเครื่องใดก็ได้ จึงจะใช้เป็นฐานของระบบอัตโนมัติได้
dependency ที่ระบุเพียงตัวเดียวพา transitive dependency มาอีกหลายสิบตัว และช่วงเวอร์ชันอย่าง ^5.3.0 ทำให้การติดตั้งต่างวันได้ผลต่างกัน lock file บันทึกรุ่นที่ติดตั้งจริงของทุกตัว ส่วน npm ci ติดตั้งตาม lock file อย่างเคร่งครัด หยุดเมื่อ lock file ไม่ตรงกับ package.json และลบ node_modules เดิมก่อนเสมอ
Semantic Versioning ใช้เลข MAJOR.MINOR.PATCH สื่อผลของการเปลี่ยนแปลงต่อผู้ใช้ API เวอร์ชัน 0.y.z เป็นช่วงพัฒนาเริ่มต้นที่เปลี่ยนได้ตลอด แนวคิด build, release, run ของ Twelve-Factor กำหนดให้ release มี ID เฉพาะและแก้ไขไม่ได้ เลขเวอร์ชันและ tag v0.2.0 จึงเป็นจุดเริ่มต้นของ immutable artifact ที่จะใช้ในบทที่ 6, 8 และ 12
สิ่งที่ส่งต่อไปยังโครงงานและ Capstone
| สิ่งที่ได้ | นำไปใช้ที่ |
|---|---|
package-lock.json และ M3 | บทที่ 5 ขั้น npm ci ใน workflow · บทที่ 6 ขั้นติดตั้ง dependency ใน Dockerfile |
npm script test | บทที่ 5 ขั้น npm test ที่ตัดสินว่า pull request ผ่านหรือไม่ |
เวอร์ชัน 0.2.0 และ tag v0.2.0 | บทที่ 8 ใช้เป็น tag ของ image · บทที่ 12 ใช้เป็นรุ่นที่ rollback กลับไปได้ |
| ทักษะอ่านผลของ test ที่ล้ม | บทที่ 5 อ่าน log ของ run สีแดง |
| Learning Log ครั้งที่ 4 | portfolio ที่ส่งในบทที่ 13 |
สรุปคำสั่งในบทนี้
| คำสั่ง | หน้าที่ |
|---|---|
npm install | ติดตั้ง dependency และสร้างหรือปรับ package-lock.json |
npm ci | ติดตั้ง dependency ตาม lock file อย่างเคร่งครัด ใช้บนเครื่องใหม่และ CI |
npm ls | แสดง dependency ที่ติดตั้งแล้ว |
npm run | แสดงรายการ npm scripts |
npm start, npm test, npm run ชื่อscript | รัน script ที่กำหนดใน package.json |
npm pkg set scripts.test="node --test" | เพิ่มหรือแก้ค่าใน package.json จาก command line |
npm pkg get version | อ่านค่าเวอร์ชันจาก package.json |
npm version minor --no-git-tag-version | เพิ่มส่วน MINOR ของเวอร์ชันโดยไม่สร้าง commit และ tag |
node --test | รัน test ที่เขียนด้วยโมดูล node:test |
PORT=4000 npm start | รันแอปโดยกำหนดค่าตัวแปรสภาพแวดล้อม |
git tag -a v0.2.0 -m "...", git push origin v0.2.0 | สร้าง annotated tag และส่งขึ้น remote |
git describe --tags | แสดง tag ล่าสุดที่เข้าถึงได้จาก commit ปัจจุบัน |
git branch -D ชื่อbranch | ลบ branch ที่ยังไม่ merge (ตั้งใจทิ้ง) |
แบบฝึกหัดท้ายบท
ตอนที่ 1 ฝึกทบทวน
ชุด FP10306343-P04 จำนวน 12 ข้อ ข้อละ 1 คะแนน รวม 12 คะแนน ครอบคลุม build lifecycle, ความต่างระหว่าง npm ci กับ npm install, lock file, npm scripts, กติกา SemVer และแนวคิด immutable artifact
เข้าสู่ระบบห้องเรียนด้วยบัญชีที่ลงทะเบียนในรายวิชา 10306343 แล้วตอบคำถามให้ครบ ระบบตรวจคำตอบและแสดงคำอธิบายเพื่อทบทวน เมื่อทำครบและส่งผล ระบบจะบันทึกคะแนนไว้กับบัญชีของผู้เรียน สามารถทำซ้ำเพื่อทบทวนได้ ส่วนคะแนนที่นำไปรวมในรายวิชาเป็นไปตามเกณฑ์และกำหนดส่งในสมุดคะแนน
ตอนที่ 2 แบบฝึกหัดเขียนตอบ (5 ข้อ)
ชุด FW10306343-P04 จำนวน 5 ข้อ ข้อละ 4 คะแนน รวม 20 คะแนน เป็นงานเขียนตอบที่ดัดแปลงจากแบบฝึกหัดเดิมด้วยสถานการณ์ใหม่ ครอบคลุมความหมายของเลขเวอร์ชัน SemVer และเวอร์ชัน 0.y.z, direct และ transitive dependency กับบทบาทของ lock file, การกำหนดเวอร์ชันถัดไปของเครื่องมือ command line, การหาเวอร์ชันที่ช่วงเวอร์ชันเลือก และการวิเคราะห์ error ของ npm ci เมื่อ lock file ไม่ตรงกับ package.json
เข้าสู่ระบบห้องเรียนด้วยบัญชีที่ลงทะเบียนในรายวิชา 10306343 แล้วเปิดรอบส่งงานจากลิงก์ด้านล่าง แต่ละข้อบอกสิ่งที่ต้องส่งและเกณฑ์ที่ใช้ตรวจ พิมพ์คำตอบหรือแนบไฟล์ได้สูงสุด 8 ไฟล์ ผู้สอนตรวจตามเกณฑ์และแจ้งผลรายเกณฑ์ คะแนนที่นำไปรวมคือครั้งที่ดีที่สุดที่ตรวจแล้วและส่งก่อนกำหนดในสมุดคะแนน
ใช้ประกอบการตรวจตามรูบริก ไม่เปิดให้นักศึกษา
แบบฝึกหัดเพิ่มเติมสำหรับอภิปรายและเขียนตอบ
แบบฝึกหัดแบ่งเป็นสามกลุ่ม ได้แก่ ความเข้าใจ การประยุกต์ และการวิเคราะห์ปัญหา แต่ละข้อระบุผลลัพธ์การเรียนรู้ที่ฝึก กลุ่ม ข ข้อ ข1–ข3 ใช้เป็นใบงาน SemVer (งานข้อ 3)
ก. ความเข้าใจ
- ก1 (LLO 4.1) ระบุความต่างระหว่าง
npm ciกับnpm install3 ข้อ และบอกว่าในงานแต่ละแบบต่อไปนี้ควรใช้คำสั่งใด: (ก) เพิ่ม dependency ใหม่ระหว่างพัฒนา (ข) ติดตั้งบนเครื่อง CI (ค) ติดตั้งใน Dockerfile - ก2 (LLO 4.2) อธิบายความหมายของ MAJOR, MINOR และ PATCH และความพิเศษของเวอร์ชันที่ขึ้นต้นด้วย
0. - ก3 (LLO 4.1) อธิบายความต่างระหว่าง direct dependency กับ transitive dependency และระบุว่าไฟล์ใดบันทึกรุ่นของ transitive dependency
ข. การประยุกต์ (ใบงาน SemVer)
แอปไลบรารีหนึ่งอยู่ที่เวอร์ชัน 2.3.1 กำหนดเวอร์ชันถัดไปในแต่ละกรณีพร้อมเหตุผล 1–2 บรรทัด
- ข1 (LLO 4.2) แก้ bug ที่ฟังก์ชัน
formatDate()คืนค่าวันผิดเมื่อเป็นปีอธิกสุรทิน โดยชื่อฟังก์ชันและพารามิเตอร์เหมือนเดิม - ข2 (LLO 4.2) เพิ่มฟังก์ชันใหม่
formatTime()โดยฟังก์ชันเดิมทั้งหมดทำงานเหมือนเดิม - ข3 (LLO 4.2) เปลี่ยนชื่อฟังก์ชัน
formatDate()เป็นtoThaiDate()และลบชื่อเดิมออก
ค. การวิเคราะห์ปัญหา
- ค1 (LLO 4.1) สมาชิกคนหนึ่งเพิ่ม dependency ด้วยการพิมพ์ลงใน
package.jsonเองแล้วเปิด pull request ผลการตรวจจาก clone ใหม่ขึ้นข้อความMissing: [email protected] from lock fileวิเคราะห์สาเหตุ และเขียนลำดับคำสั่งที่ถูกต้องสำหรับการเพิ่ม dependency - ค2 (LLO 4.2)
package.jsonของโครงการหนึ่งระบุ"some-lib": "^1.4.0"ผู้พัฒนาsome-libออกเวอร์ชัน1.5.0ที่ลบฟังก์ชันเดิมออกหนึ่งฟังก์ชัน วิเคราะห์ว่าโครงการที่ไม่มี lock file และโครงการที่มี lock file และใช้npm ciจะได้รับผลต่างกันอย่างไร และผู้พัฒนาsome-libทำผิดกติกา SemVer ข้อใด - ค3 (LLO 4.1) ผลรันของสมาชิกคนหนึ่งที่ส่งเป็นหลักฐาน M3 แสดง
npm testผ่าน แต่ไม่มีบรรทัดgit cloneและไม่มีบรรทัดadded ... packagesวิเคราะห์ว่าหลักฐานนี้ยังพิสูจน์อะไรไม่ได้ และควรรันใหม่อย่างไร
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
การศึกษาค้นคว้าเพิ่มเติม
- ศึกษาเอกสาร npm ci แล้วเขียนบันทึกความต่างระหว่าง
npm ciกับnpm install(งานข้อ 2) โดยใช้ผลการทดลองจาก Lab A.4 ประกอบ - อ่านข้อกำหนด Semantic Versioning 2.0.0 ส่วน Summary และ FAQ แล้วทำใบงาน SemVer (แบบฝึกหัดกลุ่ม ข)
- อ่าน The Twelve-Factor App: V. Build, release, run แล้วเตรียมคำตอบว่าแอปของทีมจะมี "release ID" ในรูปแบบใดเมื่อถึงบทที่ 8
- ทำแบบฝึกหัดท้ายบทให้ครบ และเขียน Learning Log ครั้งที่ 4
- (ไม่บังคับ) อ่าน Apache Maven: Introduction to the Build Lifecycle แล้วเทียบ phase ของ Maven กับคำสั่ง npm ในหัวข้อ 4.1 ซึ่งเป็นหัวข้อหนึ่งที่เลือกทำเป็นงานต่อยอดได้ตั้งแต่บทที่ 6
เอกสารอ้างอิง
แหล่งข้อมูลเพิ่มเติมสำหรับตัวอย่างและกรณีอธิบาย
ตรวจเอกสารออนไลน์เมื่อ 23 ตุลาคม 2569 ตัวเลขและเหตุการณ์ที่ระบุว่าสมมติใช้เพื่ออธิบายกลไก ไม่ใช่ผลทดลองหรือสถิติขององค์กรจริง
เอกสารอ้างอิงของเนื้อหาเดิม
- Apache Software Foundation. (n.d.). Introduction to the build lifecycle. Apache Maven. https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
- npm, Inc. (n.d.). npm ci (v10). npm Docs. https://docs.npmjs.com/cli/v10/commands/npm-ci
- npm, Inc. (n.d.). npm run-script (v10). npm Docs. https://docs.npmjs.com/cli/v10/commands/npm-run-script
- npm, Inc. (n.d.). npm test (v10). npm Docs. https://docs.npmjs.com/cli/v10/commands/npm-test
- npm, Inc. (n.d.). npm version (v10). npm Docs. https://docs.npmjs.com/cli/v10/commands/npm-version
- Preston-Werner, T. (n.d.). Semantic Versioning 2.0.0. https://semver.org/
- Wiggins, A. (n.d.). The Twelve-Factor App: V. Build, release, run. https://12factor.net/build-release-run