สารบัญ
1ภาพรวม: ไฟล์ไหนทำอะไร
Azure Artifacts ใช้ไฟล์ .npmrc เพื่อให้ npm client รู้ว่า "ไปดึงแพ็กเกจที่ไหน" และ "พิสูจน์ตัวตนอย่างไร" Microsoft แนะนำให้แยกเป็น สองไฟล์: ไฟล์ระดับโปรเจกต์เก็บ URL ของ feed (commit ลง git ได้) ส่วนไฟล์ระดับผู้ใช้ ~/.npmrc เก็บ credential (ห้าม commit)
2สิ่งที่ต้องมีก่อนเริ่ม
- Node.js + npm เวอร์ชัน LTS ที่ยังได้รับ support (Microsoft Learn ไม่ได้ระบุเวอร์ชันขั้นต่ำ) ตรวจด้วย
node -vและnpm -v - Azure DevOps organization, project และ feed ที่สร้างไว้แล้ว (Artifacts > Create Feed)
- สิทธิ์บน feed อย่างน้อย Feed Reader สำหรับอ่านแพ็กเกจที่อยู่ใน feed แล้ว หากต้องการติดตั้งแพ็กเกจจาก upstream (เช่น npmjs.com) เป็นครั้งแรก ต้องเป็น Feed and Upstream Reader (Collaborator) ขึ้นไป เพราะการติดตั้งครั้งแรกจะบันทึกสำเนาลง feed
- Personal Access Token (PAT) ที่มี scope Packaging (Read) สำหรับดึงแพ็กเกจที่อยู่ใน feed แล้ว (ถ้าต้องดึงจาก upstream ครั้งแรก ใช้ Read & write ดูหมายเหตุด้านล่าง)
ภาพจำลอง: หน้าต่างสร้าง PAT
ชื่อปุ่มและเมนูจริงอาจต่างจากภาพนี้ตามเวอร์ชันของ Azure DevOps PAT จะแสดงให้เห็น ครั้งเดียว หลังกด Create ให้คัดลอกเก็บในที่ปลอดภัยทันที
3รูปแบบ URL ของ feed
ตอนสร้าง feed คุณเลือก Scope ว่าเป็น project-scoped หรือ organization-scoped รูปแบบ URL ต่างกัน:
| ชนิด feed | Registry URL |
|---|---|
| Organization-scoped | https://pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/registry/ |
| Project-scoped | https://pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/ |
ลงท้าย / เสมอ และต้องลงท้าย /npm/registry/ สำหรับบรรทัด registry=
4.npmrc ของโปรเจกต์ (commit ลง git)
สร้างไฟล์ .npmrc ไว้ในโฟลเดอร์เดียวกับ package.json ไฟล์นี้มี URL เท่านั้น ไม่มี credential
แบบ registry= (ทุกแพ็กเกจผ่าน feed)
registry=https://pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/registry/registry=https://pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/แบบ scoped: @myscope:registry=
@myscope:registry=https://pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/แพ็กเกจต้องมีชื่อแบบ @myscope/package-name (ตั้งใน package.json ของแพ็กเกจนั้น) npm จะส่งเฉพาะชื่อที่ขึ้นต้น @myscope/ ไปที่ feed ส่วนที่เหลือไป registry เดิม
ควรเลือกแบบไหน
registry= (ทั้งหมดผ่าน feed)
เหมาะเมื่อ: feed เปิด upstream npmjs.com ไว้ และต้องการให้ทุกแพ็กเกจผ่านจุดเดียว ได้ cache, ตรวจสอบ และ audit รวมศูนย์ ข้อควรระวัง: ทุก install ต้องมี credential และถ้า upstream ปิดอยู่ แพ็กเกจสาธารณะจะหาไม่เจอ (E404)
@myscope:registry= (เฉพาะ scope)
เหมาะเมื่อ: แพ็กเกจภายในองค์กรใช้ scope ชัดเจน และ feed ไม่ได้เปิด upstream หรือไม่อยากให้แพ็กเกจสาธารณะผ่าน feed Microsoft ระบุว่ามีประโยชน์ในระบบ on-premises ที่ไม่มีอินเทอร์เน็ต ข้อดี: ลดการสับสนชื่อซ้ำ (name collision)
5Credentials (ไฟล์ระดับผู้ใช้)
5a. Windows: vsts-npm-auth
บน Windows ใช้ tool vsts-npm-auth ที่สร้างและต่ออายุ token ลงไฟล์ user-level ให้อัตโนมัติ (ไม่รองรับ Azure DevOps Server และไม่รองรับ macOS/Linux)
npm install -g vsts-npm-auth --registry https://registry.npmjs.comในโฟลเดอร์โปรเจกต์ที่มี .npmrc (หัวข้อ 4) แล้ว รัน:
vsts-npm-auth -config .npmrcเมื่อ token หมดอายุ npm จะตอบ 401 Unauthorized ให้รันคำสั่งเดิมซ้ำเพื่อต่ออายุ
--always-auth false ตอนติดตั้ง และตั้ง always-auth=true ใน .npmrc option always-auth npm 7+ ไม่ใช้ค่านี้แล้ว และ npm รุ่นใหม่ (เช่น 10/11) จะเตือน Unknown config ไม่ต้องใส่และถ้ามีอยู่ให้ลบทิ้งได้5b. macOS / Linux: ~/.npmrc + PAT (base64)
สร้าง PAT (หัวข้อ 2) แล้วแปลงเป็น base64 โดย ไม่ให้ PAT ค้างใน shell history: พิมพ์แบบไม่แสดงตัวอักษร ไม่ใส่ในอาร์กิวเมนต์ของคำสั่ง
printf 'PAT (ไม่แสดงตัวอักษร): ' && read -rs PAT && echo
printf '%s' "$PAT" | base64 | tr -d '\n'; echo
unset PATวางผลลัพธ์ (สายอักขระยาว) แทน <BASE64_PAT> ด้านล่าง ถ้าไม่มี shell ที่เหมาะ ใช้วิธีของ Microsoft ที่ถามผ่าน Node.js ก็ได้ (ตั้ง historySize:0 ไว้แล้ว):
node -e "require('readline').createInterface({input:process.stdin,output:process.stdout,historySize:0}).question('PAT> ',p=>{console.log(Buffer.from(p.trim()).toString('base64'));process.exit();})"echo -n "PAT จริง" | base64 (ตัวอย่างในเอกสารทางการ) จะทำให้ PAT ถูกบันทึกใน history ถ้าเผลอทำไปแล้วให้ revoke PAT นั้นและสร้างใหม่เพิ่มบล็อกนี้ใน ~/.npmrc (ไฟล์ในโฮมของคุณ ไม่ใช่ในโปรเจกต์) สำหรับ feed ชนิด project-scoped:
; begin auth token
//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/:username=anything
//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/:_password=<BASE64_PAT>
//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/:email=not-used@example.com
//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/:username=anything
//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/:_password=<BASE64_PAT>
//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/:email=not-used@example.com
; end auth tokenสำหรับ feed ชนิด organization-scoped ตัด /<PROJECT> ออกจากทุกบรรทัด:
; begin auth token
//pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/registry/:username=anything
//pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/registry/:_password=<BASE64_PAT>
//pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/registry/:email=not-used@example.com
//pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/:username=anything
//pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/:_password=<BASE64_PAT>
//pkgs.dev.azure.com/<ORG>/_packaging/<FEED>/npm/:email=not-used@example.com
; end auth tokenusername: ใส่ค่าอะไรก็ได้ที่ไม่ใช่สตริงว่าง_password: base64 ของ PAT เพียงอย่างเดียว (ไม่ใช่user:PAT)email: npm บังคับให้มี แต่ไม่ใช้ค่านี้ ใช้ค่าสมมติได้- บรรทัดไม่ขึ้นต้นด้วย
https:(เป็น//host/path/) เพื่อผูก credential กับ URL นั้น
chmod 600 ~/.npmrc5c. กฎเหล็ก: อย่า commit PAT
- PAT อยู่ใน
~/.npmrcเท่านั้น ไฟล์โปรเจกต์มีแค่ URL - ถ้าโปรเจกต์ต้องมีไฟล์ที่มี credential จริง (เช่น เครื่องชั่วคราว) ให้เพิ่มชื่อไฟล์นั้นใน
.gitignoreก่อน - เผลอ commit PAT แล้ว: revoke PAT ทันที ลบจากไฟล์ไม่พอเพราะยังอยู่ใน history
6CI: ไม่ใช้ PAT ส่วนตัวของใคร
6a. Azure Pipelines: npmAuthenticate@0
task นี้ใส่ credential ของ build ลงใน .npmrc ที่ระบุ เฉพาะช่วงที่ build รัน (ห้ามใช้ร่วมกับ task Npm@1) วางก่อนคำสั่ง npm install
steps:
- task: npmAuthenticate@0
inputs:
workingFile: .npmrc
- script: npm ci
displayName: Install packagescustomEndpoint กับ npm service connection)6b. GitHub Actions / CI อื่น: env var ใน .npmrc
เก็บ base64 ของ PAT ไว้ใน secret ของ CI ส่วน .npmrc ที่ commit ต้องมี URL อย่างเดียว (แบบหัวข้อ 4) แล้วให้ CI เขียนบรรทัด auth ลง ~/.npmrc ก่อน npm ci โดย npm แทนค่า ${VAR} ด้วย environment variable ตอนรัน
name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Write npm credentials (CI only)
run: |
P=//pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm
for B in "$P/registry/" "$P/"; do
printf '%s\n' "$B:username=ci" "$B:_password=\${NPM_TOKEN}" "$B:email=ci@example.com" >> ~/.npmrc
done
- run: npm ci
env:
NPM_TOKEN: ${{ secrets.AZURE_ARTIFACTS_PAT_B64 }}- secret
AZURE_ARTIFACTS_PAT_B64= ผลลัพธ์ base64 จากหัวข้อ 5b (ไม่ใช่ PAT ดิบ) - ใช้ PAT ของ service account แยกต่างหาก scope Packaging (Read) ถ้าดึงเฉพาะแพ็กเกจที่อยู่ใน feed แล้ว (ถ้าต้องดึงจาก upstream ครั้งแรก ใช้ Read & write ร่วมกับบทบาท Collaborator) อย่าใช้ PAT ส่วนตัว
- ขั้นตอน
npm ciต้องตั้งNPM_TOKENด้วย เพราะ npm อ่านค่า${NPM_TOKEN}ใน~/.npmrcตอนรัน
${NPM_TOKEN} ตรง ๆ เป็นรหัสผ่าน → E401 และเพราะค่าใน project ชนะ ~/.npmrc จึงห้ามใส่บรรทัด auth ในไฟล์ที่ commit7ตรวจสอบว่าใช้ได้จริง
registry ชี้ถูกที่
npm config get registryต้องได้ URL ของ feed (ถ้าใช้แบบ scoped ให้ดู npm config get @myscope:registry)
ดูแพ็กเกจ
npm view @myscope/<PACKAGE> versionถ้าใช้ registry= กับ upstream ลอง npm view <ชื่อแพ็กเกจสาธารณะ> version ได้เช่นกัน
ติดตั้งจริง
npm install$ npm config get registry https://pkgs.dev.azure.com/<ORG>/<PROJECT>/_packaging/<FEED>/npm/registry/ $ npm view @myscope/<PACKAGE> version 1.4.2 $ npm install added 128 packages, and audited 129 packages in 9s found 0 vulnerabilities
ตัวเลขและข้อความเป็นตัวอย่างประกอบ ผลจริงขึ้นกับโปรเจกต์ของคุณ
npm install --loglevel verbose หรือ npm config list เพื่อดูว่า .npmrc ไฟล์ไหนถูกโหลด (ระวังอย่าแชร์ผลที่มี credential)8แก้ปัญหาที่เจอบ่อย
| อาการ | สาเหตุที่เป็นไปได้ | วิธีแก้ |
|---|---|---|
| E401 Unauthorized | PAT หมดอายุ / base64 ผิด (มีขึ้นบรรทัดใหม่, เข้ารหัสผิดสตริง) / scope ไม่พอ / URL ใน credential ไม่ตรงกับ registry | Windows: รัน vsts-npm-auth -config .npmrc ใหม่ อื่น ๆ: สร้าง PAT ใหม่ เข้ารหัสใหม่ด้วย tr -d '\n' เช็กว่าบรรทัด //pkgs… ตรงกับ URL ของ registry= ทุกตัวอักษร |
| E403 Forbidden | ยืนยันตัวตนผ่านแต่ไม่มีสิทธิ์: ไม่ใช่ Feed Reader, หรือ PAT ไม่มี Packaging scope, หรือการติดตั้งจาก upstream ต้องการ Collaborator, หรือ PAT scope Read อย่างเดียวขณะดึงแพ็กเกจจาก upstream ครั้งแรก | ขอสิทธิ์ Feed Reader (หรือ Collaborator ถ้าต้องดึงจาก upstream ครั้งแรก) ตรวจ scope ของ PAT |
| E404 Not Found | ชื่อแพ็กเกจ/scope ผิด, ไม่ได้ตั้ง @myscope:registry, หรือ feed ไม่ได้เปิด upstream แต่ใช้ registry= ดึงแพ็กเกจสาธารณะ, หรือ feed อยู่ใน project ต่างจาก URL | ตรวจชื่อแพ็กเกจใน Artifacts, เปิด upstream sources ใน Feed settings หรือเปลี่ยนไปใช้แบบ scoped, ตรวจ URL ตามหัวข้อ 3 |
always-auth warning / Unknown config | ค่าเก่าจากเอกสารรุ่นเก่า npm 7+ ไม่ใช้ค่านี้แล้ว และ npm รุ่นใหม่ (เช่น 10/11) จะเตือน Unknown config | ลบ always-auth ออกจากทุกไฟล์ .npmrc (มักไม่กระทบการทำงาน) |
| timeout / ECONNRESET / ผ่าน proxy | องค์กรใช้ proxy หรือ TLS inspection | ตั้ง npm config set proxy / https-proxy ตามข้อกำหนดองค์กร ใส่ CA ภายในด้วย cafile= (ห้ามปิด strict-ssl เป็นทางลัดถาวร) |
| แก้ทุกอย่างแล้วยังเหมือนเดิม | cache เก่า หรือ metadata เก่า | npm cache clean --force แล้วลองใหม่ (ลบ node_modules และ lockfile เฉพาะเมื่อจำเป็น) |
package-lock.json อาจจำ URL ของ registry เดิมไว้ในฟิลด์ resolved ถ้าย้ายมาใช้ feed ใหม่ ให้ตรวจ URL เหล่านั้นด้วย9Security checklist และการหมุน PAT
- PAT scope Packaging (Read) สำหรับแพ็กเกจที่อยู่ใน feed แล้ว (Read & write เฉพาะกรณีต้องดึงจาก upstream ครั้งแรก)
- ตั้งวันหมดอายุสั้น (เช่น 30 วัน) และใส่ปฏิทินเตือนก่อนหมดอายุ
~/.npmrcสิทธิ์chmod 600- ไม่มี
_password/_authTokenจริงในไฟล์ที่อยู่ใน git (ตรวจด้วยgit grep _password) - CI ใช้
npmAuthenticate@0หรือ secret store ไม่ฝัง PAT ใน YAML - ใช้ service account แยกสำหรับ CI และ PAT รายคนสำหรับ dev
- คนออกจากทีม หรือสงสัยว่ารั่ว: revoke PAT ทันที
ขั้นตอนหมุน PAT
- สร้าง PAT ใหม่ (scope เดิม) ก่อนอันเก่าหมดอายุ
- เข้ารหัส base64 ตามหัวข้อ 5b และแก้
_passwordทั้งสองบรรทัด (หรืออัปเดต secret ใน CI) - รัน
npm view <แพ็กเกจใน feed> versionและnpm installเพื่อตรวจ - Revoke PAT เก่าใน User settings > Personal access tokens