nixparse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +480 -0
- package/README.md +480 -0
- package/dist/index.cjs +556 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +404 -0
- package/dist/index.d.ts +404 -0
- package/dist/index.js +525 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# nixparse
|
|
2
|
+
|
|
3
|
+
`ps`, `df`, `du`, `free`, `lsof`, `mount`, `who`, `uptime`, `ip`, `ss` といった古典的なUnixコマンドの出力を、[Zod](https://zod.dev)で実行時検証しながら型安全にパースするライブラリです。
|
|
4
|
+
|
|
5
|
+
[English README](./README.md)
|
|
6
|
+
|
|
7
|
+
これらのコマンドの多くは`docker`や`kubectl`のような`--json`オプションを持たず、出力は長らく場当たり的な正規表現でスクレイピングされてきました。`nixparse`はそれらすべてに対して、単一の・型付けされた・拡張可能なインターフェースを提供します。
|
|
8
|
+
|
|
9
|
+
- **実行時検証**: すべてのパース結果はZodスキーマで検証されるため、フォーマットが想定と異なる場合は黒魔術的に誤った値を返すのではなく`ZodError`が投げられます。
|
|
10
|
+
- **シェルを使わない=インジェクションのリスクがない**: `run()`は`child_process.execFile`を使い、シェルを経由しません。引数が文字列としてコマンドラインに展開されることはありません。
|
|
11
|
+
- **拡張可能**: `registerParser()`で任意のコマンド用パーサーを自分で追加登録できます。
|
|
12
|
+
- **ESM/CJS両対応**のビルドと完全な`.d.ts`型定義付き。
|
|
13
|
+
|
|
14
|
+
## 目次
|
|
15
|
+
|
|
16
|
+
- [インストール](#インストール)
|
|
17
|
+
- [クイックスタート](#クイックスタート)
|
|
18
|
+
- [基本概念](#基本概念)
|
|
19
|
+
- [APIリファレンス](#apiリファレンス)
|
|
20
|
+
- [`parse(name, raw)`](#parsename-raw)
|
|
21
|
+
- [`run(name, args?)`](#runname-args)
|
|
22
|
+
- [`registerParser(name, definition)`](#registerparsername-definition)
|
|
23
|
+
- [対応コマンド一覧(全フィールド解説)](#対応コマンド一覧全フィールド解説)
|
|
24
|
+
- [`ps`](#ps)
|
|
25
|
+
- [`df`](#df)
|
|
26
|
+
- [`du`](#du)
|
|
27
|
+
- [`free`](#free)
|
|
28
|
+
- [`lsof`](#lsof)
|
|
29
|
+
- [`mount`](#mount)
|
|
30
|
+
- [`who`](#who)
|
|
31
|
+
- [`uptime`](#uptime)
|
|
32
|
+
- [`ip-addr`](#ip-addr)
|
|
33
|
+
- [`ip-route`](#ip-route)
|
|
34
|
+
- [`ss`](#ss)
|
|
35
|
+
- [エラーハンドリング](#エラーハンドリング)
|
|
36
|
+
- [実用例](#実用例)
|
|
37
|
+
- [対応範囲・制限事項](#対応範囲制限事項)
|
|
38
|
+
- [開発](#開発)
|
|
39
|
+
- [ライセンス](#ライセンス)
|
|
40
|
+
|
|
41
|
+
## インストール
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npm install nixparse
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Node.js 18以上が必要です。コマンドはホストOS上で実際に実行されるため(Linux想定。詳細は[対応範囲・制限事項](#対応範囲制限事項)を参照)、対象のバイナリ(`ps`, `df`等)が事前にインストールされ`PATH`上にある必要があります。
|
|
48
|
+
|
|
49
|
+
## クイックスタート
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { run, type ProcessInfo } from "nixparse";
|
|
53
|
+
|
|
54
|
+
const processes = await run<ProcessInfo[]>("ps");
|
|
55
|
+
console.log(processes[0].pid, processes[0].command);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
これだけです。`run()`は内部で`ps aux`を実行し、標準出力をパースし、Zodスキーマで検証してから、完全に型付けされた配列を返します。
|
|
59
|
+
|
|
60
|
+
## 基本概念
|
|
61
|
+
|
|
62
|
+
nixparseでデータを取得する方法は2つあります。すでに生の出力を持っているか、nixparseに取得させたいかで選びます。
|
|
63
|
+
|
|
64
|
+
| | すでにテキストを持っている場合 | nixparseにコマンドを実行させたい場合 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| 関数 | [`parse(name, raw)`](#parsename-raw) | [`run(name, args?)`](#runname-args) |
|
|
67
|
+
| 用途 | 他所からパイプされた出力、エージェントが取得済みの出力、ログ/fixtureファイルから読んだ出力 | 通常のアプリケーションコード |
|
|
68
|
+
| 副作用 | なし(純粋関数) | 子プロセスを起動する |
|
|
69
|
+
|
|
70
|
+
どちらも同じパーサーレジストリを経由するため、返る型や検証の挙動は同一です。
|
|
71
|
+
|
|
72
|
+
## APIリファレンス
|
|
73
|
+
|
|
74
|
+
### `parse(name, raw)`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
function parse<T = unknown>(name: string, raw: string): T;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`name`に登録されたパーサーで生文字列`raw`をパースし、そのパーサーのZodスキーマで結果を検証して返します。
|
|
81
|
+
|
|
82
|
+
- `name` — パーサー名([対応コマンド一覧](#対応コマンド一覧全フィールド解説)を参照)。組み込みパーサーについては文字列リテラル型`BuiltinCommandName`によって補完が効きます。
|
|
83
|
+
- `raw` — 対応コマンドの標準出力そのもの(各パーサーがどの実行形式を想定しているかは「`run()`が実行するコマンド」列を参照)。
|
|
84
|
+
- `name`が未登録の場合は`UnknownParserError`を投げます。
|
|
85
|
+
- パース結果がスキーマと一致しない場合(例: 間違ったコマンドの出力を渡した場合)はZodの`ZodError`を投げます。
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { parse, type DiskUsage } from "nixparse";
|
|
89
|
+
import { execSync } from "node:child_process";
|
|
90
|
+
|
|
91
|
+
const raw = execSync("df -k").toString();
|
|
92
|
+
const disks = parse<DiskUsage[]>("df", raw);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `run(name, args?)`
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
function run<T = unknown>(
|
|
99
|
+
name: BuiltinCommandName,
|
|
100
|
+
args?: string[],
|
|
101
|
+
): Promise<T>;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**組み込み**パーサーに対応するバイナリを実行し、その出力を一度にパースします。組み込みの11コマンド名でのみ動作します(`registerParser`で追加したカスタムパーサーは実行できません — `run()`はそのパーサーがどのバイナリ・引数を必要とするかを知らないためです)。
|
|
105
|
+
|
|
106
|
+
- `name` — `"ps" | "df" | "du" | "free" | "lsof" | "mount" | "who" | "uptime" | "ip-addr" | "ip-route" | "ss"`のいずれか。
|
|
107
|
+
- `args` — 省略可能。パーサーのデフォルト引数を上書きします(例: `du`はパスが必須なので、ほぼ必ず指定することになります)。省略時は各パーサーのドキュメント化されたデフォルトが使われます(下表参照)。
|
|
108
|
+
- 内部的には`child_process.execFile(binary, args)`を使用しており、**シェルは一切起動されません**。そのため`args`にユーザー入力由来の文字列が含まれていてもコマンドインジェクションのリスクはありません。
|
|
109
|
+
- バイナリが`PATH`上にない場合は`CommandNotFoundError`を投げます。
|
|
110
|
+
- 出力がスキーマと一致しない場合(想定外のOS/出力フォーマット等)は`parse()`と同様にZodのエラーを投げます。
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { run, type DirSize } from "nixparse";
|
|
114
|
+
|
|
115
|
+
// デフォルト引数を上書き — duはパスの明示が必要
|
|
116
|
+
const sizes = await run<DirSize[]>("du", ["-k", "/var/log"]);
|
|
117
|
+
const biggest = sizes.sort((a, b) => b.sizeKb - a.sizeKb)[0];
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `registerParser(name, definition)`
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
function registerParser<T>(
|
|
124
|
+
name: string,
|
|
125
|
+
definition: { schema: ZodType<T>; parse: (raw: string) => unknown },
|
|
126
|
+
): void;
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
グローバルなレジストリにパーサーを追加(または上書き)し、すぐに`parse()`から使えるようにします。これがnixparseに同梱されていないコマンドへ対応を広げる方法です。
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { registerParser, parse } from "nixparse";
|
|
133
|
+
import { z } from "zod";
|
|
134
|
+
|
|
135
|
+
const UserSchema = z.object({ name: z.string(), shell: z.string() });
|
|
136
|
+
|
|
137
|
+
registerParser("getent-passwd", {
|
|
138
|
+
schema: z.array(UserSchema),
|
|
139
|
+
parse: (raw) =>
|
|
140
|
+
raw
|
|
141
|
+
.trim()
|
|
142
|
+
.split("\n")
|
|
143
|
+
.map((line) => {
|
|
144
|
+
const fields = line.split(":");
|
|
145
|
+
return { name: fields[0], shell: fields[6] };
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const users = parse("getent-passwd", rawGetentOutput);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
注意: `registerParser`は`parse()`から使えるようにするだけです。`run()`のような一発実行も欲しい場合は、自分でコマンドを実行して`parse()`を呼ぶ薄いラッパーを書いてください。
|
|
153
|
+
|
|
154
|
+
## 対応コマンド一覧(全フィールド解説)
|
|
155
|
+
|
|
156
|
+
すべての組み込みパーサーは**Linux(GNU coreutils / iproute2 / util-linux)**の出力を前提としています。他プラットフォームについては[対応範囲・制限事項](#対応範囲制限事項)を参照してください。
|
|
157
|
+
|
|
158
|
+
### `ps`
|
|
159
|
+
|
|
160
|
+
- 名前: `"ps"` · デフォルトコマンド: `ps aux` · 返り値: `ProcessInfo[]`
|
|
161
|
+
|
|
162
|
+
| フィールド | 型 | 元の列 | 補足 |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `user` | `string` | `USER` | |
|
|
165
|
+
| `pid` | `number` | `PID` | |
|
|
166
|
+
| `cpu` | `number` | `%CPU` | |
|
|
167
|
+
| `mem` | `number` | `%MEM` | |
|
|
168
|
+
| `vsz` | `number` | `VSZ` | 仮想メモリサイズ(KB) |
|
|
169
|
+
| `rss` | `number` | `RSS` | 実メモリ使用量(KB) |
|
|
170
|
+
| `tty` | `string` | `TTY` | 制御端末がない場合は`?` |
|
|
171
|
+
| `stat` | `string` | `STAT` | プロセス状態コード(例: `Ss`, `R+`) |
|
|
172
|
+
| `start` | `string` | `START` | `ps`がそのまま出力した文字列(時刻または日付) |
|
|
173
|
+
| `time` | `string` | `TIME` | 累積CPU時間 |
|
|
174
|
+
| `command` | `string` | `COMMAND` | 引数を含む完全なコマンドライン。スペースで切れることはありません |
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const procs = await run<ProcessInfo[]>("ps");
|
|
178
|
+
const highCpu = procs.filter((p) => p.cpu > 50);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `df`
|
|
182
|
+
|
|
183
|
+
- 名前: `"df"` · デフォルトコマンド: `df -k` · 返り値: `DiskUsage[]`
|
|
184
|
+
|
|
185
|
+
| フィールド | 型 | 元の列 | 補足 |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| `filesystem` | `string` | `Filesystem` | |
|
|
188
|
+
| `blocksKb` | `number` | `1K-blocks` | 合計サイズ(KB) |
|
|
189
|
+
| `usedKb` | `number` | `Used` | KB |
|
|
190
|
+
| `availableKb` | `number` | `Available` | KB |
|
|
191
|
+
| `usePercent` | `number` | `Use%` | 数値型。`%`記号は除去済み(例: `"42%"`ではなく`42`) |
|
|
192
|
+
| `mountedOn` | `string` | `Mounted on` | |
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const disks = await run<DiskUsage[]>("df");
|
|
196
|
+
const full = disks.filter((d) => d.usePercent >= 90);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `du`
|
|
200
|
+
|
|
201
|
+
- 名前: `"du"` · デフォルトコマンド: `du -k`(パス指定なし — 自分で指定すべき) · 返り値: `DirSize[]`
|
|
202
|
+
|
|
203
|
+
| フィールド | 型 | 補足 |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `sizeKb` | `number` | サイズ(KB) |
|
|
206
|
+
| `path` | `string` | `du`が出力したままのパス |
|
|
207
|
+
|
|
208
|
+
`du`は対象パスを引数として必要とするため、(パス指定のない)デフォルトに頼るのではなく、ほぼ常に`run("du", ["-k", "/some/path"])`の形で呼び出すことになります。
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const sizes = await run<DirSize[]>("du", ["-k", "-d", "1", "/home/me/projects"]);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `free`
|
|
215
|
+
|
|
216
|
+
- 名前: `"free"` · デフォルトコマンド: `free -b` · 返り値: `MemoryInfo`
|
|
217
|
+
|
|
218
|
+
`-b`(バイト単位)をデフォルトにしているのは、値を単純な数値として保つためです。`-h`(人間が読みやすい形式、例: `"3.8Gi"`)は単位が文字列に混在するため**対応していません**。
|
|
219
|
+
|
|
220
|
+
| フィールド | 型 | 補足 |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `mem.total` | `number` | バイト |
|
|
223
|
+
| `mem.used` | `number` | バイト |
|
|
224
|
+
| `mem.free` | `number` | バイト |
|
|
225
|
+
| `mem.shared` | `number` | バイト |
|
|
226
|
+
| `mem.buffCache` | `number` | バイト(`buff/cache`列) |
|
|
227
|
+
| `mem.available` | `number` | バイト |
|
|
228
|
+
| `swap.total` | `number` | バイト |
|
|
229
|
+
| `swap.used` | `number` | バイト |
|
|
230
|
+
| `swap.free` | `number` | バイト |
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const mem = await run<MemoryInfo>("free");
|
|
234
|
+
const usedPercent = (mem.mem.used / mem.mem.total) * 100;
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `lsof`
|
|
238
|
+
|
|
239
|
+
- 名前: `"lsof"` · デフォルトコマンド: `lsof -F pcufTtn` · 返り値: `OpenFile[]`
|
|
240
|
+
|
|
241
|
+
人間向けのテーブル表示をスクレイピングするのではなく、lsofの`-F`フィールド出力モードを使用しています。スペースを含むファイル名や長いコマンド名に対してもはるかに頑健です。開いているファイルディスクリプタごとに1エントリとなり、それを所有するプロセスのPID/コマンド名/ユーザーも併せて持ちます。
|
|
242
|
+
|
|
243
|
+
| フィールド | 型 | 補足 |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| `pid` | `number` | 所有プロセスのID |
|
|
246
|
+
| `command` | `string` | 所有プロセスのコマンド名 |
|
|
247
|
+
| `user` | `string` | 所有プロセスのユーザー(lsofが出力した数値UIDのまま) |
|
|
248
|
+
| `fd` | `string` | ファイルディスクリプタ(例: `cwd`, `txt`, `mem`、または数値) |
|
|
249
|
+
| `type` | `string` | ファイル種別(例: `DIR`, `REG`, `IPv4`, `unknown`) |
|
|
250
|
+
| `name` | `string` | パス、またはソケット/デバイスの説明 |
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
// /var/log 以下のファイルを開いている全プロセス
|
|
254
|
+
const open = await run<OpenFile[]>("lsof");
|
|
255
|
+
const logUsers = open.filter((f) => f.name.startsWith("/var/log"));
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
root権限がない場合、lsofは自分自身のプロセスが開いているファイルしか見えません。これはOSの権限による制約であり、nixparse自体の制限ではありません。
|
|
259
|
+
|
|
260
|
+
### `mount`
|
|
261
|
+
|
|
262
|
+
- 名前: `"mount"` · デフォルトコマンド: `mount` · 返り値: `MountPoint[]`
|
|
263
|
+
|
|
264
|
+
| フィールド | 型 | 補足 |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `device` | `string` | 例: `/dev/sda1`, `none`, `tmpfs` |
|
|
267
|
+
| `path` | `string` | マウントポイント |
|
|
268
|
+
| `fsType` | `string` | 例: `ext4`, `overlay`, `tmpfs` |
|
|
269
|
+
| `options` | `string[]` | `,`で分割されたマウントオプション(例: `["rw", "relatime"]`) |
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const mounts = await run<MountPoint[]>("mount");
|
|
273
|
+
const readOnly = mounts.filter((m) => m.options.includes("ro"));
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `who`
|
|
277
|
+
|
|
278
|
+
- 名前: `"who"` · デフォルトコマンド: `who` · 返り値: `LoggedInUser[]`
|
|
279
|
+
|
|
280
|
+
| フィールド | 型 | 補足 |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| `user` | `string` | |
|
|
283
|
+
| `tty` | `string` | 例: `pts/1` |
|
|
284
|
+
| `loginTime` | `string` | `who`がそのまま出力した文字列(ロケール依存のフォーマットのため、`Date`型へ変換せず文字列のまま保持) |
|
|
285
|
+
|
|
286
|
+
### `uptime`
|
|
287
|
+
|
|
288
|
+
- 名前: `"uptime"` · デフォルトコマンド: `uptime` · 返り値: `UptimeInfo`
|
|
289
|
+
|
|
290
|
+
| フィールド | 型 | 補足 |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| `currentTime` | `string` | 出力された現在時刻(例: `"14:32:10"`) |
|
|
293
|
+
| `upDays` | `number` | 稼働時間が1日未満なら`0` |
|
|
294
|
+
| `upHours` | `number` | |
|
|
295
|
+
| `upMinutes` | `number` | |
|
|
296
|
+
| `users` | `number` | ログイン中のユーザー数 |
|
|
297
|
+
| `loadAverage1m` | `number` | |
|
|
298
|
+
| `loadAverage5m` | `number` | |
|
|
299
|
+
| `loadAverage15m` | `number` | |
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const up = await run<UptimeInfo>("uptime");
|
|
303
|
+
if (up.loadAverage1m > 4) console.warn("システム負荷が高い");
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### `ip-addr`
|
|
307
|
+
|
|
308
|
+
- 名前: `"ip-addr"` · デフォルトコマンド: `ip -j addr` · 返り値: `NetworkInterface[]`
|
|
309
|
+
|
|
310
|
+
`ip -j`は元からネイティブなJSONを出力します。このパーサーはテキストのスクレイピングを**一切行わず**、`JSON.parse`の後にZod検証をかけるだけです。たまたまJSON対応していたコマンドの上に、保証された形(と完全なTypeScript型)を載せている形です。
|
|
311
|
+
|
|
312
|
+
| フィールド | 型 | 補足 |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| `ifindex` | `number` | |
|
|
315
|
+
| `ifname` | `string` | 例: `eth0`, `lo` |
|
|
316
|
+
| `flags` | `string[]` | 例: `["BROADCAST", "MULTICAST", "UP"]` |
|
|
317
|
+
| `mtu` | `number` | |
|
|
318
|
+
| `qdisc` | `string` | |
|
|
319
|
+
| `operstate` | `string` | 例: `UP`, `DOWN`, `UNKNOWN` |
|
|
320
|
+
| `group` | `string` | |
|
|
321
|
+
| `txqlen` | `number?` | 省略可能 — 全てのインターフェース種別に存在するわけではない |
|
|
322
|
+
| `link_type` | `string` | 例: `ether`, `loopback` |
|
|
323
|
+
| `address` | `string` | MACアドレス |
|
|
324
|
+
| `broadcast` | `string?` | 省略可能 |
|
|
325
|
+
| `addr_info` | `AddrInfo[]` | 下記参照 |
|
|
326
|
+
|
|
327
|
+
`AddrInfo`:
|
|
328
|
+
|
|
329
|
+
| フィールド | 型 | 補足 |
|
|
330
|
+
|---|---|---|
|
|
331
|
+
| `family` | `string` | `"inet"`または`"inet6"` |
|
|
332
|
+
| `local` | `string` | IPアドレス |
|
|
333
|
+
| `prefixlen` | `number` | |
|
|
334
|
+
| `broadcast` | `string?` | 省略可能 |
|
|
335
|
+
| `scope` | `string` | 例: `global`, `host`, `link` |
|
|
336
|
+
| `label` | `string?` | 省略可能 |
|
|
337
|
+
| `valid_life_time` | `number` | |
|
|
338
|
+
| `preferred_life_time` | `number` | |
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
const ifaces = await run<NetworkInterface[]>("ip-addr");
|
|
342
|
+
const eth0 = ifaces.find((i) => i.ifname === "eth0");
|
|
343
|
+
const ipv4 = eth0?.addr_info.find((a) => a.family === "inet")?.local;
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### `ip-route`
|
|
347
|
+
|
|
348
|
+
- 名前: `"ip-route"` · デフォルトコマンド: `ip -j route` · 返り値: `RouteEntry[]`
|
|
349
|
+
|
|
350
|
+
`ip-addr`と同じアプローチです。ネイティブJSONをパースではなく検証します。
|
|
351
|
+
|
|
352
|
+
| フィールド | 型 | 補足 |
|
|
353
|
+
|---|---|---|
|
|
354
|
+
| `dst` | `string` | 宛先(例: `"default"`またはCIDR) |
|
|
355
|
+
| `gateway` | `string?` | 省略可能 |
|
|
356
|
+
| `dev` | `string?` | 省略可能、送出インターフェース |
|
|
357
|
+
| `protocol` | `string?` | 省略可能 |
|
|
358
|
+
| `scope` | `string?` | 省略可能 |
|
|
359
|
+
| `prefsrc` | `string?` | 省略可能、優先送信元アドレス |
|
|
360
|
+
| `flags` | `string[]` | |
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
const routes = await run<RouteEntry[]>("ip-route");
|
|
364
|
+
const defaultRoute = routes.find((r) => r.dst === "default");
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### `ss`
|
|
368
|
+
|
|
369
|
+
- 名前: `"ss"` · デフォルトコマンド: `ss -tln` · 返り値: `Socket[]`
|
|
370
|
+
|
|
371
|
+
`[::1]:631`のような括弧付きIPv6アドレスを、先頭の`:`ではなく**最後の**`:`で分割することで正しく扱います。権限が許せば所有プロセスも取得したい場合は、デフォルトの代わりに`["-tlnp"]`を渡してください。
|
|
372
|
+
|
|
373
|
+
| フィールド | 型 | 補足 |
|
|
374
|
+
|---|---|---|
|
|
375
|
+
| `state` | `string` | 例: `LISTEN` |
|
|
376
|
+
| `recvQ` | `number` | |
|
|
377
|
+
| `sendQ` | `number` | |
|
|
378
|
+
| `localAddress` | `string` | IPv4、または`[...]`形式のIPv6、または`*` |
|
|
379
|
+
| `localPort` | `string` | `*`も有効な値であるため文字列のまま保持 |
|
|
380
|
+
| `peerAddress` | `string` | |
|
|
381
|
+
| `peerPort` | `string` | |
|
|
382
|
+
| `process` | `string?` | `-p`オプション付きかつ権限がある場合のみ存在(例: `users:(("node",pid=123,fd=10))`) |
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
const sockets = await run<Socket[]>("ss", ["-tlnp"]);
|
|
386
|
+
const listeningOn8080 = sockets.find((s) => s.localPort === "8080");
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## エラーハンドリング
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { run, parse, CommandNotFoundError, UnknownParserError } from "nixparse";
|
|
393
|
+
import { ZodError } from "zod";
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const procs = await run("ps");
|
|
397
|
+
} catch (err) {
|
|
398
|
+
if (err instanceof CommandNotFoundError) {
|
|
399
|
+
console.error(`バイナリが見つかりません: ${err.command}`);
|
|
400
|
+
} else if (err instanceof ZodError) {
|
|
401
|
+
console.error("出力が想定の形式と一致しませんでした", err.issues);
|
|
402
|
+
} else {
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
parse("not-a-real-parser", "...");
|
|
409
|
+
} catch (err) {
|
|
410
|
+
if (err instanceof UnknownParserError) {
|
|
411
|
+
console.error(`"${err.name}" という名前のパーサーは存在しません`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## 実用例
|
|
417
|
+
|
|
418
|
+
**メモリ使用量トップ5のプロセスを探す:**
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
import { run, type ProcessInfo } from "nixparse";
|
|
422
|
+
|
|
423
|
+
const procs = await run<ProcessInfo[]>("ps");
|
|
424
|
+
const top5 = [...procs].sort((a, b) => b.rss - a.rss).slice(0, 5);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**ディスクがほぼ満杯になっていないかチェックする:**
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import { run, type DiskUsage } from "nixparse";
|
|
431
|
+
|
|
432
|
+
const disks = await run<DiskUsage[]>("df");
|
|
433
|
+
for (const d of disks) {
|
|
434
|
+
if (d.usePercent >= 90) {
|
|
435
|
+
console.warn(`${d.mountedOn} が ${d.usePercent}% 使用中 (${d.filesystem})`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**LLMエージェントへ渡すプロンプト用に、構造化したシステム状態を作る:**
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
import { run, type ProcessInfo, type MemoryInfo } from "nixparse";
|
|
444
|
+
|
|
445
|
+
const [procs, mem] = await Promise.all([
|
|
446
|
+
run<ProcessInfo[]>("ps"),
|
|
447
|
+
run<MemoryInfo>("free"),
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
const summary = {
|
|
451
|
+
topProcessesByCpu: procs.sort((a, b) => b.cpu - a.cpu).slice(0, 5),
|
|
452
|
+
memoryUsedPercent: (mem.mem.used / mem.mem.total) * 100,
|
|
453
|
+
};
|
|
454
|
+
// JSON.stringify(summary) → 既に検証済みなのでプロンプトに埋め込んでも安全
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## 対応範囲・制限事項
|
|
458
|
+
|
|
459
|
+
- 組み込みパーサーは**Linux**の出力(GNU coreutils, iproute2, util-linux)を前提としています。macOS/BSD系の`ps`, `df`等はオプションや列構成が異なるため未検証です。固定の列レイアウトと一致しないため、誤った値を黒魔術的に返すのではなく、エラーを投げる可能性が高いです。macOS対応のPRは歓迎します。
|
|
460
|
+
- `lsof`で見える範囲は、`lsof`を直接実行した場合と同様にプロセスの権限に制限されます。
|
|
461
|
+
- `who`の`loginTime`はロケール依存の生文字列のまま保持しており、`Date`型へは変換していません。`who`の日付フォーマットはロケールをまたいで確実に機械的にパースできるとは言えないためです。
|
|
462
|
+
- `df`のパーサーは、ファイルシステム名が長くて行が折れない(NFS形式の長いデバイス文字列等で発生しうる)ことを前提としています。行が折れた場合は誤ったデータを黒魔術的に返すのではなく、検証エラーになります。
|
|
463
|
+
|
|
464
|
+
## 開発
|
|
465
|
+
|
|
466
|
+
```sh
|
|
467
|
+
git clone <このリポジトリ>
|
|
468
|
+
cd nixparse
|
|
469
|
+
npm install
|
|
470
|
+
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
|
|
471
|
+
npm run test # vitest。test/fixtures/ 内のfixtureに対して実行
|
|
472
|
+
npm run test:watch # ウォッチモード
|
|
473
|
+
npm run typecheck # tsc --noEmit
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
`test/fixtures/*.txt`内のfixtureは、各コマンドを実際に実行して取得した本物の出力です。新しいコマンド対応を追加したりエッジケースを修正する際は、手書きで作るのではなく実際にサンプルを取得してください(`<command> > test/fixtures/<name>.txt`)。テストが実際のツールの挙動を反映するようにするためです。
|
|
477
|
+
|
|
478
|
+
## ライセンス
|
|
479
|
+
|
|
480
|
+
MIT
|