@maschinenlesbar.org/nina-warnungen-cli 0.0.1
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/CONTRIBUTING.md +25 -0
- package/LICENSE +661 -0
- package/LICENSING.md +47 -0
- package/README.md +227 -0
- package/dist/src/cli/commands/misc.d.ts +4 -0
- package/dist/src/cli/commands/misc.d.ts.map +1 -0
- package/dist/src/cli/commands/misc.js +36 -0
- package/dist/src/cli/commands/misc.js.map +1 -0
- package/dist/src/cli/commands/warnings.d.ts +4 -0
- package/dist/src/cli/commands/warnings.d.ts.map +1 -0
- package/dist/src/cli/commands/warnings.js +38 -0
- package/dist/src/cli/commands/warnings.js.map +1 -0
- package/dist/src/cli/index.d.ts +3 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +6 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/io.d.ts +17 -0
- package/dist/src/cli/io.d.ts.map +1 -0
- package/dist/src/cli/io.js +22 -0
- package/dist/src/cli/io.js.map +1 -0
- package/dist/src/cli/program.d.ts +7 -0
- package/dist/src/cli/program.d.ts.map +1 -0
- package/dist/src/cli/program.js +53 -0
- package/dist/src/cli/program.js.map +1 -0
- package/dist/src/cli/run.d.ts +3 -0
- package/dist/src/cli/run.d.ts.map +1 -0
- package/dist/src/cli/run.js +56 -0
- package/dist/src/cli/run.js.map +1 -0
- package/dist/src/cli/shared.d.ts +69 -0
- package/dist/src/cli/shared.d.ts.map +1 -0
- package/dist/src/cli/shared.js +133 -0
- package/dist/src/cli/shared.js.map +1 -0
- package/dist/src/client/client.d.ts +45 -0
- package/dist/src/client/client.d.ts.map +1 -0
- package/dist/src/client/client.js +81 -0
- package/dist/src/client/client.js.map +1 -0
- package/dist/src/client/engine.d.ts +53 -0
- package/dist/src/client/engine.d.ts.map +1 -0
- package/dist/src/client/engine.js +105 -0
- package/dist/src/client/engine.js.map +1 -0
- package/dist/src/client/enums.d.ts +15 -0
- package/dist/src/client/enums.d.ts.map +1 -0
- package/dist/src/client/enums.js +15 -0
- package/dist/src/client/enums.js.map +1 -0
- package/dist/src/client/errors.d.ts +36 -0
- package/dist/src/client/errors.d.ts.map +1 -0
- package/dist/src/client/errors.js +60 -0
- package/dist/src/client/errors.js.map +1 -0
- package/dist/src/client/http.d.ts +26 -0
- package/dist/src/client/http.d.ts.map +1 -0
- package/dist/src/client/http.js +107 -0
- package/dist/src/client/http.js.map +1 -0
- package/dist/src/client/index.d.ts +11 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +9 -0
- package/dist/src/client/index.js.map +1 -0
- package/dist/src/client/query.d.ts +9 -0
- package/dist/src/client/query.d.ts.map +1 -0
- package/dist/src/client/query.js +33 -0
- package/dist/src/client/query.js.map +1 -0
- package/dist/src/client/types.d.ts +48 -0
- package/dist/src/client/types.d.ts.map +1 -0
- package/dist/src/client/types.js +7 -0
- package/dist/src/client/types.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +67 -0
package/LICENSING.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Licensing
|
|
2
|
+
|
|
3
|
+
`nina-warnungen-cli` is **dual-licensed**. You may use it under **either** of the
|
|
4
|
+
following, at your choice:
|
|
5
|
+
|
|
6
|
+
## 1. Open source — AGPL-3.0-or-later (default)
|
|
7
|
+
|
|
8
|
+
Free to use, modify, and distribute under the
|
|
9
|
+
[GNU Affero General Public License v3.0 or later](LICENSE).
|
|
10
|
+
|
|
11
|
+
The key obligation is the AGPL's **§13 network clause**: if you run a modified
|
|
12
|
+
version of this software to provide a service to others over a network, you must
|
|
13
|
+
make the complete corresponding source code of your modified version available
|
|
14
|
+
to the users of that service. The usual copyleft terms also apply when you
|
|
15
|
+
distribute the software or works based on it.
|
|
16
|
+
|
|
17
|
+
This is the right option for open-source projects, internal and personal use,
|
|
18
|
+
evaluation, and anyone able to comply with the AGPL.
|
|
19
|
+
|
|
20
|
+
## 2. Commercial license
|
|
21
|
+
|
|
22
|
+
If the AGPL's obligations do not fit your situation — for example, you want to:
|
|
23
|
+
|
|
24
|
+
- embed `nina-warnungen-cli` in a **closed-source / proprietary product**, or
|
|
25
|
+
- run a **modified version as a proprietary network service (SaaS)** without
|
|
26
|
+
disclosing your source, or
|
|
27
|
+
- keep your modifications private,
|
|
28
|
+
|
|
29
|
+
then a separate **commercial license** is available. It grants use of the same
|
|
30
|
+
code without the AGPL's copyleft and network-disclosure obligations, under
|
|
31
|
+
negotiated terms.
|
|
32
|
+
|
|
33
|
+
**To obtain a commercial license, contact:** sebs@2xs.org
|
|
34
|
+
|
|
35
|
+
## Why this is possible
|
|
36
|
+
|
|
37
|
+
The copyright holder owns 100% of the source code in this repository and can
|
|
38
|
+
therefore offer it under more than one license simultaneously. This project does
|
|
39
|
+
**not** accept external code contributions (see [CONTRIBUTING.md](CONTRIBUTING.md)),
|
|
40
|
+
which keeps copyright ownership single and unambiguous — a prerequisite for
|
|
41
|
+
offering the commercial option. The only third-party dependency that ships with
|
|
42
|
+
the package, [`commander`](https://github.com/tj/commander.js), is MIT-licensed
|
|
43
|
+
and compatible with both licensing options.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
Copyright © 2026 Sebastian Schürmann. All rights reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# nina-warnungen-cli
|
|
2
|
+
|
|
3
|
+
A TypeScript **API client** and **command-line interface** for the open
|
|
4
|
+
[NINA API](https://nina.api.bund.dev/) (`warnung.bund.de`) — the federal
|
|
5
|
+
civil-protection warning system run by the **BBK** (Bundesamt für Bevölkerungsschutz
|
|
6
|
+
und Katastrophenhilfe). It aggregates **MoWaS**, **KATWARN**, **BIWAPP**,
|
|
7
|
+
**DWD severe-weather**, **flood (LHP)** and **police** alerts.
|
|
8
|
+
|
|
9
|
+
- **Zero runtime HTTP dependencies** — built on Node's built-in `http`/`https` (no axios, no fetch polyfill).
|
|
10
|
+
- **One small dependency** for the CLI: [`commander`](https://github.com/tj/commander.js).
|
|
11
|
+
- **Strongly typed** — typed client surface, warning summaries and the source enum.
|
|
12
|
+
- **Well tested** — unit tests on Node's built-in test runner (`node --test`), every HTTP response mocked.
|
|
13
|
+
- **Read-only, no auth** — the NINA API needs no key; this client only reads.
|
|
14
|
+
|
|
15
|
+
New to NINA, or terms like *MoWaS*, *Amtlicher Regionalschlüssel (ARS)* or the
|
|
16
|
+
CAP *Severity* levels? See **[GLOSSARY.md](GLOSSARY.md)** for the domain concepts
|
|
17
|
+
and the project's own vocabulary.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Node.js **>= 20** (uses the stable built-in test runner, ESM and top-level `await`).
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm run build # compiles TypeScript to dist/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run the CLI without a global install:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node dist/src/cli/index.js --help
|
|
34
|
+
# or, after `npm link` / global install:
|
|
35
|
+
nina --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## CLI usage
|
|
41
|
+
|
|
42
|
+
Search/list commands print pretty JSON to stdout; `warning geojson` writes the raw
|
|
43
|
+
GeoJSON bytes to stdout or to a file via `-o/--output`. The body is buffered in
|
|
44
|
+
memory (bounded by `--max-response-bytes`), not streamed. As a sanity check, if the
|
|
45
|
+
server returns a body whose `Content-Type` does not contain `json` (e.g. a
|
|
46
|
+
`text/html` gateway error page served with a `200` status), the CLI prints a
|
|
47
|
+
warning to stderr before writing it. This is a `Content-Type`-only check — the live
|
|
48
|
+
API serves GeoJSON as `application/json`, so it does not (and cannot) verify that
|
|
49
|
+
the bytes are actually valid GeoJSON, only that the type string looks JSON-ish.
|
|
50
|
+
|
|
51
|
+
### Global options
|
|
52
|
+
|
|
53
|
+
| Option | Description |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `--base-url <url>` | API base URL (default `https://warnung.bund.de`) |
|
|
56
|
+
| `--timeout <ms>` | Per-request timeout (default `30000`; `0` disables the timeout — waits indefinitely) |
|
|
57
|
+
| `--user-agent <ua>` | `User-Agent` header value |
|
|
58
|
+
| `--max-retries <n>` | Retries for transient `429`/`503` responses (default `2`, capped at `10`) |
|
|
59
|
+
| `--max-response-bytes <n>` | Cap response body size in bytes (`0` = unlimited; default 100 MiB) |
|
|
60
|
+
| `--compact` | Print JSON on a single line |
|
|
61
|
+
| `-o, --output <file>` | For downloads: write bytes to a file instead of stdout |
|
|
62
|
+
|
|
63
|
+
Global options may be given **before or after** the command, e.g.
|
|
64
|
+
`nina --compact map-data dwd` and `nina map-data dwd --compact` are equivalent.
|
|
65
|
+
|
|
66
|
+
An identifier that begins with `-` would otherwise be parsed as an option; pass it
|
|
67
|
+
after a `--` separator so it is treated as a positional, e.g.
|
|
68
|
+
`nina warning get -- -odd.identifier`.
|
|
69
|
+
|
|
70
|
+
Numeric options (`--timeout`, `--max-retries`, `--max-response-bytes`) accept only
|
|
71
|
+
plain non-negative decimal integers; values like `5.0`, `0x10`, `1e3`, `-1` or a
|
|
72
|
+
leading/trailing space are rejected as usage errors.
|
|
73
|
+
|
|
74
|
+
> **Note on `--base-url` + `--output`:** the base URL is trusted as given, so
|
|
75
|
+
> `nina --base-url <any-http(s)-host> -o <file> ...` is effectively a general
|
|
76
|
+
> "fetch this URL and write it to a file" tool. Only `http`/`https` are allowed
|
|
77
|
+
> (`file:`/`ftp:` are rejected) and redirects are **never followed**, but point it
|
|
78
|
+
> only at hosts you trust. Setting `--max-response-bytes 0` disables the in-memory
|
|
79
|
+
> response-size guard entirely (including the 100 MiB default).
|
|
80
|
+
|
|
81
|
+
### Commands
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
sources list the valid warning sources
|
|
85
|
+
map-data <source> current warnings from a source
|
|
86
|
+
(mowas | katwarn | biwapp | dwd | lhp | police)
|
|
87
|
+
warning get <id> full CAP warning by identifier
|
|
88
|
+
warning geojson <id> warning geometry as GeoJSON (-o to save)
|
|
89
|
+
dashboard <ars> warnings for a region (Amtlicher Regionalschlüssel / AGS)
|
|
90
|
+
archive mapping <id> | get <id> MoWaS archive (history + a past warning)
|
|
91
|
+
reference notfalltipps | event-codes | data-version
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Examples
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Current severe-weather warnings from the DWD
|
|
98
|
+
nina map-data dwd
|
|
99
|
+
|
|
100
|
+
# A specific warning (identifier from a map-data entry's `id`)
|
|
101
|
+
nina warning get mow.DE-BY-A-W083-20240101
|
|
102
|
+
|
|
103
|
+
# Its geometry, saved to a file
|
|
104
|
+
nina -o warn.geojson warning geojson mow.DE-BY-A-W083-20240101
|
|
105
|
+
|
|
106
|
+
# All warnings affecting a district by its regional key
|
|
107
|
+
nina dashboard 055150000000
|
|
108
|
+
|
|
109
|
+
# Poll for changes cheaply
|
|
110
|
+
nina --compact reference data-version
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Exit codes: `0` success, `4` on a `404` from the API, `1` for any other error, non-zero for usage errors.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Library usage
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { NinaClient, NinaApiError, type NinaSource } from "@maschinenlesbar.org/nina-warnungen-cli";
|
|
121
|
+
|
|
122
|
+
const client = new NinaClient(); // defaults to https://warnung.bund.de
|
|
123
|
+
|
|
124
|
+
const alerts = await client.mapData("dwd"); // MapWarning[]
|
|
125
|
+
const full = await client.warnings.get(alerts[0]!.id);
|
|
126
|
+
const region = await client.dashboard("055150000000");
|
|
127
|
+
|
|
128
|
+
const geo = await client.warnings.geojson(alerts[0]!.id); // raw bytes
|
|
129
|
+
await import("node:fs/promises").then((fs) => fs.writeFile("warn.geojson", geo.data));
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await client.warnings.get("DOES-NOT-EXIST");
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (err instanceof NinaApiError) console.error(err.status, err.detail);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Client options
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
new NinaClient({
|
|
142
|
+
baseUrl: "https://warnung.bund.de",
|
|
143
|
+
timeoutMs: 15_000,
|
|
144
|
+
maxRetries: 3, // 429 / 503 are retried with linear backoff
|
|
145
|
+
maxResponseBytes: 50 << 20, // abort responses larger than 50 MiB (0 = unlimited)
|
|
146
|
+
userAgent: "my-app/1.0",
|
|
147
|
+
transport: customTransport, // inject your own HTTP transport
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Resource groups
|
|
152
|
+
|
|
153
|
+
`client.mapData(source)`, `client.dashboard(ars)`, `client.warnings` (`.get` / `.geojson`),
|
|
154
|
+
`client.archive` (`.mapping` / `.get`), `client.reference` (`.notfalltipps` / `.eventCodes` / `.dataVersion`).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Architecture
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
src/
|
|
162
|
+
client/
|
|
163
|
+
enums.ts # NinaSource + severity value sets (runtime + type)
|
|
164
|
+
types.ts # response interfaces (typed summaries; full warnings as JsonObject)
|
|
165
|
+
query.ts # dependency-free query-string builder
|
|
166
|
+
http.ts # the Transport interface + default node:http/https transport
|
|
167
|
+
engine.ts # URL building, retry/backoff, JSON/raw decoding, error mapping
|
|
168
|
+
errors.ts # NinaError / NinaApiError / NinaNetworkError / NinaParseError
|
|
169
|
+
client.ts # NinaClient — resource groups over the engine
|
|
170
|
+
cli/
|
|
171
|
+
io.ts # injectable I/O seam (stdout/stderr/file)
|
|
172
|
+
shared.ts # option parsers, global-option resolver, JSON/raw renderers
|
|
173
|
+
commands/ # warnings + archive/reference (misc) command groups
|
|
174
|
+
program.ts # assembles the commander program from injectable deps
|
|
175
|
+
run.ts # parses argv -> exit code (no process.exit; testable)
|
|
176
|
+
index.ts # #! bin shim
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Design notes**
|
|
180
|
+
|
|
181
|
+
- The HTTP layer is a single `Transport` function (`(req) => Promise<HttpResponse>`). The default
|
|
182
|
+
uses `node:http`/`node:https`; tests inject a mock. This keeps the client free of any HTTP framework.
|
|
183
|
+
- The CLI is built around injectable `CliDeps` (client factory + I/O), so the whole program can be
|
|
184
|
+
driven in-process by tests with a mocked client and captured output — no subprocesses.
|
|
185
|
+
- Full CAP warning payloads are deeply nested and standard-specific, so they are returned as
|
|
186
|
+
faithful raw `JsonObject`s rather than partially-guessed types.
|
|
187
|
+
- The transport issues exactly one request and **does not follow 3xx redirects** — a redirect is
|
|
188
|
+
surfaced as a `NinaApiError` like any other non-2xx status. This avoids replaying headers to a
|
|
189
|
+
redirect target (no cross-host header leak / SSRF via `Location`).
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Testing
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm test # builds, then runs `node --test` over dist/test
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- **`query.test.ts`** — query-string serialisation.
|
|
200
|
+
- **`http.test.ts`** — the default transport against a real loopback `http.createServer` (GET, unsupported protocol, size cap, timeout, no-redirect-follow).
|
|
201
|
+
- **`engine.test.ts`** — URL building, JSON/raw decoding, error mapping, 429/503 retry, redirect-as-error, size-cap forwarding — mocked transport.
|
|
202
|
+
- **`client.test.ts`** — every endpoint's method/URL mapping, including identifier URL-encoding — mocked transport.
|
|
203
|
+
- **`cli.test.ts`** — end-to-end command parsing, rendering, file output and exit codes, plus negative paths (network/parse/API errors, write failures, content-type warning) — mocked client.
|
|
204
|
+
- **`shared.test.ts`** — the `parseIntArg` value parser (accepts plain decimals, rejects everything else).
|
|
205
|
+
|
|
206
|
+
## Continuous integration
|
|
207
|
+
|
|
208
|
+
GitHub Actions workflows under `.github/workflows/`:
|
|
209
|
+
|
|
210
|
+
- **ci.yml** — type-check, build and test on Node 20/22/24 for every push and PR.
|
|
211
|
+
- **release.yml** — on a `v*` tag: verify the tag matches `package.json`, test, `npm pack`, and create a GitHub Release with the tarball.
|
|
212
|
+
- **publish.yml** — manual dispatch: publish to npm via OIDC **Trusted Publishing** (no stored `NPM_TOKEN`) with provenance.
|
|
213
|
+
- **docs.yml** — build TypeDoc API docs and deploy to GitHub Pages on each `v*` tag.
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
**Dual-licensed** — use it under **either**:
|
|
218
|
+
|
|
219
|
+
- **[AGPL-3.0-or-later](LICENSE)** (default, free). Note the AGPL's §13 network
|
|
220
|
+
clause: if you run a modified version as a network service, you must offer that
|
|
221
|
+
modified source to the service's users.
|
|
222
|
+
- **Commercial license** (paid), for closed-source / proprietary or SaaS use
|
|
223
|
+
without the AGPL's obligations.
|
|
224
|
+
|
|
225
|
+
See **[LICENSING.md](LICENSING.md)** for details, and **[CONTRIBUTING.md](CONTRIBUTING.md)**
|
|
226
|
+
for the contribution policy (this project does not accept external code
|
|
227
|
+
contributions). Commercial enquiries: **sebs@2xs.org**.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"misc.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/misc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAiD1E"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { action, renderJson, requireIdentifier } from "../shared.js";
|
|
2
|
+
export function registerMiscCommands(program, deps) {
|
|
3
|
+
const archive = program.command("archive").description("MoWaS archive (historical warnings)");
|
|
4
|
+
archive
|
|
5
|
+
.command("mapping <identifier>")
|
|
6
|
+
.description("Revision history for an archived MoWaS identifier")
|
|
7
|
+
.action(action(deps, async ({ client, global }, [id]) => {
|
|
8
|
+
renderJson(deps, global, await client.archive.mapping(requireIdentifier(id, "identifier")));
|
|
9
|
+
}));
|
|
10
|
+
archive
|
|
11
|
+
.command("get <identifier>")
|
|
12
|
+
.description("A specific archived MoWaS warning")
|
|
13
|
+
.action(action(deps, async ({ client, global }, [id]) => {
|
|
14
|
+
renderJson(deps, global, await client.archive.get(requireIdentifier(id, "identifier")));
|
|
15
|
+
}));
|
|
16
|
+
const reference = program.command("reference").description("Static reference data");
|
|
17
|
+
reference
|
|
18
|
+
.command("notfalltipps")
|
|
19
|
+
.description("Emergency-preparedness tips (German)")
|
|
20
|
+
.action(action(deps, async ({ client, global }) => {
|
|
21
|
+
renderJson(deps, global, await client.reference.notfalltipps());
|
|
22
|
+
}));
|
|
23
|
+
reference
|
|
24
|
+
.command("event-codes")
|
|
25
|
+
.description("The CAP event-code catalogue")
|
|
26
|
+
.action(action(deps, async ({ client, global }) => {
|
|
27
|
+
renderJson(deps, global, await client.reference.eventCodes());
|
|
28
|
+
}));
|
|
29
|
+
reference
|
|
30
|
+
.command("data-version")
|
|
31
|
+
.description("Current data version/hash (useful for change detection)")
|
|
32
|
+
.action(action(deps, async ({ client, global }) => {
|
|
33
|
+
renderJson(deps, global, await client.reference.dataVersion());
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=misc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"misc.js","sourceRoot":"","sources":["../../../../src/cli/commands/misc.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,UAAU,oBAAoB,CAAC,OAAgB,EAAE,IAAa;IAClE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC;IAE9F,OAAO;SACJ,OAAO,CAAC,sBAAsB,CAAC;SAC/B,WAAW,CAAC,mDAAmD,CAAC;SAChE,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAG,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/F,CAAC,CAAC,CACH,CAAC;IAEJ,OAAO;SACJ,OAAO,CAAC,kBAAkB,CAAC;SAC3B,WAAW,CAAC,mCAAmC,CAAC;SAChD,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAG,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC3F,CAAC,CAAC,CACH,CAAC;IAEJ,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC;IAEpF,SAAS;SACN,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,sCAAsC,CAAC;SACnD,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;QACxC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CACH,CAAC;IAEJ,SAAS;SACN,OAAO,CAAC,aAAa,CAAC;SACtB,WAAW,CAAC,8BAA8B,CAAC;SAC3C,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;QACxC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CACH,CAAC;IAEJ,SAAS;SACN,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;QACxC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CACH,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnings.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/warnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAIxC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAiD7E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { action, assertEnum, renderJson, renderRaw, requireIdentifier } from "../shared.js";
|
|
2
|
+
import { NinaSourceValues } from "../../client/enums.js";
|
|
3
|
+
export function registerWarningCommands(program, deps) {
|
|
4
|
+
program
|
|
5
|
+
.command("sources")
|
|
6
|
+
.description("List the valid warning sources")
|
|
7
|
+
.action(action(deps, async ({ global }) => {
|
|
8
|
+
renderJson(deps, global, [...NinaSourceValues]);
|
|
9
|
+
}));
|
|
10
|
+
program
|
|
11
|
+
.command("map-data <source>")
|
|
12
|
+
.description(`Current warnings from a source (${NinaSourceValues.join(" | ")})`)
|
|
13
|
+
.action(action(deps, async ({ client, global }, [source]) => {
|
|
14
|
+
const s = assertEnum(source, NinaSourceValues, "source");
|
|
15
|
+
renderJson(deps, global, await client.mapData(s));
|
|
16
|
+
}));
|
|
17
|
+
const warning = program.command("warning").description("A single warning by identifier");
|
|
18
|
+
warning
|
|
19
|
+
.command("get <identifier>")
|
|
20
|
+
.description("Get the full CAP warning for an identifier")
|
|
21
|
+
.action(action(deps, async ({ client, global }, [id]) => {
|
|
22
|
+
renderJson(deps, global, await client.warnings.get(requireIdentifier(id, "identifier")));
|
|
23
|
+
}));
|
|
24
|
+
warning
|
|
25
|
+
.command("geojson <identifier>")
|
|
26
|
+
.description("Download the warning's geometry as GeoJSON (-o to write a file)")
|
|
27
|
+
.action(action(deps, async ({ client, global }, [id]) => {
|
|
28
|
+
const geojson = await client.warnings.geojson(requireIdentifier(id, "identifier"));
|
|
29
|
+
renderRaw(deps, global, geojson, "json");
|
|
30
|
+
}));
|
|
31
|
+
program
|
|
32
|
+
.command("dashboard <ars>")
|
|
33
|
+
.description("Warnings affecting a region (Amtlicher Regionalschlüssel / AGS)")
|
|
34
|
+
.action(action(deps, async ({ client, global }, [ars]) => {
|
|
35
|
+
renderJson(deps, global, await client.dashboard(requireIdentifier(ars, "region key")));
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=warnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnings.js","sourceRoot":"","sources":["../../../../src/cli/commands/warnings.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,UAAU,uBAAuB,CAAC,OAAgB,EAAE,IAAa;IACrE,OAAO;SACJ,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,gCAAgC,CAAC;SAC7C,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QAChC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CACH,CAAC;IAEJ,OAAO;SACJ,OAAO,CAAC,mBAAmB,CAAC;SAC5B,WAAW,CAAC,mCAAmC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;SAC/E,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE;QAClD,MAAM,CAAC,GAAG,UAAU,CAAC,MAAO,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QAC1D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CACH,CAAC;IAEJ,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAC;IAEzF,OAAO;SACJ,OAAO,CAAC,kBAAkB,CAAC;SAC3B,WAAW,CAAC,4CAA4C,CAAC;SACzD,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAG,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC5F,CAAC,CAAC,CACH,CAAC;IAEJ,OAAO;SACJ,OAAO,CAAC,sBAAsB,CAAC;SAC/B,WAAW,CAAC,iEAAiE,CAAC;SAC9E,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAG,EAAE,YAAY,CAAC,CAAC,CAAC;QACpF,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC,CAAC,CACH,CAAC;IAEJ,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,iEAAiE,CAAC;SAC9E,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE;QAC/C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,GAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1F,CAAC,CAAC,CACH,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":";AACA,oFAAoF;AAEpF,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NinaClient } from "../client/client.js";
|
|
2
|
+
import type { EngineOptions } from "../client/engine.js";
|
|
3
|
+
export interface CliIO {
|
|
4
|
+
out(text: string): void;
|
|
5
|
+
err(text: string): void;
|
|
6
|
+
/** Persist raw bytes to a file. */
|
|
7
|
+
writeFile(path: string, data: Buffer): void;
|
|
8
|
+
/** Write raw bytes to stdout (binary-safe). */
|
|
9
|
+
outBinary(data: Buffer): void;
|
|
10
|
+
}
|
|
11
|
+
export interface CliDeps {
|
|
12
|
+
io: CliIO;
|
|
13
|
+
/** Build a client from the resolved global options (injectable for tests). */
|
|
14
|
+
createClient(options: EngineOptions): NinaClient;
|
|
15
|
+
}
|
|
16
|
+
export declare const defaultIO: CliIO;
|
|
17
|
+
//# sourceMappingURL=io.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../../../src/cli/io.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,WAAW,KAAK;IACpB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mCAAmC;IACnC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,+CAA+C;IAC/C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,KAAK,CAAC;IACV,8EAA8E;IAC9E,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,UAAU,CAAC;CAClD;AAED,eAAO,MAAM,SAAS,EAAE,KAevB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// I/O seam for the CLI. Everything the CLI writes goes through a CliIO object so
|
|
2
|
+
// tests can capture output instead of hitting the real stdout/stderr/filesystem.
|
|
3
|
+
import { writeFileSync } from "node:fs";
|
|
4
|
+
import { NinaIOError } from "../client/errors.js";
|
|
5
|
+
export const defaultIO = {
|
|
6
|
+
out: (text) => process.stdout.write(text + "\n"),
|
|
7
|
+
err: (text) => process.stderr.write(text + "\n"),
|
|
8
|
+
writeFile: (path, data) => {
|
|
9
|
+
// A failed file write (no such dir, EISDIR, EACCES) is a routine,
|
|
10
|
+
// user-caused condition — classify it as a NinaError so run() prints a clean
|
|
11
|
+
// "Error: ..." rather than the "Unexpected error:" wording for internal bugs.
|
|
12
|
+
try {
|
|
13
|
+
writeFileSync(path, data);
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
17
|
+
throw new NinaIOError(`Failed to write ${path}: ${reason}`, { cause });
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
outBinary: (data) => process.stdout.write(data),
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../../../src/cli/io.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,iFAAiF;AAEjF,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiBlD,MAAM,CAAC,MAAM,SAAS,GAAU;IAC9B,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAChD,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAChD,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACxB,kEAAkE;QAClE,6EAA6E;QAC7E,8EAA8E;QAC9E,IAAI,CAAC;YACH,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,IAAI,WAAW,CAAC,mBAAmB,IAAI,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IACD,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;CAChD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { CliDeps } from "./io.js";
|
|
3
|
+
export declare const VERSION: string;
|
|
4
|
+
/** Default dependencies: real client + real stdout/stderr/filesystem. */
|
|
5
|
+
export declare const defaultDeps: CliDeps;
|
|
6
|
+
export declare function buildProgram(deps?: CliDeps): Command;
|
|
7
|
+
//# sourceMappingURL=program.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../../src/cli/program.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAuBvC,eAAO,MAAM,OAAO,QAAgB,CAAC;AAErC,yEAAyE;AACzE,eAAO,MAAM,WAAW,EAAE,OAGzB,CAAC;AAEF,wBAAgB,YAAY,CAAC,IAAI,GAAE,OAAqB,GAAG,OAAO,CAmCjE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Assemble the full commander program. The program is built around an injectable
|
|
2
|
+
// CliDeps so the entire CLI can be driven in tests with a mocked client and
|
|
3
|
+
// captured output.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { defaultIO } from "./io.js";
|
|
8
|
+
import { NinaClient } from "../client/client.js";
|
|
9
|
+
import { parseIntArg } from "./shared.js";
|
|
10
|
+
import { registerWarningCommands } from "./commands/warnings.js";
|
|
11
|
+
import { registerMiscCommands } from "./commands/misc.js";
|
|
12
|
+
/**
|
|
13
|
+
* Single source of truth for the version: read from package.json at runtime
|
|
14
|
+
* rather than duplicating a literal that can silently drift after a release bump.
|
|
15
|
+
* From the compiled location (dist/src/cli/program.js) package.json is three
|
|
16
|
+
* directories up; the same offset holds for the source under src/cli.
|
|
17
|
+
*/
|
|
18
|
+
function readVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const pkgUrl = new URL("../../../package.json", import.meta.url);
|
|
21
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(pkgUrl), "utf8"));
|
|
22
|
+
return pkg.version ?? "0.0.0";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "0.0.0";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const VERSION = readVersion();
|
|
29
|
+
/** Default dependencies: real client + real stdout/stderr/filesystem. */
|
|
30
|
+
export const defaultDeps = {
|
|
31
|
+
io: defaultIO,
|
|
32
|
+
createClient: (options) => new NinaClient(options),
|
|
33
|
+
};
|
|
34
|
+
export function buildProgram(deps = defaultDeps) {
|
|
35
|
+
const program = new Command();
|
|
36
|
+
program
|
|
37
|
+
.name("nina")
|
|
38
|
+
.description("CLI for the open NINA civil-protection warning API (https://warnung.bund.de) — " +
|
|
39
|
+
"MoWaS, KATWARN, BIWAPP, DWD severe weather, flood (LHP) and police alerts.")
|
|
40
|
+
.version(VERSION)
|
|
41
|
+
.option("--base-url <url>", "API base URL", "https://warnung.bund.de")
|
|
42
|
+
.option("--timeout <ms>", "per-request timeout in milliseconds (0 disables; waits indefinitely)", parseIntArg)
|
|
43
|
+
.option("--user-agent <ua>", "User-Agent header value")
|
|
44
|
+
.option("--max-retries <n>", "retries for transient 429/503 responses (capped at 10)", parseIntArg)
|
|
45
|
+
.option("--max-response-bytes <n>", "cap response body size in bytes (0 = unlimited; default 100 MiB)", parseIntArg)
|
|
46
|
+
.option("--compact", "print JSON on a single line instead of pretty-printed")
|
|
47
|
+
.option("-o, --output <file>", "for downloads: write bytes to this file instead of stdout")
|
|
48
|
+
.showHelpAfterError();
|
|
49
|
+
registerWarningCommands(program, deps);
|
|
50
|
+
registerMiscCommands(program, deps);
|
|
51
|
+
return program;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../../src/cli/program.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,4EAA4E;AAC5E,mBAAmB;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;;;;GAKG;AACH,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC5F,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;AAErC,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC,EAAE,EAAE,SAAS;IACb,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC;CACnD,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,OAAgB,WAAW;IACtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,MAAM,CAAC;SACZ,WAAW,CACV,iFAAiF;QAC/E,4EAA4E,CAC/E;SACA,OAAO,CAAC,OAAO,CAAC;SAChB,MAAM,CAAC,kBAAkB,EAAE,cAAc,EAAE,yBAAyB,CAAC;SACrE,MAAM,CACL,gBAAgB,EAChB,sEAAsE,EACtE,WAAW,CACZ;SACA,MAAM,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;SACtD,MAAM,CACL,mBAAmB,EACnB,wDAAwD,EACxD,WAAW,CACZ;SACA,MAAM,CACL,0BAA0B,EAC1B,kEAAkE,EAClE,WAAW,CACZ;SACA,MAAM,CAAC,WAAW,EAAE,uDAAuD,CAAC;SAC5E,MAAM,CAAC,qBAAqB,EAAE,2DAA2D,CAAC;SAC1F,kBAAkB,EAAE,CAAC;IAExB,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvC,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAEpC,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cli/run.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAiBvC,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,OAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCtF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Run the CLI and resolve to a process exit code. Kept separate from the bin
|
|
2
|
+
// shim so tests can call run() directly with injected deps and assert on the
|
|
3
|
+
// captured output and exit code without spawning a subprocess.
|
|
4
|
+
import { CommanderError } from "commander";
|
|
5
|
+
import { buildProgram, defaultDeps } from "./program.js";
|
|
6
|
+
import { NinaApiError, NinaError } from "../client/errors.js";
|
|
7
|
+
/**
|
|
8
|
+
* Apply exitOverride + output redirection to every command in the tree.
|
|
9
|
+
* commander does not propagate these to subcommands, so a parse error on a
|
|
10
|
+
* subcommand would otherwise call process.exit() and bypass our error handling.
|
|
11
|
+
*/
|
|
12
|
+
function configureTree(command, deps) {
|
|
13
|
+
command.exitOverride();
|
|
14
|
+
command.configureOutput({
|
|
15
|
+
writeOut: (str) => deps.io.out(str.replace(/\n$/, "")),
|
|
16
|
+
writeErr: (str) => deps.io.err(str.replace(/\n$/, "")),
|
|
17
|
+
});
|
|
18
|
+
for (const child of command.commands)
|
|
19
|
+
configureTree(child, deps);
|
|
20
|
+
}
|
|
21
|
+
export async function run(argv, deps = defaultDeps) {
|
|
22
|
+
const program = buildProgram(deps);
|
|
23
|
+
configureTree(program, deps);
|
|
24
|
+
try {
|
|
25
|
+
await program.parseAsync(argv, { from: "user" });
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err instanceof CommanderError) {
|
|
30
|
+
// When commander has displayed help — whether from an explicit --help
|
|
31
|
+
// (code "commander.helpDisplayed", exitCode 0) or from a bare invocation /
|
|
32
|
+
// bare command group with no subcommand (code "commander.help", exitCode 1)
|
|
33
|
+
// — the visible output is the same successful help text, so exit 0 in both
|
|
34
|
+
// cases for a consistent "showed help" result. Genuine parse errors
|
|
35
|
+
// (unknown command/option, missing argument) keep their own non-zero code.
|
|
36
|
+
if (err.code === "commander.help" || err.code === "commander.helpDisplayed") {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
return err.exitCode;
|
|
40
|
+
}
|
|
41
|
+
if (err instanceof NinaApiError) {
|
|
42
|
+
deps.io.err(`Error: ${err.message}`);
|
|
43
|
+
// Map a few notable statuses to distinct exit codes for scripting.
|
|
44
|
+
if (err.status === 404)
|
|
45
|
+
return 4;
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
if (err instanceof NinaError) {
|
|
49
|
+
deps.io.err(`Error: ${err.message}`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
deps.io.err(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/cli/run.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,+DAA+D;AAE/D,OAAO,EAAE,cAAc,EAAgB,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;GAIG;AACH,SAAS,aAAa,CAAC,OAAgB,EAAE,IAAa;IACpD,OAAO,CAAC,YAAY,EAAE,CAAC;IACvB,OAAO,CAAC,eAAe,CAAC;QACtB,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtD,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;KACvD,CAAC,CAAC;IACH,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ;QAAE,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc,EAAE,OAAgB,WAAW;IACnE,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YAClC,sEAAsE;YACtE,2EAA2E;YAC3E,4EAA4E;YAC5E,2EAA2E;YAC3E,oEAAoE;YACpE,2EAA2E;YAC3E,IAAI,GAAG,CAAC,IAAI,KAAK,gBAAgB,IAAI,GAAG,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;gBAC5E,OAAO,CAAC,CAAC;YACX,CAAC;YACD,OAAO,GAAG,CAAC,QAAQ,CAAC;QACtB,CAAC;QACD,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;YAChC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACrC,mEAAmE;YACnE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,CAAC,CAAC;YACjC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACrC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,qBAAqB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrF,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
|