11
CLI `ua-parser-js`
script/cli.js adalah satu-satunya bin di repository ini: package.json:2421 mendaftarkannya sebagai ./script/cli.js, dan berkas itu diawali shebang #!/usr/bin/env node (script/cli.js:12). 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-103). 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-944).
Tiga cabang pemanggilan
Cabang ditentukan oleh argumen pertama, yaitu process.argv[2] (script/cli.js:12-185).
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.Nilai process.argv[2] | Jalur | Ringkas |
|---|---|---|
bukan string berawalan - | mode string | setiap argumen diparse, array hasil dicetak (script/cli.js:14-166) |
-h atau --help | usage | empat baris teks usage, lalu exit 0 (script/cli.js:20-247) |
| opsi lain | mode berkas | parseArgs membaca --input-file dan --output-file (script/cli.js:28-398) |
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-166). 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-5169). 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-3110.
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'.| Opsi | Short | type | Variabel | Perilaku |
|---|---|---|---|---|
--input-file | -i | string | inputFile | wajib; path di-resolve dengan path.resolve(__dirname, inputFile) (script/cli.js:46-4711) |
--output-file | -o | string | outputFile | opsional; tanpa opsi ini outputStream menjadi process.stdout (script/cli.js:6012) |
parseArgs dipanggil tanpa menyetel strict maupun allowPositionals (script/cli.js:34-3913), jadi validasi opsi mengikuti default Node. Apa pun yang dilempar parseArgs ditangkap blok catch terluar (script/cli.js:91-944).
Berkas input dibuka lewat fs.createReadStream(inputPath, 'utf8') lalu dibungkus readline.createInterface({ input, crlfDelay: Infinity }) (script/cli.js:54-5814); 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-6315). Melewatkan array sebagai argumen pertama memang dikenali constructor sebagai daftar extensions (src/main/ua-parser.js:1424-143416). 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-7320 dan script/cli.js:75-7621). 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:6922).
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-8523). Durasi memakai performance.now() yang diambil sebelum parseArgs dan dibulatkan toFixed(3) (script/cli.js:28-398). Tanpa --output-file, hasil langsung ke stdout, log ringkasan dilewati, dan proses memanggil process.exit(0) di handler close (script/cli.js:86-8824).
Pesan error dan exit code
| Situasi | Keluaran | Exit |
|---|---|---|
inputFile tidak terisi | Input file must be present (script/cli.js:41-4425) | 1 |
fs.existsSync(inputPath) bernilai false | Input file not found: <inputPath> (script/cli.js:49-5226) | 1 |
exception di mana pun dalam blok try | objek err dicetak apa adanya (script/cli.js:91-944) | 1 |
| mode string atau mode usage selesai | array hasil atau teks usage (script/cli.js:14-166 dan script/cli.js:20-247) | 0 |
⚠️
path.resolve(__dirname, inputFile)(script/cli.js:46-4711) me-resolve path relatif terhadap direktoriscript/, bukanprocess.cwd(). Berikan path absolut bila berkas log berada di direktori kerja saat ini.
⚠️ Memanggil CLI tanpa argumen membuat
process.argv[2]bernilaiundefined, sehinggaprocess.argv[2].startsWith('-')melempar TypeError (script/cli.js:12-185). TypeError itu ditangkap blokcatchdan dicetak sebagai error, bukan sebagai teks usage.
⚠️ Handler
linememanggiloutputStream.writetanpa memeriksa nilai balik atau menunggu eventdrain(script/cli.js:67-7317). Untuk berkas input yang sangat besar, buffer tulis dapat menumpuk di memori karena tidak ada penanganan backpressure.