IlmHamroh
JavaScript Full-stack/24-qism. Node.js52/55-dars17 daqiqa
Mundarija (31)

Interaktiv CLI Node.js'da: savollar, ranglar va spinner (clack, inquirer, ora, styleText)

Qisqacha: Terminaldagi rang, savol va spinner — maxsus belgilar ketma-ketligi (ANSI kodlari), ular faqat odam qarab turgan terminalda (TTY) ma'noga ega. Node 24 da rang uchun util.styleText yetarli — u quvur va NO_COLOR ni o'zi tekshiradi. Savol uchun @clack/prompts yoki @inquirer/prompts, spinner uchun ora bor. Bizning o'lchovda ular sovuq startga 32–87 ms qo'shdi. Quvurda esa yomon ishladi: clack osilib qoldi, inquirer stdout'ni boshqaruv kodlari bilan to'ldirdi. valyuta o'z readline rejimini va 20 qatorlik ko'rsatkichni ishlatadi — u faqat TTY'da chiqadi, quvurda jim.

Bu darsda

  • ANSI kodlari nima ekanini va \r, \x1b[K bilan bitta qatorni qanday "jonlantirish"ni tushunasiz.
  • util.styleText bilan rang berasiz, NO_COLOR, FORCE_COLOR va validateStream qanday ishlashini bilasiz.
  • clack, inquirer, ora va picocolors'ni o'lchov bilan taqqoslaysiz va quvurdagi xulqini ko'rasiz.
  • TTY bo'lmaganda (quvur, CI) CLI qanday o'zini tutishi kerakligini qoida sifatida yozasiz.
  • valyuta ga bog'liqliksiz jarayon ko'rsatkichini qo'shasiz (holat 52).

Oldin bilishingiz kerak: readline va terminal, Node'da CLI dastur anatomiyasi, Commander bilan buyruqlar tizimi, util moduli.

1. Nega bu kerak?

valyuta tarix USD --dan 2026-01-01 — 296 kun, har kun alohida so'rov. Tarmoq sekin bo'lsa, bu bir necha daqiqa. Shu vaqt ekran qora va jim. Foydalanuvchi o'ylaydi: "osilib qoldimi?" — va Ctrl+C bosadi.

Yaxshi CLI odamga jarayonni ko'rsatadi: ⠙ 12/296 2026-01-12. Muhim narsani rang bilan ajratadi: kurs tushdi — yashil, oshdi — qizil. Kerak bo'lsa savol beradi.

Lekin xuddi shu CLI skriptda ham ishlaydi: valyuta tarix … > usd.csv. U yerda spinner — faylni buzadigan axlat, savol — abadiy kutish. Bu darsning asosiy g'oyasi shu ikki dunyoni ajratish.

O'xshatish: restorandagi ofitsiant. Mehmon stolda o'tirgan bo'lsa — salomlashadi, menyuni tushuntiradi, "yana nima?" deb so'raydi. Telefon orqali buyurtma bergan yetkazish xizmatiga esa faqat chek yuboriladi — savolsiz, bezaksiz. Bir xil oshxona, ikki xil muomala.

2. Terminal qanday "chizadi": ANSI kodlari

2.1 Maxsus ketma-ketliklar

Terminal faqat matn oladi. Rang va kursor harakati — matn ichidagi maxsus buyruqlar. Ular ESC belgisi (kodi 27, JS'da \x1b) bilan boshlanadi:

Ketma-ketlik Ma'nosi
\x1b[31m bundan keyin — qizil
\x1b[39m rangni sukutga qaytarish
\x1b[1m qalin
\x1b[K kursordan qator oxirigacha tozalash
\r kursorni qator boshiga (yangi qatorsiz)

Bularni ANSI escape kodlari deyiladi. Terminal ularni ko'rsatmaydi, bajaradi. Fayl yoki quvur esa ularni oddiy belgilar sifatida saqlaydi — shuning uchun rangli chiqishni faylga yozsangiz, [31m kabi "axlat" ko'rasiz.

2.2 Bitta qatorni jonlantirish

Spinner sirining hammasi \r da. Yangi qator (\n) o'rniga \r yozilsa, keyingi matn eskisining ustiga tushadi. \x1b[K esa eski matnning uzunroq qoldig'ini o'chiradi:

sequenceDiagram
  participant D as Dastur
  participant T as Terminal qatori
  D->>T: "\r⠙ 1/3 2026-10-21\x1b[K"
  Note over T: ⠙ 1/3 2026-10-21
  D->>T: "\r⠹ 2/3 2026-10-22\x1b[K"
  Note over T: ⠹ 2/3 2026-10-22
  D->>T: "\r\x1b[K"
  Note over T: (bo'sh qator)

Nimaga qarang: qator bitta, unga uch marta yozildi. Oxirgi yozuv qatorni tozalaydi — natija chiqqanda spinner izi qolmaydi.

Tekshirib ko'ring: \r bilan yozilgan spinner chiqishini faylga yo'naltirsangiz (2> log.txt), faylda nima bo'ladi?

Javob

Fayl "ustiga yozish"ni bilmaydi: unda barcha kadrlar ketma-ket, \r va \x1b[K belgilari bilan saqlanadi. Matn muharririda bu bitta uzun, axlatli qator bo'lib ko'rinadi. Shuning uchun spinner TTY bo'lmasa umuman yozilmasligi kerak.

3. Ranglar

3.1 util.styleText

Node 24 da rang uchun paket kerak emas. node:util dagi styleText(format, matn, sozlama):

js
import { styleText } from "node:util";

const tekshirib = styleText("red", "-62.36");
const majburan = styleText("red", "-62.36", {
  validateStream: false,
});
console.log(JSON.stringify(tekshirib));
console.log(JSON.stringify(majburan));

Konsolda:

text
"-62.36"
"\u001b[31m-62.36\u001b[39m"

JSON.stringify maxfiy belgilarni ko'rinadigan qiladi. Birinchi qatorda rang yo'q: bu blok quvurga yozildi (tekshiruvchi chiqishni ushlaydi), styleText esa sukut bo'yicha oqimni tekshiradi (validateStream: true) — TTY emas yoki NO_COLOR o'rnatilgan bo'lsa, rang qo'shmaydi. Ikkinchi qatorda tekshiruv o'chirilgan — kod har doim bor.

Biz yana ikki holatni sinadik (Node 24.21.0, quvur orqali):

  • NO_COLOR=1 — birinchi qator rangsiz (kutilgandek); validateStream: false esa NO_COLOR ga qaramaydi — kod bor.
  • FORCE_COLOR=1 — quvurda ham rang: "\u001b[31m\u001b[1m-62.36\u001b[22m\u001b[39m" (["red", "bold"] bilan). NO_COLOR va FORCE_COLOR birga berilsa, Node ogohlantiradi: Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.

NO_COLOR — umumiy kelishuv (no-color.org): o'zgaruvchi bo'lsa — rang yo'q. FORCE_COLOR — teskarisi, CI loglari rangli bo'lishi uchun. Yaxshi CLI ikkalasini hurmat qiladi; styleText buni o'zi qiladi.

valyuta dagi format.ts:

ts
function farqRangi(
  farq: number,
  matn: string,
  rang: boolean,
): string {
  if (!rang || farq === 0) return matn;
  // styleText o'zi ham TTY va NO_COLOR ni tekshiradi;
  // --rangsiz uchun alohida bayroq
  return styleText(farq > 0 ? "red" : "green", matn, {
    stream: process.stdout,
  });
}

stream: process.stdout — "aynan stdout'ni tekshir". Jadval stdout'ga ketadi: valyuta kurs > fayl.txt da fayl toza.

3.2 picocolors va chalk

styleText Node'ga yaqinda kelgan: hujjatga ko'ra u 20.12 / 21.7 da qo'shilgan, TTY va NO_COLOR ni 20.18 / 22.8 dan hisobga oladi, 22.13 da esa barqaror (stable) deb belgilangan. Undan oldin rang uchun paket kerak edi — shuning uchun ekotizimda rang kutubxonalari ko'p:

  • picocolors (1.1.1) — eng kichik: 1 paket, import +7.8 ms (kanon). pc.red("…"). PostCSS, Vite, Browserslist ishlatadi.
  • chalk (npm view bo'yicha 6.0.1, 2026-10-23; biz o'rnatmadik) — eng mashhur, zanjirli API: chalk.red.bold("…").

Yangi Node 24 loyihasida styleText yetarli. Bu kutubxonalarni eski kodda va Node 20 ni qo'llab-quvvatlashi kerak bo'lgan paketlarda uchratasiz.

Diqqat: Rangga ma'no yuklamang. Qizil-yashil farqini ko'rmaydigan odamlar ko'p (taxminan har 12 erkakdan biri — rang ko'rishning eng keng tarqalgan buzilishi), NO_COLOR da esa rang umuman yo'q. valyuta da farq ishorasi (-62.36) raqamning o'zida — rang faqat yordamchi.

4. Savollar: clack, inquirer va o'z readline

4.1 Uch yo'l

readline va terminal darsida valyuta i ni o'z readline ingiz bilan qurdingiz: taklif (valyuta> ), tarix, Tab bilan to'ldirish. Ekotizimda tayyor "chiroyli" savol kutubxonalari bor:

  • @inquirer/prompts (8.7.3) — eng eski va keng: input, select, checkbox, confirm, password. Har biri alohida funksiya.
  • @clack/prompts (1.8.1) — zamonaviy ko'rinish (chiziqlar, belgilar), intro, outro, spinner bilan birga. npm create vite va Svelte'ning sv CLI'si shu kutubxonada (npm view, 2026-10-23).

Ikkalasi bilan bitta savol:

js
import { text, isCancel } from "@clack/prompts";
const javob = await text({ message: "Qaysi valyuta?" });
console.log("javob:", isCancel(javob) ? "(bekor)" : javob);
js
import { input } from "@inquirer/prompts";
const javob = await input({ message: "Qaysi valyuta?" });
console.log("javob:", javob);

4.2 Biz topgan: quvurda nima bo'ladi

Haqiqiy terminalda (TTY) bu kutubxonalarni sinay olmadik — zavod muhitida terminal oynasi yo'q. Lekin quvur — skript va CI'ning muhiti — sinaldi. Ikkalasiga echo "EUR" | bilan javob berdik.

clack — osilib qoldi va xato bilan tugadi:

text
Warning: Detected unsettled top-level await at file:///…/clack.mjs:2
const javob = await text({ message: "Qaysi valyuta?" });
              ^
[exit 13]

Tarjimasi: "hal qilinmagan top-level await aniqlandi". clack harflarni o'qib chizdi (E, EU, EUR), lekin quvurdan kelgan qator oxirini "Enter" deb tanimadi. Kirish tugadi, Promise esa hech qachon hal bo'lmadi. Node buni sezib, maxsus exit 13 bilan to'xtatdi. Bo'sh kirish (< /dev/null) bilan ham aynan shu.

inquirer — javobni oldi (javob: EUR, exit 0), lekin stdout'ga boshqaruv kodlarini ham yozdi. od -c bilan qaraganimizda chiqish boshi:

text
? Qaysi valyuta?\033[18G\033[19G\033[2K\033[G? Qaysi valyuta? E…

\033 — o'sha ESC. Har harf uchun qator qayta chizilgan. > javob.txt qilsangiz, fayl shu axlat bilan to'ladi.

O'z readline rejimimiz (valyuta i, holat 23) quvurda toza ishladi — biz yakuniy holatda sinadik:

text
$ printf "100 usd eur\nkurs gbp\n1 XYZ\nchiqish\n" | valyuta i
Ogohlantirish: offline rejim — saqlangan namuna kurslar (fixtures/) ishlatildi
100.00 USD = 89.30 EUR
2026-10-23 holatiga (fixture)
GBP    1    15 916.89  -112.11  Angliya funt sterlingi
Xato: Noma'lum valyuta: XYZ
[exit 1]

Taklif yo'q, boshqaruv kodi yo'q, xato qatori bo'lgani uchun exit 1 — skript buni biladi. Sabab: interaktiv.ts isTTY ni tekshirib, quvurda terminal: false bilan ishlaydi.

Tekshirib ko'ring: CI'da (GitHub Actions) CLI'ingiz clack bilan "Davom etasizmi?" deb so'raydi. Nima bo'ladi va qanday tuzatasiz?

Javob

CI'da hech kim javob bermaydi va stdin TTY emas: biz ko'rgandek, savol hal bo'lmaydi va jarayon exit 13 bilan tugaydi yoki vaqt chegarasigacha osilib turadi. Tuzatish: savoldan oldin process.stdin.isTTY ni tekshiring. TTY bo'lmasa — savol bermang: bayroq (--yes) bilan javobni oling yoki aniq xabar va exit 2 bilan to'xtang.

5. Spinner va jarayon ko'rsatkichi

5.1 ora

ora (9.4.1) — eng mashhur spinner:

js
import ora from "ora";
const s = ora("Kurslar olinmoqda").start();
await new Promise((r) => setTimeout(r, 300));
s.succeed("Kurslar olindi");

ora stderr'ga yozadi (to'g'ri). TTY bo'lmasa animatsiyani o'chiradi. Biz stderr'ni faylga yo'naltirdik (2>err.txt) — fayl 49 bayt:

text
- Kurslar olinmoqda
[32m✔[39m Kurslar olindi

Animatsiya yo'q, lekin ikki qator va yashil rang kodi baribir faylga tushdi. Log faylda bu qatorlar ortiqcha. Yana ora 17 paketni olib keladi.

Ko'p qadamli jarayon uchun cli-progress (npm view bo'yicha 3.12.0; biz o'rnatmadik) — [████░░░░] 40% | 120/296 ko'rinishidagi chiziq.

5.2 O'lchov

Kanon har kutubxona importini alohida papkada o'lchadi (15 jarayon, mediana, bo'sh jarayonga nisbatan):

Interaktiv kutubxonalar importining narxi
  • picocolors 1.1.11 paket7,8 ms
  • @clack/prompts 1.8.16 paket32,4 ms
  • ora 9.4.117 paket80,9 ms
  • @inquirer/prompts 8.7.325 paket86,6 ms

Manba: IlmHamroh kanon o'lchovi t51, 2026-10-23: Node 24.21.0, Windows 11, i5-12500H; 15 jarayon mediana

valyuta --help ~95 ms. ora va inquirer qo'shilsa — ~260 ms: CLI sezilarli "og'irlashadi". Va bu narxni har chaqiruv to'laydi, hatto valyuta kurs USD > fayl — spinner hech qachon ko'rinmaydigan chaqiruv ham. Agar kutubxona kerak bo'lsa, uni faqat ishlatiladigan joyda dinamik import() bilan yuklash mumkin (ES Modules Node'da).

5.3 O'z ko'rsatkichimiz: 20 qator

valyuta uchun kerakli narsa kichik: kadr, hisoblagich, sana — faqat TTY'da. Kanon src/jarayon.ts:

ts
// Jarayon ko'rsatkichi (spinner + "12/34 kun") — faqat stderr TTY
// bo'lsa. Quvur/CI/faylda jim: stdout toza qoladi
const KADRLAR = ["⠋", "⠙", "⠹", "⠸", "⠼",
  "⠴", "⠦", "⠧", "⠇", "⠏"];

export interface Korsatkich {
  qadam: (izoh: string) => void;
  tugat: () => void;
}

export function korsatkich(
  oqim: { isTTY?: boolean; write: (s: string) => unknown },
  jami: number,
): Korsatkich {
  if (oqim.isTTY !== true) {
    return { qadam: () => undefined, tugat: () => undefined };
  }
  let n = 0;
  return {
    qadam: (izoh) => {
      n++;
      // \r — qator boshiga; \x1b[K — qatorning qolganini tozalash
      const kadr = KADRLAR[n % KADRLAR.length] ?? "";
      const son = `${String(n)}/${String(jami)}`;
      oqim.write(`\r${kadr} ${son} ${izoh}\x1b[K`);
    },
    tugat: () => {
      oqim.write("\r\x1b[K");
    },
  };
}

Kanondagi uzun qatorlarni telefon uchun bo'ldik (ma'no o'zgarmagan). E'tibor bering:

  • Oqim parametr. korsatkich(process.stderr, …) — haqiqiy chaqiruv; testda — soxta obyekt. node:test bilan sinash qulay.
  • TTY bo'lmasa — bo'sh funksiyalar. Chaqiruvchi tekshirib o'tirmaydi: k.qadam(...) har doim xavfsiz.
  • KADRLAR — Braille belgilari: bitta belgi joyida "aylanuvchi" nuqtalar.

Mantiqni JavaScript'da tekshiramiz — soxta oqim bilan:

js
const KADRLAR = ["⠋", "⠙", "⠹", "⠸", "⠼",
  "⠴", "⠦", "⠧", "⠇", "⠏"];

function korsatkich(oqim, jami) {
  if (oqim.isTTY !== true) return { qadam() {}, tugat() {} };
  let n = 0;
  return {
    qadam(izoh) {
      n++;
      const kadr = KADRLAR[n % KADRLAR.length];
      oqim.write(`\r${kadr} ${n}/${jami} ${izoh}\x1b[K`);
    },
    tugat() {
      oqim.write("\r\x1b[K");
    },
  };
}

const tty = [];
const k = korsatkich({ isTTY: true, write: (s) => tty.push(s) }, 3);
k.qadam("2026-10-21");
k.qadam("2026-10-22");
k.tugat();
console.log(tty.map((s) => JSON.stringify(s)).join("\n"));

const quvur = [];
korsatkich({ write: (s) => quvur.push(s) }, 3).qadam("x");
console.log("quvurda yozildi:", quvur.length);

Konsolda:

text
"\r⠙ 1/3 2026-10-21\u001b[K"
"\r⠹ 2/3 2026-10-22\u001b[K"
"\r\u001b[K"
quvurda yozildi: 0

Haqiqiy terminalda bu animatsiyani ko'rmadik (zavodda terminal oynasi yo'q) — buni o'zingiz valyuta tarix bilan tekshirasiz. Quvur esa sinaldi (52-korsatkich.txt): 2>fayl bilan stderr 0 bayt.

6. TTY bo'lmaganda: qoidalar

flowchart TD
  A["Chiqish oldidan"] --> B{"Oqim TTY?"}
  B -- "ha" --> C["Rang, spinner, savol — mumkin"]
  B -- "yo'q" --> D["Rangsiz, spinnersiz, savolsiz"]
  C --> E{"NO_COLOR?"}
  E -- "ha" --> F["Rangsiz"]
  D --> G{"Javob kerakmi?"}
  G -- "ha" --> H["Bayroq: --yes yoki xato, exit 2"]

Qoidalar ro'yxati:

  1. Har oqimni alohida tekshiring: stdout quvurda, stderr esa ekranda bo'lishi mumkin (valyuta kurs | grep USD — ogohlantirish ko'rinadi).
  2. Jarayon ko'rsatkichi — faqat stderr'ga va faqat stderr.isTTY da.
  3. Savol — faqat stdin.isTTY va stdout.isTTY da; aks holda bayroq yoki aniq xato.
  4. NO_COLOR — rang yo'q; FORCE_COLOR — rang bor (styleText buni biladi).
  5. Ko'p CI tizimlari CI=true o'zgaruvchisini o'rnatadi — ba'zi CLI'lar u bilan interaktivlikni o'chiradi (hujjatlar bo'yicha keng tarqalgan kelishuv).

7. CLI qadami: tarix uchun jarayon ko'rsatkichi (holat 52)

Commit: tarix uchun jarayon ko'rsatkichi (faqat TTY'da), bog'liqliksiz

Holat 52 uch darsni yakunlaydi (CLI anatomiyasi, Commander va shu dars). O'zgarishlar:

  1. Yangi src/jarayon.ts — yuqoridagi 20 qatorlik korsatkich.
  2. src/tarix.ts — tarixOqimi ga ixtiyoriy ilgak. Har kun boshida chaqiriladi:
diff
   oraliqMs?: number;
+  kunBoshida?: (sana: string) => void; // jarayon ko'rsatkichi uchun
 }
@@
   for (const sana of kunlar(s.dan, s.gacha)) {
+    s.kunBoshida?.(sana);
     const url = tarixUrl(asos, kod, sana);
  1. src/cli.ts — tarix buyrug'ida ko'rsatkich yaratiladi va finally da tozalanadi (xato bo'lsa ham qator tozalansin):
diff
-      const oqim = tarixOqimi({ kod, dan, gacha, kesh: papkalar.kesh, api: env.VALYUTA_API, offline, olish });
-      await oqimniYoz(matnga(oqim, format), { gzip: b.gzip, fayl: b.chiqish, signal });
+      const k = korsatkich(process.stderr, [...kunlar(dan, gacha)].length);
+      const oqim = tarixOqimi({ kod, dan, gacha, kesh: papkalar.kesh, api: env.VALYUTA_API, offline, olish, kunBoshida: k.qadam });
+      try {
+        await oqimniYoz(matnga(oqim, format), { gzip: b.gzip, fayl: b.chiqish, signal });
+      } finally {
+        k.tugat();
+      }

Kanon qatorlari uzun (K3) — ular aynan iqtibos. [...kunlar(dan, gacha)].length — generatorni massivga yoyib, kunlar sonini oladi.

  1. Test (test/tarix.test.ts, +1): TTY'da 2 ta yozuv ("\r⠙ 1/3 2026-10-21\u001b[K" aynan), TTY bo'lmasa — 0.

Tekshiruv (tekshiruv-52.txt, Node 24.21.0): build/tip/lint 0, test 42/42; npm pack 59 fayl, 28.3 kB (jarayon.js 768 B); prod bog'liqlik 0; sovuq start --help 94.3 ms, kurs USD 102.5 ms. ora ishlatilganda shu joyga +81 ms bo'lardi.

bash
git add src/jarayon.ts src/tarix.ts src/cli.ts test/tarix.test.ts
git commit -m "tarix uchun jarayon ko'rsatkichi (faqat TTY'da), bog'liqliksiz"

8. Ko'p uchraydigan xatolar

8.1 Spinner stdout'ga

valyuta tarix … > usd.csv — CSV ichida ⠙ 12/296 qatorlari. Spinner — xabar, natija emas: faqat stderr.

8.2 exit 13 va "unsettled top-level await"

text
Warning: Detected unsettled top-level await

Promise hech qachon hal bo'lmadi va event loop'da boshqa ish qolmadi — ko'pincha javob kutayotgan savol quvurda. Savoldan oldin isTTY tekshiruvi.

8.3 Faylda [31m axlati

Rang kodi tekshiruvsiz yozilgan (validateStream: false, FORCE_COLOR, yoki qo'lda \x1b[31m). styleText ni sukut sozlamasida qoldiring.

8.4 Xatoda spinner qatori qoldi

Xato tashlandi, tugat() chaqirilmadi — xato xabari spinner qoldig'i bilan bir qatorda. finally da tozalang (holat 52 dagidek).

8.5 Juda tez yangilash

Har millisekundda write — terminal miltillaydi va CPU sarflanadi. Har qadamda yoki ~80–100 ms da bir marta yangilang.

9. Mashqlar

Mashqlar kurs/mashqlar/24/52-interaktiv/ da.

1-mashq (oson): bo'sh joylarni to'ldiring

  • Qator boshiga qaytish belgisi: \
  • Rangni o'chirish uchun umumiy muhit o'zgaruvchisi:
  • Node 24 dagi rang funksiyasi: util.
  • Quvurda clack savoli bilan tugagan exit kod:
  • valyuta ko'rsatkichi qaysi oqimga yozadi:
Yechim
  • r — \r, yangi qatorsiz qator boshi.
  • NO_COLOR — no-color.org kelishuvi.
  • styleText — validateStream sukut bo'yicha yoqiq.
  • 13 — hal bo'lmagan top-level await.
  • stderr — natija (stdout) toza qolishi uchun.

2-mashq (o'rta): rang qarori

rangKerakmi({ tty, env, bayroq }) funksiyasini yozing — u true/false qaytarsin. Qoidalar tartibi: bayroq === "--rangsiz" → yo'q; env.FORCE_COLOR bo'lsa (va "0" emas) → ha; env.NO_COLOR bo'lsa (bo'sh satr emas) → yo'q; aks holda tty. Besh holatni jadval qilib chiqaring. Yordam: obyektdan destructuring (Destructuring).

Yechim
js
function rangKerakmi({ tty, env = {}, bayroq }) {
  if (bayroq === "--rangsiz") return false;
  if (env.FORCE_COLOR && env.FORCE_COLOR !== "0") return true;
  if (env.NO_COLOR) return false;
  return tty === true;
}

const holatlar = [
  ["terminal", { tty: true }],
  ["quvur", { tty: false }],
  ["NO_COLOR", { tty: true, env: { NO_COLOR: "1" } }],
  ["CI + FORCE", { tty: false, env: { FORCE_COLOR: "1" } }],
  ["--rangsiz", { tty: true, bayroq: "--rangsiz" }],
];
for (const [nom, h] of holatlar) {
  console.log(`${nom.padEnd(11)} ${rangKerakmi(h) ? "rang" : "-"}`);
}

Konsolda:

text
terminal    rang
quvur       -
NO_COLOR    -
CI + FORCE  rang
--rangsiz   -

Tartib muhim. Aniq bayroq eng kuchli: foydalanuvchi aynan shu chaqiruv uchun aytdi. Keyin muhit o'zgaruvchilari, oxirida avtomatik aniqlash — CLI anatomiyasi darsidagi sozlash ustuvorligi bilan bir xil. no-color.org bo'yicha NO_COLOR faqat bo'sh bo'lmasa hisobga olinadi — if (env.NO_COLOR) bo'sh satrni o'tkazib yuboradi.

3-mashq (qiyin): progress chizig'i

chiziq(n, jami, en = 20) funksiyasini yozing: [########------------] 40% 8/20 ko'rinishida satr qaytarsin. To'lgan qism #, qolgani -, foiz butun son. n > jami bo'lsa — jami deb hisoblang, jami === 0 bo'lsa — 100%. Keyin soxta oqimga 0, 5, 10, 20 qiymatlarini \r bilan yozing va natijani JSON.stringify bilan chiqaring. Yordam: "#".repeat(k) (Satr metodlari), Math.round.

Yechim
js
function chiziq(n, jami, en = 20) {
  const ulush = jami === 0 ? 1 : Math.min(n, jami) / jami;
  const tola = Math.round(ulush * en);
  const foiz = Math.round(ulush * 100);
  const bar = "#".repeat(tola) + "-".repeat(en - tola);
  return `[${bar}] ${foiz}% ${Math.min(n, jami)}/${jami}`;
}

const yozuvlar = [];
const oqim = { write: (s) => yozuvlar.push(s) };
for (const n of [0, 5, 10, 20]) oqim.write(`\r${chiziq(n, 20, 10)}`);
console.log(yozuvlar.map((s) => JSON.stringify(s)).join("\n"));
console.log(chiziq(25, 20, 10));
console.log(chiziq(0, 0, 10));

Konsolda:

text
"\r[----------] 0% 0/20"
"\r[###-------] 25% 5/20"
"\r[#####-----] 50% 10/20"
"\r[##########] 100% 20/20"
[##########] 100% 20/20
[##########] 100% 0/0

jami === 0 holati — bo'sh oraliq (masalan --dan va --gacha bir xil dam olish kuni): nolga bo'lish o'rniga "tayyor" deymiz. Chiziqni toza funksiya qilib, yozishni alohida qilganimiz uchun uni jarayonsiz, oddiy blokda sinadik.

4-mashq: CLI qadami — holat 52

O'z valyuta ingizga «CLI qadami» bo'limidagi o'zgarishlarni kiriting: src/jarayon.ts, tarixOqimi ga kunBoshida, cli.ts da try/finally, testga bitta yangi test. npm run check ni ishga tushiring, keyin terminalda valyuta tarix USD --dan 2026-10-01 ni oching va ko'rsatkichni o'z ko'zingiz bilan ko'ring. So'ng 2>log.txt bilan qaytaring va log.txt hajmini tekshiring. Commit xabari — yuqoridagidek.

Yechim

Kutilgan natija: testlar 42 (+1), hammasi o'tadi; terminalda bitta qator o'zgarib turadi (⠙ 5/23 2026-10-05), natija chiqishidan oldin tozalanadi; log.txt — 0 bayt (agar ogohlantirish bo'lmasa; oflayn rejimda Ogohlantirish: qatori bo'ladi — u xabar, spinner emas). Agar faylda ⠙ belgilari bo'lsa — TTY tekshiruvi oqim.isTTY !== true ni qayta ko'ring: quvurda isTTY false emas, undefined (readline darsi). Ko'rsatkichni haqiqiy terminalda kanon sinamagan — sizning kuzatuvingiz bu yerda birinchi.

10. Real ishda

  • create- vositalari:* npm create vite@latest, npm create astro — savol-javobli o'rnatuvchilar (create-vite — clack'da), va --template react kabi bayroqlar bilan savolsiz ham ishlaydi — aynan bu darsdagi "TTY bo'lmasa bayroq" qoidasi.
  • CI loglari: GitHub Actions loglari rangni ko'rsatadi, lekin TTY emas — shuning uchun rangli log kerak bo'lsa, workflow'da FORCE_COLOR beriladi.
  • Kirish imkoniyati: ekran o'quvchi bilan ishlaydigan dasturchilar spinner animatsiyasidan qiynaladi — ko'p CLI'lar --quiet, --no-progress bayroqlarini beradi.
  • Intervyu: "CLI'da spinner nega stderr'ga?", "NO_COLOR nima?", "TTY'ni qanday aniqlaysiz?" — CLI va DevTools jamoalarida so'raladi.

Xulosa

  • Rang, kursor va spinner — ANSI kodlari; ular faqat TTY'da ma'noli, faylda — axlat.
  • util.styleText (Node 22.13 dan barqaror) quvur va NO_COLOR ni o'zi tekshiradi; validateStream: false va FORCE_COLOR majburlaydi.
  • clack quvurda osilib qoldi (exit 13), inquirer stdout'ga boshqaruv kodlarini yozdi, ora faylga rang kodini tushirdi; importlari +32…87 ms.
  • Qoida: savol va spinner — faqat TTY'da; aks holda bayroq yoki aniq xato.
  • valyuta holat 52: 20 qatorlik korsatkich (stderr, faqat TTY), finally da tozalash, test 42/42, bog'liqlik 0.

Keyingi dars: Skriptlash va avtomatlashtirish — bash o'rniga Node skriptlari: zx, execa, fs.glob, ommaviy qayta nomlash, zaxira va valyuta uchun nashrdan oldingi paket tekshiruvi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Interaktiv CLI Node.js'da: savollar, ranglar va spinner (clack, inquirer, ora, styleText) — IlmHamroh