@tikoci/centrs 0.1.9-next.0 → 0.1.9-next.23
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/CHANGELOG.md +177 -40
- package/GLOSSARY.txt +32 -0
- package/README.md +12 -8
- package/SECURITY.md +11 -3
- package/commands/api/README.md +39 -15
- package/commands/api/examples.md +44 -6
- package/commands/execute/README.md +108 -14
- package/commands/execute/examples.md +137 -3
- package/commands/explain/README.md +430 -129
- package/commands/explain/examples.md +260 -1
- package/commands/mcp/README.md +11 -0
- package/commands/mcp/examples.md +6 -0
- package/commands/retrieve/README.md +30 -13
- package/commands/retrieve/examples.md +10 -6
- package/commands/transfer/examples.md +9 -0
- package/docs/CLI.md +8 -8
- package/docs/CONSTITUTION.md +29 -2
- package/docs/MATRIX.md +26 -13
- package/docs/errors/cdb/empty-group.md +19 -3
- package/docs/errors/cdb/empty-selection.md +9 -3
- package/docs/errors/cmr/caller-policy-not-enforced.md +46 -0
- package/docs/errors/cmr/empty-selection.md +17 -3
- package/docs/errors/cmr/rule-selects-every-device.md +32 -0
- package/docs/errors/cmr/selection-includes-controller.md +27 -0
- package/docs/errors/usage/flag-after-separator.md +34 -0
- package/package.json +10 -5
- package/src/api-fanout.ts +11 -1
- package/src/api.ts +205 -65
- package/src/btest.ts +7 -2
- package/src/cli/api.ts +30 -16
- package/src/cli/common.ts +180 -1
- package/src/cli/devices.ts +0 -2
- package/src/cli/discover.ts +0 -1
- package/src/cli/execute.ts +44 -20
- package/src/cli/explain.ts +2 -0
- package/src/cli/mcp.ts +0 -1
- package/src/cli/retrieve.ts +40 -18
- package/src/cli/settings.ts +0 -3
- package/src/cli/terminal.ts +0 -2
- package/src/cli/transfer.ts +10 -8
- package/src/cli/version.ts +87 -0
- package/src/cli.ts +5 -3
- package/src/core/cmr-fleet.ts +486 -0
- package/src/core/cmr.ts +78 -0
- package/src/core/envelope.ts +32 -3
- package/src/core/error-catalog.ts +25 -3
- package/src/core/execute-command.ts +144 -19
- package/src/core/fanout.ts +130 -6
- package/src/errors.ts +2 -1
- package/src/execute-cmr.ts +95 -78
- package/src/execute-fanout.ts +11 -1
- package/src/execute.ts +105 -23
- package/src/explain/args.ts +69 -8
- package/src/explain/blocks.ts +3 -4
- package/src/explain/brace-slots.ts +5 -0
- package/src/explain/catalog.ts +13 -6
- package/src/explain/defects.ts +1 -0
- package/src/explain/group-faults.ts +927 -0
- package/src/explain/highlight-projection.ts +10 -8
- package/src/explain/operators.ts +20 -19
- package/src/explain/pathresolve.ts +102 -20
- package/src/explain/quoted-string.ts +41 -6
- package/src/explain/run-tokens.ts +223 -0
- package/src/explain/scope-brace.ts +103 -6
- package/src/explain/segment.ts +41 -3
- package/src/explain/spaced-args.ts +116 -0
- package/src/explain/symbols.ts +17 -1
- package/src/explain/transport.ts +13 -0
- package/src/explain/verbsplit.ts +14 -188
- package/src/explain/write.ts +8 -1
- package/src/explain.ts +378 -18
- package/src/index.ts +18 -1
- package/src/mcp/safety.ts +11 -0
- package/src/mcp/tools.ts +4 -1
- package/src/offline-gate.ts +30 -9
- package/src/protocols/btest-session.ts +30 -10
- package/src/protocols/mac-telnet-console.ts +16 -0
- package/src/resolver/cdb.ts +13 -1
- package/src/resolver/index.ts +4 -0
- package/src/resolver/selection.ts +37 -1
- package/src/resolver/target.ts +96 -8
- package/src/retrieve-fanout.ts +4 -1
- package/src/retrieve-follow.ts +29 -33
- package/src/retrieve-sample.ts +14 -8
- package/src/retrieve-stream.ts +38 -6
- package/src/retrieve.ts +73 -59
- package/src/transfer-fanout.ts +8 -1
- package/src/transfer.ts +15 -19
|
@@ -2,8 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
The named CDB group contains no records.
|
|
4
4
|
|
|
5
|
+
## Typical trigger
|
|
6
|
+
|
|
7
|
+
A fan-out selected only by `--group` (no other selector) whose group matched
|
|
8
|
+
no record: a misspelled or case-mismatched group name, or a group whose last
|
|
9
|
+
member was removed.
|
|
10
|
+
|
|
11
|
+
- For a **read**, it is a warning on an `ok: true` fan-out envelope with
|
|
12
|
+
`summary { total: 0, ok: 0, failed: 0 }` and exit code 0.
|
|
13
|
+
- For a **write-shaped** fan-out (`execute` write, a mutating `api` method,
|
|
14
|
+
`transfer upload|remove|mkdir|copy`) it is an outer error (`ok: false`, exit
|
|
15
|
+
code 1), with or without `--yes`. `context.write` is `true`.
|
|
16
|
+
|
|
17
|
+
Any other empty selection (`--where`, `--all`, positionals, mixed) is
|
|
18
|
+
[`cdb/empty-selection`](empty-selection.md).
|
|
19
|
+
|
|
5
20
|
## Fix
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
for
|
|
22
|
+
Run `centrs devices groups` to see the groups that exist and their member
|
|
23
|
+
counts, then re-run with the right name. See
|
|
24
|
+
[`docs/CONSTITUTION.md`](../../CONSTITUTION.md) (Target selection) for the
|
|
25
|
+
selector grammar.
|
|
@@ -5,9 +5,15 @@ The target selection matched no CDB records.
|
|
|
5
5
|
## Fix
|
|
6
6
|
|
|
7
7
|
A fan-out selection involving `--where` / `--all` / `--default` / positionals (on
|
|
8
|
-
its own or mixed with `--group`) resolved to zero targets.
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
its own or mixed with `--group`) resolved to zero targets.
|
|
9
|
+
|
|
10
|
+
- For a **read**, it is a warning on an `ok: true` fan-out envelope with
|
|
11
|
+
`summary { total: 0, ok: 0, failed: 0 }` and exit code 0: there was simply
|
|
12
|
+
nothing to read.
|
|
13
|
+
- For a **write-shaped** fan-out (`execute` write, a mutating `api` method,
|
|
14
|
+
`transfer upload|remove|mkdir|copy`) it is an outer error (`ok: false`, exit
|
|
15
|
+
code 1), with or without `--yes`, so a mistyped selector does not look like
|
|
16
|
+
a successful bulk write. `context.write` is `true`.
|
|
11
17
|
|
|
12
18
|
A **group-only** empty (bare `--group`, no other selector) is reported as
|
|
13
19
|
[`cdb/empty-group`](empty-group.md) instead, naming the group that matched nothing;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# `cmr/caller-policy-not-enforced`
|
|
2
|
+
|
|
3
|
+
A warning on `execute --cmr`, and on `/cmr/device/run-script` or
|
|
4
|
+
`/cmr/device/reboot` run directly with `execute` or `api`: the controller login
|
|
5
|
+
lacks a policy that the command runs with on the clients, and RouterOS does
|
|
6
|
+
not check it. The login does not limit what a CMR command can change.
|
|
7
|
+
|
|
8
|
+
## Typical trigger
|
|
9
|
+
|
|
10
|
+
A write through `--cmr` with a controller login whose group lacks `write`,
|
|
11
|
+
`test` or `reboot`, such as a "reader" account given to an agent. CMR runs
|
|
12
|
+
every `run-script` script on each selected client with `read`, `write`,
|
|
13
|
+
`test` and `reboot`, the controller's own row included. On 7.26beta1,
|
|
14
|
+
RouterOS checked only that the caller had `read` and `api`. These results
|
|
15
|
+
come from the CHR reproduction on #456 (REST, two-CHR lab, 2026-10-13); the
|
|
16
|
+
same `run-script` write was seen on real hardware, and the integration test
|
|
17
|
+
re-proves it (CMR15 in `commands/execute/examples.md`):
|
|
18
|
+
|
|
19
|
+
| Controller login | Direct write or reboot | Through CMR |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `read,api,rest-api` | `not enough permissions (9)` | `run-script` wrote to a client and rebooted it |
|
|
22
|
+
| `read,test,api,rest-api` | `not enough permissions (9)` | `run-script` wrote to the controller's own row; `/cmr/device/reboot` rebooted a client |
|
|
23
|
+
|
|
24
|
+
MikroTik [says](https://forum.mikrotik.com/t/7-26beta-development-is-released/273582/139) this is fixed for 7.26beta2: a `read` group will not
|
|
25
|
+
be allowed to do it. That has not been checked on a beta2 build yet. centrs
|
|
26
|
+
still warns on any version, because the warning reads the login's policy, not
|
|
27
|
+
the RouterOS version. On a build that refuses the write, the members fail
|
|
28
|
+
instead and the warning only says what the login lacks.
|
|
29
|
+
|
|
30
|
+
`context` names the login (`username`, `group`), the policies it lacks
|
|
31
|
+
(`missing`) and the ones the command runs with (`runsWith`): `read`, `write`,
|
|
32
|
+
`test` and `reboot` for `run-script`, and `reboot` for `/cmr/device/reboot`.
|
|
33
|
+
centrs reads the policy once per run, from the login's own `/user` row and
|
|
34
|
+
`/user/group`, and only for a write. When that read fails (for example a
|
|
35
|
+
RADIUS login with no `/user` row) there is no warning. Other fleet commands
|
|
36
|
+
(`/cmr/device/upgrade`, `pair`, the rule menus) were not measured, so they
|
|
37
|
+
carry no such warning.
|
|
38
|
+
|
|
39
|
+
## Fix
|
|
40
|
+
|
|
41
|
+
Nothing failed; this is information. Do not treat a controller login as
|
|
42
|
+
read-only once the `cmr` package is installed (on 7.26beta1 at least). The
|
|
43
|
+
gates that hold are centrs' own: pass `--yes` only when you intend the write
|
|
44
|
+
on every selected client, and over MCP keep the controller's record at
|
|
45
|
+
`mcp=ro` (the default), which refuses CMR fleet commands outright. Give an
|
|
46
|
+
agent a login on the controller only if it may change every paired device.
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
# `cmr/empty-selection`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`/cmr/device` inventory, so nothing ran.
|
|
5
|
-
|
|
3
|
+
The `--labels` expression of `execute --cmr` matched no device in the
|
|
4
|
+
controller's `/cmr/device` inventory, so nothing ran.
|
|
5
|
+
|
|
6
|
+
- For a **read**, it is a warning: `ok: true` with an empty selection and exit
|
|
7
|
+
code 0, the same as an empty CDB group. A collector that polls a sometimes
|
|
8
|
+
empty label stays quiet.
|
|
9
|
+
- For a **write-shaped** command it is an error (`ok: false`, exit code 1),
|
|
10
|
+
with or without `--yes`, so a misspelled label does not look like a
|
|
11
|
+
successful bulk write. RouterOS refuses an empty selection the same way
|
|
12
|
+
(`no devices selected`). `context.write` is `true`.
|
|
13
|
+
|
|
14
|
+
The same code is a **warning** on a CMR fleet command run directly with
|
|
15
|
+
`execute` or `api`, such as `/cmr/vlan/add labels=EDGE` or
|
|
16
|
+
`/cmr/device/reboot labels=Edge`: by the controller's inventory now, its
|
|
17
|
+
selection reaches no device. RouterOS refuses an empty selection for a
|
|
18
|
+
`/cmr/device` command itself; it accepts a rule whose labels match nothing,
|
|
19
|
+
and the rule applies to devices that get the label later.
|
|
6
20
|
|
|
7
21
|
## Typical trigger
|
|
8
22
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `cmr/rule-selects-every-device`
|
|
2
|
+
|
|
3
|
+
A warning on `execute` or `api` to a CMR controller: a `/cmr/vlan`,
|
|
4
|
+
`/cmr/wifi`, `/cmr/wifi/radio` or `/cmr/upgrade` rule command has no device
|
|
5
|
+
`labels`, so the rule applies to every device the controller manages, the
|
|
6
|
+
controller included. For `/cmr/vlan` it fires only when `port-labels` is
|
|
7
|
+
missing too.
|
|
8
|
+
|
|
9
|
+
## Typical trigger
|
|
10
|
+
|
|
11
|
+
- `/cmr/vlan/add vlan-ids=10` with no `labels` and no `port-labels`. The
|
|
12
|
+
manual warns that such a rule moves every port of every connected device
|
|
13
|
+
into `cmr-bridge`, the controller included, which cuts the whole fleet's
|
|
14
|
+
management access.
|
|
15
|
+
- `/cmr/wifi/add ssid=Office` with no `labels`, or labels that are only band
|
|
16
|
+
labels (`+5ghz`). The network takes over the radios of every device.
|
|
17
|
+
- `set`, `remove`, `enable` or `disable` of an existing rule that has no
|
|
18
|
+
`labels`. Its reach is every device too.
|
|
19
|
+
|
|
20
|
+
centrs reads the rule's `labels` from the command (`add`) or from the rule
|
|
21
|
+
itself (the other verbs). A rule added without `labels` stores none (7.26beta1
|
|
22
|
+
CHR). The command still runs as typed; the warning rides the envelope, and
|
|
23
|
+
the write gate's prompt and `usage/confirmation-required` summary repeat it.
|
|
24
|
+
|
|
25
|
+
## Fix
|
|
26
|
+
|
|
27
|
+
Give the rule explicit selectors, for example
|
|
28
|
+
`/cmr/vlan/add labels=office-ap port-labels=ether1 vlan-ids=10 role=trunk`.
|
|
29
|
+
Check which devices a label selects with
|
|
30
|
+
`centrs retrieve <controller> /cmr/device` (`labels`, `auto-labels`). Answer
|
|
31
|
+
`yes` or pass `--yes` only if every device, the controller included, is
|
|
32
|
+
intended.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# `cmr/selection-includes-controller`
|
|
2
|
+
|
|
3
|
+
A warning on `execute` or `api` to a CMR controller: a fleet command such as
|
|
4
|
+
`/cmr/device/reboot` or `/cmr/device/run-script` selects the controller's own
|
|
5
|
+
`/cmr/device` row (`controller=true`) as well as its clients. The command acts
|
|
6
|
+
on the controller too.
|
|
7
|
+
|
|
8
|
+
## Typical trigger
|
|
9
|
+
|
|
10
|
+
- `labels=all`: per the manual, `all` selects every device, the CMR server
|
|
11
|
+
included.
|
|
12
|
+
- An exclusion-only expression such as `labels=-core`, which starts from every
|
|
13
|
+
device.
|
|
14
|
+
- A label the controller also carries, such as its architecture (`x86`) or
|
|
15
|
+
RouterOS version, from `auto-labels`.
|
|
16
|
+
|
|
17
|
+
centrs reads the controller's inventory and evaluates the manual's label
|
|
18
|
+
rules over each row's `labels` and `auto-labels`. On a 7.26beta1 lab this
|
|
19
|
+
matched the device's own selection for every expression tried. The result is
|
|
20
|
+
the controller's current view: a disconnected device's `auto-labels` can be
|
|
21
|
+
stale. `context` names the controller and the number of devices selected.
|
|
22
|
+
|
|
23
|
+
## Fix
|
|
24
|
+
|
|
25
|
+
Nothing failed; this is information. To leave the controller out, exclude it
|
|
26
|
+
explicitly, for example `labels=all,-<controller identity>` (its identity is
|
|
27
|
+
an automatic label while `/cmr auto-labels` includes identity).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# `usage/flag-after-separator`
|
|
2
|
+
|
|
3
|
+
A centrs flag was written after `--`, where every token is RouterOS input.
|
|
4
|
+
|
|
5
|
+
## Fix
|
|
6
|
+
|
|
7
|
+
Move the flag before `--`. The error's remediation prints the corrected
|
|
8
|
+
command line:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
centrs execute r1 -- '/system/identity/print' --json # refused
|
|
12
|
+
centrs execute r1 --json -- '/system/identity/print' # fixed
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
A bare `--` ends centrs option parsing, so without this check a trailing
|
|
16
|
+
`--json` was sent to RouterOS and failed there as `unknown-attribute`. The check
|
|
17
|
+
covers `execute` (including `--cmr`) and `explain`. It matches only a token that
|
|
18
|
+
is exactly one of the command's long flags (or its `--flag=value` spelling);
|
|
19
|
+
short aliases such as `-u` pass through as RouterOS input. If RouterOS really
|
|
20
|
+
needs such a token, put it inside the quoted command. Password values are
|
|
21
|
+
replaced by `<password>` in the printed command line.
|
|
22
|
+
Credentials embedded in a positional target or a `--host` value are redacted
|
|
23
|
+
to `***@host`, including when `--host` is moved before the separator.
|
|
24
|
+
Credentials in an explicit URL are redacted even when its colon is percent-encoded;
|
|
25
|
+
bare CDB identities such as `rtr@site` remain unchanged. A bare target with
|
|
26
|
+
a percent-encoded colon is masked in diagnostics while remaining eligible
|
|
27
|
+
for CDB identity lookup.
|
|
28
|
+
|
|
29
|
+
For boolean flags that accept no value (such as `--json` and `--yes`), the
|
|
30
|
+
error explains how to remove an unsupported `=value` rather than printing
|
|
31
|
+
a command that the parser would reject.
|
|
32
|
+
|
|
33
|
+
See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
|
|
34
|
+
contract.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tikoci/centrs",
|
|
3
|
-
"version": "0.1.9-next.
|
|
3
|
+
"version": "0.1.9-next.23",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"repository": {
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://github.com/tikoci/centrs#readme",
|
|
11
11
|
"bugs": "https://github.com/tikoci/centrs/issues",
|
|
12
|
-
"packageManager": "bun@1.
|
|
12
|
+
"packageManager": "bun@1.4.2",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@biomejs/biome": "2.5.15",
|
|
16
16
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.7",
|
|
17
|
-
"@types/bun": "1.
|
|
17
|
+
"@types/bun": "1.4.2",
|
|
18
18
|
"cloc": "^2.6.0-cloc",
|
|
19
19
|
"cspell": "^10.3.6",
|
|
20
20
|
"has-flag": "4.0.0",
|
|
@@ -74,12 +74,14 @@
|
|
|
74
74
|
"lint:biome": "biome check .",
|
|
75
75
|
"lint:fix": "biome check --write .",
|
|
76
76
|
"lint:secretlint": "secretlint \"**/*\"",
|
|
77
|
+
"lint:actions": "uvx zizmor --offline .",
|
|
77
78
|
"test": "bun test",
|
|
78
79
|
"test:smoke": "bun test test/integration/cli-smoke.test.ts",
|
|
79
80
|
"test:integration:fast": "bun test test/integration/devices.test.ts test/integration/settings.test.ts",
|
|
80
81
|
"test:integration:settings": "bun test test/integration/settings.test.ts",
|
|
81
|
-
"
|
|
82
|
-
"test:integration": "CENTRS_RUN_FAST_INTEGRATION=1 bun test test/integration/",
|
|
82
|
+
"chr:prefetch": "bun run scripts/chr-prefetch.ts",
|
|
83
|
+
"test:integration:smoke": "bun run chr:prefetch && CENTRS_RUN_FAST_INTEGRATION=1 bun test test/integration/chr-smoke.test.ts",
|
|
84
|
+
"test:integration": "bun run chr:prefetch && CENTRS_RUN_FAST_INTEGRATION=1 bun test test/integration/",
|
|
83
85
|
"test:integration:stable": "CENTRS_CHR_CHANNEL=stable bun run test:integration",
|
|
84
86
|
"test:integration:long-term": "CENTRS_CHR_CHANNEL=long-term bun run test:integration",
|
|
85
87
|
"test:integration:testing": "CENTRS_CHR_CHANNEL=testing bun run test:integration",
|
|
@@ -101,11 +103,14 @@
|
|
|
101
103
|
"explain:catalog": "bun run scripts/gen-explain-catalog.ts",
|
|
102
104
|
"explain:catalog:check": "bun run scripts/gen-explain-catalog.ts --check",
|
|
103
105
|
"explain:corpus-census": "bun run scripts/explain-corpus-census.ts",
|
|
106
|
+
"explain:conformance": "bun run scripts/explain-conformance.ts",
|
|
107
|
+
"explain:conformance:check": "bun run scripts/explain-conformance.ts --check",
|
|
104
108
|
"explain:highlight-agreement": "bun run scripts/explain-highlight-agreement.ts",
|
|
105
109
|
"explain:highlight-agreement:check": "bun run scripts/explain-highlight-agreement.ts --check",
|
|
106
110
|
"explain:highlight-agreement:readme": "bun run scripts/explain-highlight-agreement.ts --readme",
|
|
107
111
|
"explain:highlight-agreement:readme:check": "bun run scripts/explain-highlight-agreement.ts --readme --check",
|
|
108
112
|
"explain:highlight-slice": "bun run scripts/explain-highlight-slice.ts",
|
|
113
|
+
"explain:highlight-slice:check": "bun run scripts/explain-highlight-slice.ts --check",
|
|
109
114
|
"explain:menus": "bun run scripts/gen-explain-menus.ts",
|
|
110
115
|
"explain:operator-census": "bun run scripts/explain-operator-census.ts",
|
|
111
116
|
"explain:operator-census:check": "bun run scripts/explain-operator-census.ts --check",
|
package/src/api-fanout.ts
CHANGED
|
@@ -62,9 +62,11 @@ import {
|
|
|
62
62
|
import { promptForWriteConfirmation } from "./execute.ts";
|
|
63
63
|
import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
|
|
64
64
|
import {
|
|
65
|
+
emptyWriteSelectionError,
|
|
65
66
|
expandSelection,
|
|
66
67
|
isDefaultRecordTarget,
|
|
67
68
|
loadEnvFileDefaults,
|
|
69
|
+
redactTargetInput,
|
|
68
70
|
type SelectionExpansion,
|
|
69
71
|
type SelectionMember,
|
|
70
72
|
type SelectionResolveInput,
|
|
@@ -87,6 +89,8 @@ export interface ApiFanoutOperationMeta {
|
|
|
87
89
|
concurrency: number;
|
|
88
90
|
summary: FanoutSummary;
|
|
89
91
|
request: ApiRequestSummary;
|
|
92
|
+
/** Hoisted from the targets when every target used the same login (#461). */
|
|
93
|
+
auth?: { username?: string; passwordProvided: boolean };
|
|
90
94
|
selection: ApiSelectionSummary;
|
|
91
95
|
}
|
|
92
96
|
|
|
@@ -125,7 +129,7 @@ function summarizeSelection(selection: TargetSelection): ApiSelectionSummary {
|
|
|
125
129
|
where: selection.where.map((clause) => `${clause.key}=${clause.value}`),
|
|
126
130
|
all: selection.all,
|
|
127
131
|
default: selection.default,
|
|
128
|
-
positionals: selection.positionals,
|
|
132
|
+
positionals: selection.positionals.map((value) => redactTargetInput(value)),
|
|
129
133
|
quickchr: selection.quickchr,
|
|
130
134
|
};
|
|
131
135
|
}
|
|
@@ -242,6 +246,12 @@ export async function apiFanout(
|
|
|
242
246
|
const concurrency = resolveFanoutConcurrency(options.concurrency, via);
|
|
243
247
|
|
|
244
248
|
if (expansion.empty) {
|
|
249
|
+
if (requestSummary.write) {
|
|
250
|
+
throw emptyWriteSelectionError(
|
|
251
|
+
expansion,
|
|
252
|
+
`${method} ${requestSummary.path}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
245
255
|
return buildApiFanoutSuccess({
|
|
246
256
|
summary: { total: 0, ok: 0, failed: 0 },
|
|
247
257
|
targets: [],
|