@suphark/config-client
v0.15.0
Published
Pi Config CLI/SDK: ดึง env ของแอปจาก Pi Config มาเขียน .env.local ตอน build (pi-config pull)
Maintainers
Readme
@suphark/config-client
CLI และ SDK ของ Pi Config — deployment ของแอปถือความลับตัวเดียวคือ PI_CONFIG_TOKEN
ค่าที่เหลือดึงตอน build แล้วเขียนลง .env.local ให้
pnpm add -D @suphark/config-client # npm i -D … / yarn add -D … / bun add -d …แพ็กเกจไม่ผูกกับตัวจัดการแพ็กเกจไหน — dependency ตอนรันมีแค่ zod
ใช้ในเครื่อง
วาง token ไว้บรรทัดแรกของ .env.local (ไฟล์ที่ gitignore อยู่แล้ว) แล้ว pull
PI_CONFIG_TOKEN=pct_…pnpm pi-config pullค่าจะถูกต่อท้ายไฟล์ในช่วง marker — ไม่ต้องตั้งตัวแปรใน terminal
(ถ้าตั้ง PI_CONFIG_TOKEN ใน environment ไว้ด้วย ตัวใน environment ชนะ)
ใช้กับ Vercel / CI
ตั้ง env สองตัวที่ deployment (คนละ token ต่อ environment)
PI_CONFIG_TOKEN=pct_…
PI_CONFIG_URL=https://config.suphark.com # ไม่ตั้งก็ได้ ค่าเริ่มต้นเป็นตัวนี้แล้วเรียกก่อน build
{ "buildCommand": "pnpm pi-config pull && pnpm next build" }ตัวอื่น: npx pi-config pull && npx next build · yarn pi-config pull && yarn next build · bunx pi-config pull && bunx next build
ถ้าใช้
pull --replace: ไฟล์เดิมจะถูกสำรองเป็น.env.local.bakซึ่งมีความลับชุดเดียวกัน.gitignoreมาตรฐานของ Next มี.env*.localซึ่ง ไม่ ครอบไฟล์นี้ — เติม.env*.bakด้วย (CLI ตั้งแต่ 0.5.0 ปฏิเสธการสำรองจนกว่าจะเติม หรือสั่ง--no-backup)
ลบ env เดิมออกจาก dashboard ให้หมด — process.env ชนะไฟล์ .env* เสมอ ถ้าไม่ลบ การย้ายจะดู "สำเร็จ" ทั้งที่ยังใช้ค่าเดิม
คีย์ที่ค่าจริงต้องอยู่ที่ Vercel
คีย์ที่ Vercel เองเป็นคนอ่านหรือตั้ง จาก env ของโปรเจกต์ Vercel — ไม่ใช่แอปของคุณ ค่าจริงต้องตั้งที่ Vercel เสมอ
เก็บไว้ใน Pi Config เป็นเอกสาร (ว่าแอปนี้ต้องมีคีย์นี้) ได้ ถ้าติ๊ก "ไม่เขียนค่านี้ลงไฟล์ .env" — Pi Config จะไม่ส่งค่ามาให้
pull เขียนทับ แม้แต่ CLI รุ่นเก่า (CLI ≥ 0.11.0 บอกชื่อคีย์พวกนี้ใน build log และเตือนบน Vercel ถ้าลืมตั้งที่ Vercel env):
| คีย์ | ทำไม |
|---|---|
| CRON_SECRET | Vercel Cron แนบ Authorization: Bearer <CRON_SECRET> จาก env ของโปรเจกต์ Vercel ถ้าเก็บใน Pi Config ด้วย ค่าที่ pull ลง .env.local ตอน build จะทับค่าในฟังก์ชัน → cron ได้ 401 ทุกครั้งแม้ redeploy (เจอจริงที่ pi-done) · ตั้งค่าที่ Vercel แล้วติ๊ก "ไม่เขียนลงไฟล์" ใน Pi Config → redeploy → 200 |
| VERCEL_* | system env ที่ Vercel ตั้งให้ทุก deployment (VERCEL_URL, VERCEL_ENV, …) ทับแล้วข้อมูลของ deployment ผิด |
| PI_CONFIG_TOKEN, PI_CONFIG_URL | ตัวที่ใช้ดึงค่าเอง — วนลูป |
pi-config pull เตือนใน build log เมื่อเจอคีย์เหล่านี้ที่ยังไม่ได้ติ๊ก (CLI ≥ 0.10.1) และหน้าเว็บเตือนตอนเพิ่มคีย์
คำสั่ง
| คำสั่ง | ทำอะไร |
|---|---|
| pi-config pull | เขียนค่าลง .env.local เฉพาะช่วงที่ pi-config ดูแล (บันทึก local cache เข้ารหัสเสมอ) |
| pi-config pull --offline | ดึงค่าจาก local cache โดยไม่ออกเน็ต (หรือตั้ง PI_CONFIG_OFFLINE=1) |
| pi-config pull --check | ไม่เขียนไฟล์ ออก exit 1 ถ้าไฟล์ไม่ตรงกับค่าล่าสุด (ใช้ใน CI) |
| pi-config pull --print | พิมพ์ออก stdout แทนการเขียนไฟล์ — มีค่าความลับอยู่ในนั้น |
| pi-config pull --replace | เขียนทับทั้งไฟล์เมื่อไฟล์เดิมไม่มี marker (สำรองเป็น .bak ให้ก่อน) |
| pi-config pull --file <path> | เปลี่ยนไฟล์ปลายทาง (หรือ --file=<path>) |
| pi-config pull --cache-path <p> | กำหนดที่อยู่ไฟล์ local cache (ค่าเริ่มต้น .next/cache/pi-config/cache.bin) |
| pi-config pull --replace --no-backup | เหมือน --replace แต่ไม่สำรอง .bak (ไฟล์เดิมหายถาวร) |
| pi-config init [--dry-run] | ตั้งค่าแอปใหม่ครั้งเดียว: .gitignore, PI_CONFIG_TOKEN=, build (ไม่ต้องมี token) |
| pi-config types | สร้าง pi-config-env.d.ts ให้ process.env.X มี type (มีเฉพาะชื่อคีย์) |
| pi-config run -- <คำสั่ง> | รันคำสั่งโดยใส่ค่าที่ pull ไว้ลง env (ดูหัวข้อ "รันคำสั่งที่ต้องใช้ค่า") |
| pi-config run --only production -- <คำสั่ง> | เหมือนบน แต่รันเฉพาะ build ของ production (VERCEL_ENV=production) |
| pi-config --help / --version | วิธีใช้ / รุ่นของ CLI |
| pi-config status | token นี้ผูกกับโปรเจกต์/environment ไหน มีกี่คีย์ เวอร์ชันอะไร (รองรับ --offline) |
| pi-config diff | บอก ชื่อ คีย์ที่ต่างจากไฟล์ (ไม่แสดงค่า, รองรับ --offline) |
exit code: 0 เรียบร้อย · 1 ไฟล์ไม่ตรงกับค่าล่าสุด · 2 ใช้คำสั่งผิดหรือไม่มี token · 3 เซิร์ฟเวอร์/เครือข่ายมีปัญหา
ตั้งค่าแอปใหม่ครั้งเดียว (pi-config init)
npx pi-config init --dry-run # ดูก่อนว่าจะแก้อะไร (ไม่เขียนอะไร)
npx pi-config initไม่ต้องมี token — ตั้งค่าก่อนออก token ได้ เติมเฉพาะของที่ขาด ไม่ทับของที่มี และรันซ้ำได้โดยไม่เปลี่ยนอะไร:
| ไฟล์ | ทำอะไร |
|---|---|
| .gitignore | เติม .env*.local, .env*.bak (กับดัก: --replace สำรองเป็น .env.local.bak ซึ่ง .gitignore มาตรฐานของ Next ไม่ครอบ), .next/cache/pi-config/, pi-config-env.d.ts — ถาม git check-ignore ก่อน ถ้า ignore อยู่แล้วด้วย pattern อื่น (เช่น .env* หรือที่ .gitignore ของรากใน monorepo) จะไม่เติมซ้ำ |
| .env.local | เพิ่ม PI_CONFIG_TOKEN= (ว่าง — วาง token ที่ออกจากหน้า environment) · มีอยู่แล้วไม่แตะ |
| package.json | ให้ build เรียก pi-config pull นำหน้า (แก้เฉพาะบรรทัดนั้น คงรูปแบบเดิมของไฟล์) · ถ้ามีแล้วหรือรูปแบบไม่ตรง จะบอกให้ทำเอง · ถ้า vercel.json มี buildCommand จะไม่แตะ (Vercel ไม่ใช้ scripts.build) — ดูว่า buildCommand เรียก pi-config อยู่ไหม รวมถึงผ่าน script เช่น bun run config:pull |
ไม่แตะ vercel.json (ไฟล์ตั้งค่า Vercel) — พิมพ์สิ่งที่ต้องทำเองที่ Vercel แทน (ตั้ง PI_CONFIG_TOKEN, ลบ env เดิม, ใส่ pi-config pull ใน buildCommand ถ้ายังไม่มี)
ให้ process.env.X มี type (pi-config types)
npx pi-config typesสร้าง pi-config-env.d.ts จากชื่อคีย์ของ environment ที่ token ผูกอยู่ — process.env.DATABASE_URL เป็น string (ไม่ใช่ string | undefined)
และพิมพ์ชื่อผิด (DATABSE_URL) แล้ว typecheck ล้ม แทนที่จะเป็น undefined ตอนรัน
- มีเฉพาะชื่อคีย์ ไม่มีค่า (แม้เป็นความลับ) และไม่เอาหมายเหตุของคีย์ไป
- ไม่ commit — ใส่ไว้ใน
.gitignore(pi-config initเติมให้): ไฟล์มาจาก environment ที่ token ในเครื่องนั้น ผูกอยู่ (มักเป็น development) แต่ละเครื่อง/แต่ละคนจึงได้ไม่เหมือนกัน commit แล้ว diff ไม่หยุด · ไม่มีไฟล์นี้ typecheck ยังผ่าน (แค่ไม่ได้ตัวช่วยจับชื่อผิด) — ทุกแอปของเราทำแบบนี้ตั้งแต่ 2026-10-23 - ทำให้คีย์ในรายการเป็น
stringไม่ optional → เทสต์ที่delete process.env.Xจะฟ้อง TS2790 ให้ใช้Reflect.deleteProperty(process.env, "X") - ครอบทุกคีย์ที่แอปต้องมี: ที่ pull ลงไฟล์ + คีย์ที่ตั้งเองที่ deployment (
externalKeys) + คีย์ที่แพลตฟอร์มฉีดให้ (unsyncedKeys) - เพิ่ม/ลบคีย์ใน Pi Config แล้วรันใหม่ ·
--file <path>เปลี่ยนที่อยู่ไฟล์ · ต้องมีpi-config-env.d.tsอยู่ในincludeของtsconfig.json
รันคำสั่งที่ต้องใช้ค่า (prisma migrate ฯลฯ)
prisma, drizzle-kit และสคริปต์อื่นไม่รู้จักไฟล์ .env.local ที่ pull เขียน — ไม่ต้องเขียนตัวอ่านเอง ใช้ run:
{
"scripts": {
"build": "pi-config pull && pi-config run --only production -- prisma migrate deploy && next build",
"db:migrate": "pi-config run -- prisma migrate deploy"
}
}prisma.config.ts เหลือแค่ url: process.env.DATABASE_URL ไม่ต้อง dotenv ไม่ต้องอ่านไฟล์เอง
- ค่าตรงทุกไบต์: ใช้ตัวอ่านเดียวกับที่เขียนไฟล์ — ค่าที่มี
$,${…}, หลายบรรทัด, เครื่องหมายคำพูด, ภาษาไทย ถึงคำสั่งลูกครบ (dotenvไม่แปลง\$กลับเป็น$จึงทำให้รหัสผ่านที่มี$ผิดเงียบ ๆ) - env ที่ตั้งไว้แล้วชนะ: ค่าที่ Vercel/CI ตั้งเองไม่ถูกทับ ·
runไม่ยิงเครือข่าย (ต้องpullมาก่อน — ไม่พบค่าที่ pull = exit 2) --only production: migrate ตอน build ของ branch ที่ยังไม่ merge (preview) ลงฐานที่ preview ชี้ — มักเป็นฐาน production จึงรันเฉพาะVERCEL_ENV=production· preview/development ข้ามพร้อมบอกใน log · อยู่บน Vercel แต่ไม่มีVERCEL_ENV= หยุด (exit 2) ไม่ใช่ข้าม เพราะถ้า Vercel เปลี่ยนชื่อตัวแปร production จะข้าม migrate โดยไม่มีใครรู้- ไม่ผ่าน shell: argument ไม่ถูกตีความ · exit code ของคำสั่งลูกถูกส่งกลับ · Windows ที่เรียก
.cmd(pnpm/npm/prisma) argument ที่มี%ถูกปฏิเสธ
การรับมือตอน Pi Config ล่ม / ใช้งานออฟไลน์
CLI จะบันทึก cache เข้ารหัส (AES-256-GCM ผูกกับ sha256(PI_CONFIG_TOKEN)) ไว้ที่ .next/cache/pi-config/cache.bin ทุกครั้งที่ pull สำเร็จ (Vercel build cache จะเก็บโฟลเดอร์นี้ไว้ข้าม build ให้เอง):
PI_CONFIG_STALE_CACHE=1: หาก Pi Config ล่มหรือเน็ตมีปัญหาตอน build (502/503/504 หรือเครือข่าย) จะ fallback ไปใช้ค่าจาก local cache อัตโนมัติ พร้อมเตือนชัดเจนใน build logPI_CONFIG_OFFLINE=1หรือpi-config pull --offline: ทำงานออฟไลน์จาก cache โดยไม่ส่ง request ออกเน็ตเลย
ช่วงที่ pi-config ดูแล
เขียนเฉพาะระหว่าง marker — บรรทัดอื่นในไฟล์ไม่ถูกแตะ
PI_CONFIG_TOKEN='ของคุณเอง' ← ไม่ถูกแตะ
# >>> pi-config pull — do not edit (project=ui env=development v=sha256:1f3a… at=2026-09-24T…)
APP_URL='http://localhost:3200'
DATABASE_URL='postgres://u:p\$ss@host/db'
# <<< pi-config pull
# บรรทัดของคุณท้ายไฟล์ ← ไม่ถูกแตะถ้าไฟล์มีแค่ PI_CONFIG_TOKEN / PI_CONFIG_URL (และ comment) จะต่อท้ายให้เลย
ถ้าไฟล์มีคีย์อื่นอยู่แล้วแต่ยังไม่มี marker คำสั่งจะปฏิเสธ จนกว่าจะใส่ --replace (สำรอง .bak ให้ก่อน)
ค่าที่เหมือนเดิมทั้งชุดจะไม่ถูกเขียนซ้ำ ไฟล์จึงไม่เปลี่ยนทุก build
เรื่องที่ระวังให้แล้ว
$ในค่า —@next/envรัน dotenvexpand()ซึ่งจะแทน$VARด้วยค่าจากprocess.envตัว render จึง escape$ให้เสมอ\ท้ายค่า /"ในค่า — เลือกเครื่องหมายคำพูดตามเนื้อค่า และปฏิเสธค่าที่เขียนแล้วอ่านกลับไม่ตรงตั้งแต่ตอนบันทึกในเว็บ.env.production.local— ถ้ามีไฟล์นี้อยู่ Next จะอ่านก่อน.env.localตอน build production → CLI เตือนให้- ไฟล์ไม่ถูก gitignore → เตือน
- เขียนไฟล์ชั่วคราวแล้ว rename ไฟล์ปลายทางจึงไม่เหลือครึ่งเดียวถ้าดับกลางคัน
- ไม่มีคำสั่งไหนพิมพ์ "ค่า" ออกมา ยกเว้น
--printที่สั่งเอง
ใช้เป็นไลบรารี
import { fetchConfig, fetchWhoami } from "@suphark/config-client";
const result = await fetchConfig({ token, url });
if (result.status === 200) {
console.log(Object.keys(result.data.values), result.data.version);
}