@maschinenlesbar.org/luftqualitaet-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 +208 -0
- package/dist/src/cli/commands/data.d.ts +4 -0
- package/dist/src/cli/commands/data.d.ts.map +1 -0
- package/dist/src/cli/commands/data.js +152 -0
- package/dist/src/cli/commands/data.js.map +1 -0
- package/dist/src/cli/commands/reference.d.ts +4 -0
- package/dist/src/cli/commands/reference.d.ts.map +1 -0
- package/dist/src/cli/commands/reference.js +33 -0
- package/dist/src/cli/commands/reference.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 +13 -0
- package/dist/src/cli/io.d.ts.map +1 -0
- package/dist/src/cli/io.js +7 -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 +52 -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 +48 -0
- package/dist/src/cli/run.js.map +1 -0
- package/dist/src/cli/shared.d.ts +65 -0
- package/dist/src/cli/shared.d.ts.map +1 -0
- package/dist/src/cli/shared.js +142 -0
- package/dist/src/cli/shared.js.map +1 -0
- package/dist/src/client/client.d.ts +30 -0
- package/dist/src/client/client.d.ts.map +1 -0
- package/dist/src/client/client.js +76 -0
- package/dist/src/client/client.js.map +1 -0
- package/dist/src/client/engine.d.ts +54 -0
- package/dist/src/client/engine.d.ts.map +1 -0
- package/dist/src/client/engine.js +117 -0
- package/dist/src/client/engine.js.map +1 -0
- package/dist/src/client/enums.d.ts +13 -0
- package/dist/src/client/enums.d.ts.map +1 -0
- package/dist/src/client/enums.js +17 -0
- package/dist/src/client/enums.js.map +1 -0
- package/dist/src/client/errors.d.ts +33 -0
- package/dist/src/client/errors.d.ts.map +1 -0
- package/dist/src/client/errors.js +40 -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 +111 -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 +57 -0
- package/dist/src/client/types.d.ts.map +1 -0
- package/dist/src/client/types.js +8 -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 +66 -0
package/LICENSING.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Licensing
|
|
2
|
+
|
|
3
|
+
`luftqualitaet-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 `luftqualitaet-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,208 @@
|
|
|
1
|
+
# luftqualitaet-cli
|
|
2
|
+
|
|
3
|
+
A TypeScript **API client** and **command-line interface** for the open
|
|
4
|
+
[Umweltbundesamt Air Data API](https://luftqualitaet.api.bund.dev/)
|
|
5
|
+
(`umweltbundesamt.de/api/air_data`) — German **air-quality** measurements,
|
|
6
|
+
indices, annual balances, exceedances and the station/component metadata behind them.
|
|
7
|
+
|
|
8
|
+
> This client targets `/api/air_data/v3`, the live API path, which supersedes the
|
|
9
|
+
> **v2** OpenAPI spec published at [luftqualitaet.api.bund.dev](https://luftqualitaet.api.bund.dev/).
|
|
10
|
+
|
|
11
|
+
New to the UBA Air Data API, or terms like *component*, *scope*, *station type*
|
|
12
|
+
or the hour-ending time window? See **[GLOSSARY.md](GLOSSARY.md)** for the domain
|
|
13
|
+
concepts and the project's own vocabulary.
|
|
14
|
+
|
|
15
|
+
- **Zero runtime HTTP dependencies** — built on Node's built-in `http`/`https` (no axios, no fetch polyfill).
|
|
16
|
+
- **One small dependency** for the CLI: [`commander`](https://github.com/tj/commander.js).
|
|
17
|
+
- **Strongly typed** — typed parameter objects and the lang/index/use enums.
|
|
18
|
+
- **Tested** — unit tests on Node's built-in test runner (`node --test`); every HTTP response is mocked (no real network in the suite).
|
|
19
|
+
- **Read-only, no auth** — the UBA Air Data API needs no key; this client only reads.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Node.js **>= 20** (uses the stable built-in test runner, ESM and top-level `await`).
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install
|
|
29
|
+
npm run build # compiles TypeScript to dist/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run the CLI without a global install:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node dist/src/cli/index.js --help
|
|
36
|
+
# or, after `npm link` / global install:
|
|
37
|
+
luftqualitaet --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## How the API works
|
|
43
|
+
|
|
44
|
+
UBA responses are **index + data** structures: an `indices` array names the
|
|
45
|
+
columns and the payload is a compact map keyed by id/code/timestamp. This client
|
|
46
|
+
returns those payloads faithfully (as JSON) and focuses on getting the request
|
|
47
|
+
right. Look up station ids and component ids via `meta`, `components`, etc.
|
|
48
|
+
|
|
49
|
+
### Global options
|
|
50
|
+
|
|
51
|
+
| Option | Description |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `--base-url <url>` | API base URL (default `https://www.umweltbundesamt.de`) |
|
|
54
|
+
| `--timeout <ms>` | Per-request timeout (default `30000`) |
|
|
55
|
+
| `--user-agent <ua>` | `User-Agent` header value |
|
|
56
|
+
| `--max-retries <n>` | Retries for transient `429`/`503` responses (default `2`) |
|
|
57
|
+
| `--max-response-bytes <n>` | Cap response body size in bytes (`0` = unlimited; default 100 MiB) |
|
|
58
|
+
| `--compact` | Print JSON on a single line |
|
|
59
|
+
|
|
60
|
+
Global options may be given either before or after the command, e.g.
|
|
61
|
+
`luftqualitaet --compact components --lang de` or `luftqualitaet components --compact --lang de`.
|
|
62
|
+
|
|
63
|
+
### Commands
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
# Reference lists
|
|
67
|
+
components | networks | scopes [--lang de|en] [--index id|code]
|
|
68
|
+
station-types | station-settings | transgression-types [--lang de|en]
|
|
69
|
+
|
|
70
|
+
# Data (a time window is date-from/time-from .. date-to/time-to, hour 1..24)
|
|
71
|
+
airquality --date-from --time-from --date-to --time-to --station
|
|
72
|
+
airquality-limits
|
|
73
|
+
measures --date-from --time-from --date-to --time-to --station [--component] [--scope]
|
|
74
|
+
measures-limits
|
|
75
|
+
annual-balances --component --year [--lang] [--index]
|
|
76
|
+
transgressions --component --year [--lang] [--index]
|
|
77
|
+
thresholds --use airquality|measure [--lang] [--component] [--scope]
|
|
78
|
+
meta --use airquality|measure|transgression|annualbalance|map [--lang] [--date-from --date-to]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Examples
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Components (pollutants) in German, keyed by code
|
|
85
|
+
luftqualitaet components --lang de --index code
|
|
86
|
+
|
|
87
|
+
# Air-quality index for station 143 on one day
|
|
88
|
+
luftqualitaet airquality --date-from 2024-01-01 --time-from 1 \
|
|
89
|
+
--date-to 2024-01-01 --time-to 24 --station 143
|
|
90
|
+
|
|
91
|
+
# Annual PM10 (component 1) balance for 2023
|
|
92
|
+
luftqualitaet annual-balances --component 1 --year 2023 --lang de
|
|
93
|
+
|
|
94
|
+
# Metadata needed to pick stations/components for a measurement query
|
|
95
|
+
luftqualitaet meta --use measure --lang de
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Exit codes: `0` success, `4` on a `404` from the API, `1` for any other error, non-zero for usage errors.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Library usage
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { LuftqualitaetClient, LuftApiError } from "@maschinenlesbar.org/luftqualitaet-cli";
|
|
106
|
+
|
|
107
|
+
const client = new LuftqualitaetClient(); // defaults to https://www.umweltbundesamt.de
|
|
108
|
+
|
|
109
|
+
const components = await client.components({ lang: "de" });
|
|
110
|
+
const aq = await client.airquality({
|
|
111
|
+
date_from: "2024-01-01", time_from: 1, date_to: "2024-01-01", time_to: 24, station: 143,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await client.thresholds({ use: "measure" });
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof LuftApiError) console.error(err.status, err.detail);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Client options
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
new LuftqualitaetClient({
|
|
125
|
+
baseUrl: "https://www.umweltbundesamt.de",
|
|
126
|
+
timeoutMs: 15_000,
|
|
127
|
+
maxRetries: 3, // 429 / 503 are retried with linear backoff
|
|
128
|
+
maxResponseBytes: 50 << 20, // abort responses larger than 50 MiB (0 = unlimited)
|
|
129
|
+
userAgent: "my-app/1.0",
|
|
130
|
+
transport: customTransport, // inject your own HTTP transport
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Methods
|
|
135
|
+
|
|
136
|
+
`airquality`, `airqualityLimits`, `measures`, `measuresLimits`, `annualBalances`,
|
|
137
|
+
`transgressions`, `components`, `networks`, `scopes`, `stationTypes`, `stationSettings`,
|
|
138
|
+
`transgressionTypes`, `thresholds`, `meta`. The `LangValues` / `IndexValues` /
|
|
139
|
+
`MetaUseValues` / `ThresholdUseValues` enums are exported for reference.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
src/
|
|
147
|
+
client/
|
|
148
|
+
enums.ts # Lang / IndexKind / MetaUse / ThresholdUse value sets
|
|
149
|
+
types.ts # parameter objects (responses kept as faithful JsonObject)
|
|
150
|
+
query.ts # dependency-free query-string builder
|
|
151
|
+
http.ts # the Transport interface + default node:http/https transport
|
|
152
|
+
engine.ts # URL building, retry/backoff, redirects (cross-origin headers stripped), JSON decoding, error mapping
|
|
153
|
+
errors.ts # LuftError / LuftApiError / LuftNetworkError / LuftParseError
|
|
154
|
+
client.ts # LuftqualitaetClient — the air-data surface over the engine
|
|
155
|
+
cli/
|
|
156
|
+
io.ts # injectable I/O seam (stdout/stderr)
|
|
157
|
+
shared.ts # option parsers, global-option resolver, JSON renderer
|
|
158
|
+
commands/ # reference lists + data endpoints
|
|
159
|
+
program.ts # assembles the commander program from injectable deps
|
|
160
|
+
run.ts # parses argv -> exit code (no process.exit; testable)
|
|
161
|
+
index.ts # #! bin shim
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Design notes**
|
|
165
|
+
|
|
166
|
+
- The HTTP layer is a single `Transport` function (`(req) => Promise<HttpResponse>`). The default
|
|
167
|
+
uses `node:http`/`node:https`; tests inject a mock. This keeps the client free of any HTTP framework.
|
|
168
|
+
- The CLI is built around injectable `CliDeps` (client factory + I/O), so the whole program can be
|
|
169
|
+
driven in-process by tests with a mocked client and captured output — no subprocesses.
|
|
170
|
+
- UBA responses are index/data structures whose exact shape varies by endpoint, so they are returned
|
|
171
|
+
as faithful raw `JsonObject`s rather than partially-guessed types.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Testing
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm test # builds, then runs `node --test` over dist/test
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- **`query.test.ts`** — query-string serialisation.
|
|
182
|
+
- **`http.test.ts`** — the default transport against a real loopback `http.createServer`.
|
|
183
|
+
- **`engine.test.ts`** — URL building, JSON decoding, error mapping, 429/503 retry, redirects — mocked transport.
|
|
184
|
+
- **`client.test.ts`** — a parameterized table over all 15 client methods asserting URL/query mapping plus parameter pruning — mocked transport.
|
|
185
|
+
- **`cli.test.ts`** — end-to-end command parsing, domain validation (hour `1..24`, year `>= 2016`, positive ids, date format, conditional `meta` window) and exit codes — mocked client.
|
|
186
|
+
|
|
187
|
+
## Continuous integration
|
|
188
|
+
|
|
189
|
+
GitHub Actions workflows under `.github/workflows/`:
|
|
190
|
+
|
|
191
|
+
- **ci.yml** — type-check, build and test on Node 20/22/24 for every push and PR.
|
|
192
|
+
- **release.yml** — on a `v*` tag: verify the tag matches `package.json`, test, `npm pack`, and create a GitHub Release with the tarball.
|
|
193
|
+
- **publish.yml** — manual dispatch: publish to npm via OIDC **Trusted Publishing** (no stored `NPM_TOKEN`) with provenance.
|
|
194
|
+
- **docs.yml** — build TypeDoc API docs and deploy to GitHub Pages on each `v*` tag.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
**Dual-licensed** — use it under **either**:
|
|
199
|
+
|
|
200
|
+
- **[AGPL-3.0-or-later](LICENSE)** (default, free). Note the AGPL's §13 network
|
|
201
|
+
clause: if you run a modified version as a network service, you must offer that
|
|
202
|
+
modified source to the service's users.
|
|
203
|
+
- **Commercial license** (paid), for closed-source / proprietary or SaaS use
|
|
204
|
+
without the AGPL's obligations.
|
|
205
|
+
|
|
206
|
+
See **[LICENSING.md](LICENSING.md)** for details, and **[CONTRIBUTING.md](CONTRIBUTING.md)**
|
|
207
|
+
for the contribution policy (this project does not accept external code
|
|
208
|
+
contributions). Commercial enquiries: **sebs@2xs.org**.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAkExC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAsK1E"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { InvalidArgumentError } from "commander";
|
|
2
|
+
import { action, assertEnum, index, lang, parseHour, parseIndexArg, parseLangArg, parsePositiveIntArg, parseYear, renderJson, } from "../shared.js";
|
|
3
|
+
import { MetaUseValues, ThresholdUseValues } from "../../client/enums.js";
|
|
4
|
+
import { LuftError } from "../../client/errors.js";
|
|
5
|
+
/** commander value-parser: a real calendar date in `YYYY-MM-DD` form. */
|
|
6
|
+
function parseDate(value) {
|
|
7
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
8
|
+
if (!match) {
|
|
9
|
+
throw new InvalidArgumentError("Expected a date in YYYY-MM-DD format.");
|
|
10
|
+
}
|
|
11
|
+
const year = Number(match[1]);
|
|
12
|
+
const month = Number(match[2]);
|
|
13
|
+
const day = Number(match[3]);
|
|
14
|
+
// Reject impossible calendar dates (e.g. 2024-13-40, 0000-00-00). Round-tripping
|
|
15
|
+
// through Date catches month/day overflow including leap-year boundaries.
|
|
16
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
17
|
+
if (date.getUTCFullYear() !== year ||
|
|
18
|
+
date.getUTCMonth() !== month - 1 ||
|
|
19
|
+
date.getUTCDate() !== day) {
|
|
20
|
+
throw new InvalidArgumentError(`Expected a valid calendar date, got "${value}".`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Reject a reversed time window (start after end) locally, consistent with the
|
|
26
|
+
* other client-side guards (positive ids, year floor). The window is ordered by
|
|
27
|
+
* date first, then by hour-ending within the same date.
|
|
28
|
+
*/
|
|
29
|
+
function assertWindowOrdered(dateFrom, timeFrom, dateTo, timeTo) {
|
|
30
|
+
if (dateFrom > dateTo || (dateFrom === dateTo && timeFrom > timeTo)) {
|
|
31
|
+
throw new LuftError(`Window start (${dateFrom} ${timeFrom}:00) is after window end (${dateTo} ${timeTo}:00).`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Add the shared time-window + station options required by the data endpoints. */
|
|
35
|
+
function addWindowOptions(cmd) {
|
|
36
|
+
return cmd
|
|
37
|
+
.requiredOption("--date-from <YYYY-MM-DD>", "window start date", parseDate)
|
|
38
|
+
.requiredOption("--time-from <1-24>", "window start hour", parseHour)
|
|
39
|
+
.requiredOption("--date-to <YYYY-MM-DD>", "window end date", parseDate)
|
|
40
|
+
.requiredOption("--time-to <1-24>", "window end hour", parseHour)
|
|
41
|
+
.requiredOption("--station <id>", "station id", parsePositiveIntArg);
|
|
42
|
+
}
|
|
43
|
+
export function registerDataCommands(program, deps) {
|
|
44
|
+
addWindowOptions(program.command("airquality").description("Air-quality index data for a station/window")).action(action(deps, async ({ client, global, opts }) => {
|
|
45
|
+
assertWindowOrdered(String(opts["dateFrom"]), opts["timeFrom"], String(opts["dateTo"]), opts["timeTo"]);
|
|
46
|
+
renderJson(deps, global, await client.airquality({
|
|
47
|
+
date_from: String(opts["dateFrom"]),
|
|
48
|
+
time_from: opts["timeFrom"],
|
|
49
|
+
date_to: String(opts["dateTo"]),
|
|
50
|
+
time_to: opts["timeTo"],
|
|
51
|
+
station: opts["station"],
|
|
52
|
+
}));
|
|
53
|
+
}));
|
|
54
|
+
program
|
|
55
|
+
.command("airquality-limits")
|
|
56
|
+
.description("Available date range per station for air-quality data")
|
|
57
|
+
.action(action(deps, async ({ client, global }) => {
|
|
58
|
+
renderJson(deps, global, await client.airqualityLimits());
|
|
59
|
+
}));
|
|
60
|
+
addWindowOptions(program
|
|
61
|
+
.command("measures")
|
|
62
|
+
.description("Raw measurements for a station/window")
|
|
63
|
+
.option("--component <id>", "component id", parsePositiveIntArg)
|
|
64
|
+
.option("--scope <id>", "scope id", parsePositiveIntArg)).action(action(deps, async ({ client, global, opts }) => {
|
|
65
|
+
assertWindowOrdered(String(opts["dateFrom"]), opts["timeFrom"], String(opts["dateTo"]), opts["timeTo"]);
|
|
66
|
+
renderJson(deps, global, await client.measures({
|
|
67
|
+
date_from: String(opts["dateFrom"]),
|
|
68
|
+
time_from: opts["timeFrom"],
|
|
69
|
+
date_to: String(opts["dateTo"]),
|
|
70
|
+
time_to: opts["timeTo"],
|
|
71
|
+
station: opts["station"],
|
|
72
|
+
component: opts["component"],
|
|
73
|
+
scope: opts["scope"],
|
|
74
|
+
}));
|
|
75
|
+
}));
|
|
76
|
+
program
|
|
77
|
+
.command("measures-limits")
|
|
78
|
+
.description("Available date range per scope/component/station")
|
|
79
|
+
.action(action(deps, async ({ client, global }) => {
|
|
80
|
+
renderJson(deps, global, await client.measuresLimits());
|
|
81
|
+
}));
|
|
82
|
+
const yearComponent = [
|
|
83
|
+
{
|
|
84
|
+
name: "annual-balances",
|
|
85
|
+
method: "annualBalances",
|
|
86
|
+
desc: "Annual tabulations for a component and year",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "transgressions",
|
|
90
|
+
method: "transgressions",
|
|
91
|
+
desc: "Exceedances for a component and year",
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
for (const { name, method, desc } of yearComponent) {
|
|
95
|
+
program
|
|
96
|
+
.command(name)
|
|
97
|
+
.description(desc)
|
|
98
|
+
.requiredOption("--component <id>", "component id", parsePositiveIntArg)
|
|
99
|
+
.requiredOption("--year <YYYY>", "year (>= 2016)", parseYear)
|
|
100
|
+
.option("--lang <lang>", "de | en", parseLangArg)
|
|
101
|
+
.option("--index <index>", "id | code", parseIndexArg)
|
|
102
|
+
.action(action(deps, async ({ client, global, opts }) => {
|
|
103
|
+
renderJson(deps, global, await client[method]({
|
|
104
|
+
component: opts["component"],
|
|
105
|
+
year: opts["year"],
|
|
106
|
+
lang: lang(opts),
|
|
107
|
+
index: index(opts),
|
|
108
|
+
}));
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
program
|
|
112
|
+
.command("thresholds")
|
|
113
|
+
.description("Thresholds for a use (airquality | measure)")
|
|
114
|
+
.requiredOption("--use <use>", `${ThresholdUseValues.join(" | ")}`)
|
|
115
|
+
.option("--lang <lang>", "de | en", parseLangArg)
|
|
116
|
+
.option("--component <id>", "component id", parsePositiveIntArg)
|
|
117
|
+
.option("--scope <id>", "scope id", parsePositiveIntArg)
|
|
118
|
+
.action(action(deps, async ({ client, global, opts }) => {
|
|
119
|
+
renderJson(deps, global, await client.thresholds({
|
|
120
|
+
use: assertEnum(String(opts["use"]), ThresholdUseValues, "use"),
|
|
121
|
+
lang: lang(opts),
|
|
122
|
+
component: opts["component"],
|
|
123
|
+
scope: opts["scope"],
|
|
124
|
+
}));
|
|
125
|
+
}));
|
|
126
|
+
program
|
|
127
|
+
.command("meta")
|
|
128
|
+
.description("Combined metadata for a use")
|
|
129
|
+
.requiredOption("--use <use>", `${MetaUseValues.join(" | ")}`)
|
|
130
|
+
.option("--lang <lang>", "de | en", parseLangArg)
|
|
131
|
+
.option("--date-from <YYYY-MM-DD>", "required when use=airquality", parseDate)
|
|
132
|
+
.option("--date-to <YYYY-MM-DD>", "required when use=airquality", parseDate)
|
|
133
|
+
.option("--time-from <1-24>", "window start hour", parseHour)
|
|
134
|
+
.option("--time-to <1-24>", "window end hour", parseHour)
|
|
135
|
+
.action(action(deps, async ({ client, global, opts }) => {
|
|
136
|
+
const use = assertEnum(String(opts["use"]), MetaUseValues, "use");
|
|
137
|
+
const dateFrom = opts["dateFrom"];
|
|
138
|
+
const dateTo = opts["dateTo"];
|
|
139
|
+
if (use === "airquality" && (dateFrom === undefined || dateTo === undefined)) {
|
|
140
|
+
throw new LuftError("meta --use airquality requires --date-from and --date-to.");
|
|
141
|
+
}
|
|
142
|
+
renderJson(deps, global, await client.meta({
|
|
143
|
+
use,
|
|
144
|
+
lang: lang(opts),
|
|
145
|
+
date_from: dateFrom,
|
|
146
|
+
date_to: dateTo,
|
|
147
|
+
time_from: opts["timeFrom"],
|
|
148
|
+
time_to: opts["timeTo"],
|
|
149
|
+
}));
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../../../src/cli/commands/data.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAuC/E"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { action, index, lang, parseIndexArg, parseLangArg, renderJson } from "../shared.js";
|
|
2
|
+
export function registerReferenceCommands(program, deps) {
|
|
3
|
+
const indexed = [
|
|
4
|
+
{ name: "components", method: "components", desc: "List measured components (pollutants)" },
|
|
5
|
+
{ name: "networks", method: "networks", desc: "List measurement networks" },
|
|
6
|
+
{ name: "scopes", method: "scopes", desc: "List measurement scopes" },
|
|
7
|
+
];
|
|
8
|
+
for (const { name, method, desc } of indexed) {
|
|
9
|
+
program
|
|
10
|
+
.command(name)
|
|
11
|
+
.description(desc)
|
|
12
|
+
.option("--lang <lang>", "de | en", parseLangArg)
|
|
13
|
+
.option("--index <index>", "id | code", parseIndexArg)
|
|
14
|
+
.action(action(deps, async ({ client, global, opts }) => {
|
|
15
|
+
renderJson(deps, global, await client[method]({ lang: lang(opts), index: index(opts) }));
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
const simple = [
|
|
19
|
+
{ name: "station-types", method: "stationTypes", desc: "List station types" },
|
|
20
|
+
{ name: "station-settings", method: "stationSettings", desc: "List station settings" },
|
|
21
|
+
{ name: "transgression-types", method: "transgressionTypes", desc: "List exceedance types" },
|
|
22
|
+
];
|
|
23
|
+
for (const { name, method, desc } of simple) {
|
|
24
|
+
program
|
|
25
|
+
.command(name)
|
|
26
|
+
.description(desc)
|
|
27
|
+
.option("--lang <lang>", "de | en", parseLangArg)
|
|
28
|
+
.action(action(deps, async ({ client, global, opts }) => {
|
|
29
|
+
renderJson(deps, global, await client[method](lang(opts)));
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../../../src/cli/commands/reference.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5F,MAAM,UAAU,yBAAyB,CAAC,OAAgB,EAAE,IAAa;IACvE,MAAM,OAAO,GAAmF;QAC9F,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,uCAAuC,EAAE;QAC3F,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,2BAA2B,EAAE;QAC3E,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,yBAAyB,EAAE;KACtE,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC;QAC7C,OAAO;aACJ,OAAO,CAAC,IAAI,CAAC;aACb,WAAW,CAAC,IAAI,CAAC;aACjB,MAAM,CAAC,eAAe,EAAE,SAAS,EAAE,YAAY,CAAC;aAChD,MAAM,CAAC,iBAAiB,EAAE,WAAW,EAAE,aAAa,CAAC;aACrD,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;YAC9C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3F,CAAC,CAAC,CACH,CAAC;IACN,CAAC;IAED,MAAM,MAAM,GAIN;QACJ,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,oBAAoB,EAAE;QAC7E,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,uBAAuB,EAAE;QACtF,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,uBAAuB,EAAE;KAC7F,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;QAC5C,OAAO;aACJ,OAAO,CAAC,IAAI,CAAC;aACb,WAAW,CAAC,IAAI,CAAC;aACjB,MAAM,CAAC,eAAe,EAAE,SAAS,EAAE,YAAY,CAAC;aAChD,MAAM,CACL,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;YAC9C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC,CAAC,CACH,CAAC;IACN,CAAC;AACH,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,13 @@
|
|
|
1
|
+
import type { LuftqualitaetClient } 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
|
+
}
|
|
7
|
+
export interface CliDeps {
|
|
8
|
+
io: CliIO;
|
|
9
|
+
/** Build a client from the resolved global options (injectable for tests). */
|
|
10
|
+
createClient(options: EngineOptions): LuftqualitaetClient;
|
|
11
|
+
}
|
|
12
|
+
export declare const defaultIO: CliIO;
|
|
13
|
+
//# sourceMappingURL=io.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../../../src/cli/io.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,KAAK;IACpB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,KAAK,CAAC;IACV,8EAA8E;IAC9E,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,mBAAmB,CAAC;CAC3D;AAED,eAAO,MAAM,SAAS,EAAE,KAGvB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
export const defaultIO = {
|
|
4
|
+
out: (text) => process.stdout.write(text + "\n"),
|
|
5
|
+
err: (text) => process.stderr.write(text + "\n"),
|
|
6
|
+
};
|
|
7
|
+
//# 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;AAgBjF,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;CACjD,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,CA0BjE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { LuftqualitaetClient } from "../client/client.js";
|
|
9
|
+
import { parseIntArg } from "./shared.js";
|
|
10
|
+
import { registerReferenceCommands } from "./commands/reference.js";
|
|
11
|
+
import { registerDataCommands } from "./commands/data.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 LuftqualitaetClient(options),
|
|
33
|
+
};
|
|
34
|
+
export function buildProgram(deps = defaultDeps) {
|
|
35
|
+
const program = new Command();
|
|
36
|
+
program
|
|
37
|
+
.name("luftqualitaet")
|
|
38
|
+
.description("CLI for the open Umweltbundesamt Air Data API " +
|
|
39
|
+
"(https://www.umweltbundesamt.de/api/air_data/v3)")
|
|
40
|
+
.version(VERSION)
|
|
41
|
+
.option("--base-url <url>", "API base URL", "https://www.umweltbundesamt.de")
|
|
42
|
+
.option("--timeout <ms>", "per-request timeout in milliseconds", parseIntArg)
|
|
43
|
+
.option("--user-agent <ua>", "User-Agent header value")
|
|
44
|
+
.option("--max-retries <n>", "retries for transient 429/503 responses", 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
|
+
.showHelpAfterError();
|
|
48
|
+
registerReferenceCommands(program, deps);
|
|
49
|
+
registerDataCommands(program, deps);
|
|
50
|
+
return program;
|
|
51
|
+
}
|
|
52
|
+
//# 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,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,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,mBAAmB,CAAC,OAAO,CAAC;CAC5D,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,OAAgB,WAAW;IACtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,eAAe,CAAC;SACrB,WAAW,CACV,gDAAgD;QAC9C,kDAAkD,CACrD;SACA,OAAO,CAAC,OAAO,CAAC;SAChB,MAAM,CAAC,kBAAkB,EAAE,cAAc,EAAE,gCAAgC,CAAC;SAC5E,MAAM,CAAC,gBAAgB,EAAE,qCAAqC,EAAE,WAAW,CAAC;SAC5E,MAAM,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;SACtD,MAAM,CAAC,mBAAmB,EAAE,yCAAyC,EAAE,WAAW,CAAC;SACnF,MAAM,CACL,0BAA0B,EAC1B,kEAAkE,EAClE,WAAW,CACZ;SACA,MAAM,CAAC,WAAW,EAAE,uDAAuD,CAAC;SAC5E,kBAAkB,EAAE,CAAC;IAExB,yBAAyB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzC,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,CAyBtF"}
|