Lompat ke konten
dokumentasi.id
Commit bd74801
v2
Star10 rb

11

CLI `ua-parser-js`

script/cli.js adalah satu-satunya bin di repository ini: package.json:242 mendaftarkannya sebagai ./script/cli.js, dan berkas itu diawali shebang #!/usr/bin/env node (script/cli.js:1). Karena itu npx ua-parser-js ... langsung mengeksekusi berkas tersebut. CLI hanya memakai modul bawaan Node — node:fs, node:path, node:perf_hooks, node:readline, node:util — ditambah ../src/main/ua-parser dan ../src/extensions/ua-parser-extensions (script/cli.js:4-10). Seluruh program berada di dalam satu blok try; setiap exception dicetak apa adanya lewat console.error(err) lalu diakhiri process.exit(1) (script/cli.js:91-94).

Tiga cabang pemanggilan

Cabang ditentukan oleh argumen pertama, yaitu process.argv[2] (script/cli.js:12-18).

graph TD
    ARGV["process.argv[2]"] --> CHECK{"diawali '-'?"}
    CHECK -->|tidak| STR["argv.slice(2).map: UAParser(ua, [Bots, Emails, ExtraDevices, InApps, Vehicles])"]
    STR --> PRINT["console.log(JSON.stringify(results, null, 4))"]
    CHECK -->|ya| HELP{"-h atau --help?"}
    HELP -->|ya| USAGE["console.log usage, exit 0"]
    HELP -->|tidak| OPTS["parseArgs: input-file (-i), output-file (-o)"]
    OPTS --> NOIN{"inputFile terisi?"}
    NOIN -->|tidak| ERR1["console.error('Input file must be present')"]
    NOIN -->|ya| RESOLVE["path.resolve(__dirname, inputFile)"]
    RESOLVE --> EXISTS{"fs.existsSync(inputPath)?"}
    EXISTS -->|tidak| ERR2["console.error('Input file not found: ...')"]
    EXISTS -->|ya| STREAM["fs.createReadStream + readline.createInterface"]
    STREAM --> LOOP["setUA(line).getResult() per baris"]
    LOOP --> OUT["createWriteStream atau process.stdout"]
    style STR fill:#2d7d46,color:#fff
    style PRINT fill:#2d7d46,color:#fff
    style USAGE fill:#f59e0b,color:#000
    style OPTS fill:#f59e0b,color:#000
    style LOOP fill:#8b5cf6,color:#fff
    style STREAM fill:#336791,color:#fff
    style OUT fill:#336791,color:#fff
    style ERR1 fill:#dc2626,color:#fff
    style ERR2 fill:#dc2626,color:#fff
%% fig: Percabangan mode pada script/cli.js
%% desc: Argumen pertama memilih mode string, mode usage, atau mode berkas; mode berkas berakhir di pembacaan per baris atau di dua pesan error.
Fig. 1: Percabangan mode pada script/cli.jsArgumen pertama memilih mode string, mode usage, atau mode berkas; mode berkas berakhir di pembacaan per baris atau di dua pesan error.
Nilai process.argv[2]JalurRingkas
bukan string berawalan -mode stringsetiap argumen diparse, array hasil dicetak (script/cli.js:14-16)
-h atau --helpusageempat baris teks usage, lalu exit 0 (script/cli.js:20-24)
opsi lainmode berkasparseArgs membaca --input-file dan --output-file (script/cli.js:28-39)

Mode string: satu perintah untuk banyak UA

Setiap argumen sejak indeks 2 dipetakan dengan UAParser(ua, [Bots, Emails, ExtraDevices, InApps, Vehicles]), hasilnya dicetak oleh console.log(JSON.stringify(results, null, 4)), lalu proses keluar dengan kode 0 (script/cli.js:14-16). Keluaran selalu berupa array, bahkan ketika hanya satu user-agent yang diberikan. Karena UAParser(...) di sini dipanggil sebagai fungsi tanpa new, ia mengembalikan objek hasil, bukan instance parser; bentuk objek hasilnya dibahas di API Utama dan Bentuk Hasil.

Lima extension yang dipakai CLI adalah Bots, Emails, ExtraDevices, InApps, dan Vehicles — tiga di antaranya ikut diekspor dari module.exports bersama CLIs, Crawlers, Fetchers, Libraries, dan MediaPlayers (src/extensions/ua-parser-extensions.js:505-516). Isi tiap extension dibahas di Extensions: Bots, Crawlers, dan Lainnya.

Mode --input-file dan --output-file

Mode ini membaca berkas baris demi baris, misalnya npx ua-parser-js --input-file=log.txt --output-file=result.json seperti yang didokumentasikan di CHANGELOG.md:26-31.

sequenceDiagram
    autonumber
    participant CLI as script/cli.js
    participant OPT as parseArgs
    participant IN as input stream + readline
    participant UAP as UAParser instance
    participant OUT as outputStream
    CLI->>OPT: "parseArgs({ 'input-file', 'output-file' })"
    OPT-->>CLI: "inputFile, outputFile"
    CLI->>CLI: "cek inputFile dan fs.existsSync(inputPath)"
    CLI->>IN: "fs.createReadStream + readline.createInterface"
    CLI->>OUT: "write('[' + baris baru)"
    loop setiap event 'line'
        IN-->>UAP: "setUA(line)"
        UAP-->>OUT: "write(JSON.stringify(result, null, 4))"
    end
    IN-->>CLI: event 'close'
    CLI->>OUT: "write(baris baru + ']')"
    CLI-->>CLI: "outputStream.end() atau process.exit(0)"
%% fig: Siklus satu pemanggilan CLI dengan --input-file
%% desc: readline mengirim setiap baris ke satu instance UAParser, hasil tiap baris ditulis ke outputStream, dan array JSON ditutup saat event 'close'.
Fig. 2: Siklus satu pemanggilan CLI dengan --input-filereadline mengirim setiap baris ke satu instance UAParser, hasil tiap baris ditulis ke outputStream, dan array JSON ditutup saat event 'close'.
OpsiShorttypeVariabelPerilaku
--input-file-istringinputFilewajib; path di-resolve dengan path.resolve(__dirname, inputFile) (script/cli.js:46-47)
--output-file-ostringoutputFileopsional; tanpa opsi ini outputStream menjadi process.stdout (script/cli.js:60)

parseArgs dipanggil tanpa menyetel strict maupun allowPositionals (script/cli.js:34-39), jadi validasi opsi mengikuti default Node. Apa pun yang dilempar parseArgs ditangkap blok catch terluar (script/cli.js:91-94).

Berkas input dibuka lewat fs.createReadStream(inputPath, 'utf8') lalu dibungkus readline.createInterface({ input, crlfDelay: Infinity }) (script/cli.js:54-58); dengan crlfDelay: Infinity pasangan \r\n tidak dipecah menjadi dua baris.

Parser dibuat sekali di luar handler: const uap = new UAParser([Bots, Emails, ExtraDevices, InApps, Vehicles]) beserta penghitung lineNumber (script/cli.js:62-63). Melewatkan array sebagai argumen pertama memang dikenali constructor sebagai daftar extensions (src/main/ua-parser.js:1424-1434). Setiap event line memanggil uap.setUA(line).getResult() (script/cli.js:67-73), dan setUA memotong user-agent dengan trim(ua, UA_MAX_LENGTH) (src/main/ua-parser.js:1501-1504) yang bernilai 500 (src/main/ua-parser.js:23).

Format keluaran dan log ringkasan

Array JSON ditulis bertahap, bukan disusun di memori: write('[\n') membuka array, entri pertama tanpa awalan, entri berikutnya diawali ,\n, dan event close menutup dengan write('\n]') (script/cli.js:65-73 dan script/cli.js:75-76). Setiap event line diperlakukan sama, sehingga baris kosong pun menghasilkan satu entri hasil parse. Objek per baris dicetak dengan indentasi 4 spasi (script/cli.js:69).

Bila --output-file dipakai, outputStream.end() memanggil callback yang mencetak empat baris: Done!, Number of lines found: <n>, Task finished in: <x>ms, dan Output written to: <path> (script/cli.js:77-85). Durasi memakai performance.now() yang diambil sebelum parseArgs dan dibulatkan toFixed(3) (script/cli.js:28-39). Tanpa --output-file, hasil langsung ke stdout, log ringkasan dilewati, dan proses memanggil process.exit(0) di handler close (script/cli.js:86-88).

Pesan error dan exit code

SituasiKeluaranExit
inputFile tidak terisiInput file must be present (script/cli.js:41-44)1
fs.existsSync(inputPath) bernilai falseInput file not found: <inputPath> (script/cli.js:49-52)1
exception di mana pun dalam blok tryobjek err dicetak apa adanya (script/cli.js:91-94)1
mode string atau mode usage selesaiarray hasil atau teks usage (script/cli.js:14-16 dan script/cli.js:20-24)0

⚠️ path.resolve(__dirname, inputFile) (script/cli.js:46-47) me-resolve path relatif terhadap direktori script/, bukan process.cwd(). Berikan path absolut bila berkas log berada di direktori kerja saat ini.

⚠️ Memanggil CLI tanpa argumen membuat process.argv[2] bernilai undefined, sehingga process.argv[2].startsWith('-') melempar TypeError (script/cli.js:12-18). TypeError itu ditangkap blok catch dan dicetak sebagai error, bukan sebagai teks usage.

⚠️ Handler line memanggil outputStream.write tanpa memeriksa nilai balik atau menunggu event drain (script/cli.js:67-73). Untuk berkas input yang sangat besar, buffer tulis dapat menumpuk di memori karena tidak ada penanganan backpressure.