@tikoci/centrs 0.1.3 → 0.1.4
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 +39 -0
- package/GLOSSARY.txt +213 -0
- package/LICENSE +21 -0
- package/README.md +36 -8
- package/commands/AGENTS.md +7 -1
- package/commands/api/README.md +19 -18
- package/commands/api/examples.md +15 -0
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +6 -1
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +41 -29
- package/commands/execute/examples.md +41 -3
- package/commands/explain/README.md +1716 -0
- package/commands/explain/examples.md +606 -0
- package/commands/retrieve/README.md +25 -27
- package/commands/retrieve/examples.md +16 -0
- package/commands/transfer/README.md +28 -20
- package/commands/transfer/examples.md +29 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +66 -15
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -0
- package/docs/errors/quickchr/machine-not-found.md +22 -0
- package/docs/errors/quickchr/machine-stopped.md +20 -0
- package/docs/errors/quickchr/package-unavailable.md +27 -0
- package/docs/errors/quickchr/unsupported-via.md +35 -0
- package/docs/errors/quickchr/unsupported.md +35 -0
- package/docs/errors/routeros/version-unsupported.md +26 -0
- package/docs/errors/transport/provider-trust.md +29 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +45 -10
- package/src/api-fanout.ts +29 -14
- package/src/api.ts +97 -54
- package/src/cli/api.ts +25 -6
- package/src/cli/btest.ts +18 -2
- package/src/cli/execute.ts +25 -6
- package/src/cli/explain.ts +503 -0
- package/src/cli/retrieve.ts +33 -1
- package/src/cli/selection.ts +71 -11
- package/src/cli/settings.ts +2 -2
- package/src/cli/transfer.ts +23 -6
- package/src/cli.ts +15 -2
- package/src/core/envelope.ts +5 -2
- package/src/core/error-catalog.ts +40 -0
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +29 -14
- package/src/execute.ts +114 -63
- package/src/explain/arg-tokens.ts +114 -0
- package/src/explain/args.ts +1403 -0
- package/src/explain/blocks.ts +132 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +38 -0
- package/src/explain/catalog.ts +1345 -0
- package/src/explain/coordinates.ts +329 -0
- package/src/explain/defects.ts +217 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +664 -0
- package/src/explain/operator-tokens.ts +310 -0
- package/src/explain/operators.ts +458 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1324 -0
- package/src/explain/quoted-string.ts +241 -0
- package/src/explain/scope-brace.ts +308 -0
- package/src/explain/segment.ts +757 -0
- package/src/explain/string-tokens.ts +67 -0
- package/src/explain/symbol-values.ts +520 -0
- package/src/explain/symbols.ts +1597 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +497 -0
- package/src/explain/value-tokens.ts +80 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +813 -0
- package/src/explain/write.ts +815 -0
- package/src/explain.ts +2152 -0
- package/src/index.ts +206 -4
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/facts.ts +1 -1
- package/src/resolver/index.ts +23 -4
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +99 -12
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/target.ts +1 -1
- package/src/retrieve-fanout.ts +26 -12
- package/src/retrieve.ts +81 -18
- package/src/transfer-fanout.ts +42 -25
- package/src/transfer.ts +87 -44
package/src/transfer-fanout.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Boundaries (see `commands/transfer/README.md`, `docs/CONSTITUTION.md`), shared
|
|
10
10
|
* with `api`/`retrieve`/`execute` fan-out:
|
|
11
|
-
* - The CDB is loaded ONCE (`
|
|
11
|
+
* - The CDB is loaded ONCE (`expandSelection`); a cdb member is resolved with
|
|
12
12
|
* its pre-resolved record, so `resolveCdb`'s `__default__` synthetic fallback
|
|
13
13
|
* never collides ad-hoc literals.
|
|
14
14
|
* - A `--default` / `__default__` member fails that one target deterministically
|
|
@@ -42,12 +42,12 @@ import { CentrsError, serializeCentrsError } from "./errors.ts";
|
|
|
42
42
|
import { promptForWriteConfirmation } from "./execute.ts";
|
|
43
43
|
import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
|
|
44
44
|
import {
|
|
45
|
-
|
|
46
|
-
type CdbSelectionMember,
|
|
47
|
-
type CdbSelectionResolveInput,
|
|
48
|
-
expandCdbSelection,
|
|
45
|
+
expandSelection,
|
|
49
46
|
isDefaultRecordTarget,
|
|
50
47
|
loadEnvFileDefaults,
|
|
48
|
+
type SelectionExpansion,
|
|
49
|
+
type SelectionMember,
|
|
50
|
+
type SelectionResolveInput,
|
|
51
51
|
type TargetSelection,
|
|
52
52
|
} from "./resolver/index.ts";
|
|
53
53
|
import { toYaml } from "./retrieve.ts";
|
|
@@ -71,6 +71,8 @@ export interface TransferSelectionSummary {
|
|
|
71
71
|
all: boolean;
|
|
72
72
|
default: boolean;
|
|
73
73
|
positionals: readonly string[];
|
|
74
|
+
/** quickchr machine names (`--quickchr`, repeatable; exclusive of the rest). */
|
|
75
|
+
quickchr?: readonly string[];
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
export interface TransferFanoutRequestSummary {
|
|
@@ -101,13 +103,13 @@ export type TransferFanoutErrorEnvelope =
|
|
|
101
103
|
export interface TransferFanoutInternals {
|
|
102
104
|
expand?: (
|
|
103
105
|
selection: TargetSelection,
|
|
104
|
-
input:
|
|
106
|
+
input: SelectionResolveInput,
|
|
105
107
|
env: Record<string, string | undefined>,
|
|
106
108
|
config?: Record<string, string | undefined>,
|
|
107
|
-
) => Promise<
|
|
109
|
+
) => Promise<SelectionExpansion>;
|
|
108
110
|
execute?: (
|
|
109
111
|
request: TransferRequest,
|
|
110
|
-
member:
|
|
112
|
+
member: SelectionMember,
|
|
111
113
|
) => Promise<TransferSuccessEnvelope>;
|
|
112
114
|
sleep?: (ms: number) => Promise<void>;
|
|
113
115
|
}
|
|
@@ -127,14 +129,18 @@ function summarizeSelection(
|
|
|
127
129
|
all: selection.all,
|
|
128
130
|
default: selection.default,
|
|
129
131
|
positionals: selection.positionals,
|
|
132
|
+
quickchr: selection.quickchr,
|
|
130
133
|
};
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
function memberLabel(member:
|
|
134
|
-
|
|
136
|
+
function memberLabel(member: SelectionMember): string {
|
|
137
|
+
if (member.kind === "cdb") {
|
|
138
|
+
return member.resolution.identity;
|
|
139
|
+
}
|
|
140
|
+
return member.kind === "quickchr" ? member.name : member.input;
|
|
135
141
|
}
|
|
136
142
|
|
|
137
|
-
function memberTargetMeta(member:
|
|
143
|
+
function memberTargetMeta(member: SelectionMember): {
|
|
138
144
|
input?: string;
|
|
139
145
|
identity?: string;
|
|
140
146
|
recordIndex?: number;
|
|
@@ -146,6 +152,9 @@ function memberTargetMeta(member: CdbSelectionMember): {
|
|
|
146
152
|
recordIndex: member.recordIndex,
|
|
147
153
|
};
|
|
148
154
|
}
|
|
155
|
+
if (member.kind === "quickchr") {
|
|
156
|
+
return { input: member.name, identity: member.name };
|
|
157
|
+
}
|
|
149
158
|
return { input: member.input };
|
|
150
159
|
}
|
|
151
160
|
|
|
@@ -165,11 +174,11 @@ function sanitizeFilename(label: string): string {
|
|
|
165
174
|
function planDownloadPaths(
|
|
166
175
|
outDir: string,
|
|
167
176
|
remote: string,
|
|
168
|
-
members: readonly
|
|
169
|
-
): Map<
|
|
177
|
+
members: readonly SelectionMember[],
|
|
178
|
+
): Map<SelectionMember, string> {
|
|
170
179
|
const ext = extname(basename(remote));
|
|
171
180
|
const used = new Set<string>();
|
|
172
|
-
const paths = new Map<
|
|
181
|
+
const paths = new Map<SelectionMember, string>();
|
|
173
182
|
members.forEach((member, index) => {
|
|
174
183
|
const label = sanitizeFilename(memberLabel(member));
|
|
175
184
|
let name = `${label}${ext}`;
|
|
@@ -197,7 +206,7 @@ function planDownloadPaths(
|
|
|
197
206
|
*/
|
|
198
207
|
function relabelAdhocMember(
|
|
199
208
|
envelope: TransferEnvelope,
|
|
200
|
-
member:
|
|
209
|
+
member: SelectionMember,
|
|
201
210
|
): TransferEnvelope {
|
|
202
211
|
if (member.kind !== "literal") {
|
|
203
212
|
return envelope;
|
|
@@ -217,7 +226,7 @@ function relabelAdhocMember(
|
|
|
217
226
|
*/
|
|
218
227
|
function applyMemberMeta(
|
|
219
228
|
envelope: TransferEnvelope,
|
|
220
|
-
member:
|
|
229
|
+
member: SelectionMember,
|
|
221
230
|
): TransferEnvelope {
|
|
222
231
|
if (member.kind === "literal") {
|
|
223
232
|
return relabelAdhocMember(envelope, member);
|
|
@@ -268,7 +277,7 @@ export async function transferFanout(
|
|
|
268
277
|
});
|
|
269
278
|
}
|
|
270
279
|
|
|
271
|
-
const expand = internals.expand ??
|
|
280
|
+
const expand = internals.expand ?? expandSelection;
|
|
272
281
|
const expansion = await expand(
|
|
273
282
|
selection,
|
|
274
283
|
{
|
|
@@ -310,7 +319,7 @@ export async function transferFanout(
|
|
|
310
319
|
|
|
311
320
|
const execute =
|
|
312
321
|
internals.execute ??
|
|
313
|
-
((req: TransferRequest, member:
|
|
322
|
+
((req: TransferRequest, member: SelectionMember) =>
|
|
314
323
|
transfer(req, env, {
|
|
315
324
|
// cdb members carry their pre-resolved record (CDB loaded once);
|
|
316
325
|
// literals fall through to single-target resolution.
|
|
@@ -319,8 +328,8 @@ export async function transferFanout(
|
|
|
319
328
|
const sleep = internals.sleep ?? defaultFanoutSleep;
|
|
320
329
|
|
|
321
330
|
const targets = await runFanout<
|
|
322
|
-
|
|
323
|
-
{ request: TransferRequest; member:
|
|
331
|
+
SelectionMember,
|
|
332
|
+
{ request: TransferRequest; member: SelectionMember },
|
|
324
333
|
TransferEnvelope
|
|
325
334
|
>({
|
|
326
335
|
members: expansion.targets,
|
|
@@ -339,9 +348,17 @@ export async function transferFanout(
|
|
|
339
348
|
context: { target: member.resolution.target },
|
|
340
349
|
});
|
|
341
350
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
351
|
+
// A quickchr member carries the machine name in `quickchr` (resolved live
|
|
352
|
+
// inside `resolveTransferRequest`, so a stopped machine is an inner
|
|
353
|
+
// per-target failure), never as a `targetInput`.
|
|
354
|
+
const perRequest: TransferRequest =
|
|
355
|
+
member.kind === "quickchr"
|
|
356
|
+
? { ...request, targetInput: undefined, quickchr: member.name }
|
|
357
|
+
: {
|
|
358
|
+
...request,
|
|
359
|
+
targetInput:
|
|
360
|
+
member.kind === "cdb" ? member.resolution.target : member.input,
|
|
361
|
+
};
|
|
345
362
|
if (downloadPaths !== undefined) {
|
|
346
363
|
perRequest.local = downloadPaths.get(member);
|
|
347
364
|
}
|
|
@@ -377,7 +394,7 @@ export async function transferFanout(
|
|
|
377
394
|
|
|
378
395
|
async function assertFanoutWriteConfirmed(
|
|
379
396
|
request: TransferRequest,
|
|
380
|
-
expansion:
|
|
397
|
+
expansion: SelectionExpansion,
|
|
381
398
|
): Promise<void> {
|
|
382
399
|
if (request.yes) {
|
|
383
400
|
return;
|
|
@@ -407,7 +424,7 @@ interface TransferFanoutEnvelopeInput {
|
|
|
407
424
|
summary: FanoutSummary;
|
|
408
425
|
requestSummary: TransferFanoutRequestSummary;
|
|
409
426
|
via: RouterOsProtocol | string | null;
|
|
410
|
-
warnings:
|
|
427
|
+
warnings: SelectionExpansion["warnings"];
|
|
411
428
|
targets: readonly TransferEnvelope[];
|
|
412
429
|
}
|
|
413
430
|
|
package/src/transfer.ts
CHANGED
|
@@ -38,15 +38,18 @@ import {
|
|
|
38
38
|
import type { RouterOsProtocol } from "./protocols/index.ts";
|
|
39
39
|
import { SftpClient } from "./protocols/sftp.ts";
|
|
40
40
|
import {
|
|
41
|
+
assertNoQuickchrOverrideConflict,
|
|
41
42
|
type CdbResolution,
|
|
42
43
|
loadEnvFileDefaults,
|
|
43
44
|
parseDuration,
|
|
45
|
+
quickchrConnection,
|
|
44
46
|
type ResolvedAuth,
|
|
45
47
|
type ResolvedSetting,
|
|
46
48
|
type ResolvedTarget,
|
|
47
49
|
resolveAuth,
|
|
48
50
|
resolveBooleanSetting,
|
|
49
51
|
resolveCdb,
|
|
52
|
+
resolveQuickchrTarget,
|
|
50
53
|
resolveStringSetting,
|
|
51
54
|
resolveTarget,
|
|
52
55
|
toCoreSource,
|
|
@@ -104,6 +107,12 @@ export const FILE_READ_CHUNK_BYTES = 32_768;
|
|
|
104
107
|
export interface TransferRequest {
|
|
105
108
|
verb: TransferVerb;
|
|
106
109
|
targetInput?: string;
|
|
110
|
+
/**
|
|
111
|
+
* quickchr machine name (`--quickchr <name>`): resolve host/port/auth from
|
|
112
|
+
* the live VM descriptor instead of CDB/env (`docs/CONSTITUTION.md` →
|
|
113
|
+
* Resolution providers). Conflicts with host/port/username/password/sshKey.
|
|
114
|
+
*/
|
|
115
|
+
quickchr?: string;
|
|
107
116
|
/** Local path (or `-` for stdin/stdout). Present for upload/download. */
|
|
108
117
|
local?: string;
|
|
109
118
|
/** RouterOS file path. Present for upload/download/remove/mkdir and copy source. */
|
|
@@ -818,19 +827,31 @@ export async function resolveTransferRequest(
|
|
|
818
827
|
config,
|
|
819
828
|
);
|
|
820
829
|
|
|
830
|
+
// A quickchr target is a named-live-provider: host/port/auth come from the
|
|
831
|
+
// live descriptor, bypassing CDB and the `__default__` ladder. For
|
|
832
|
+
// `--via sftp`/`scp`, `quickchrConnection` additionally gates on the SSH
|
|
833
|
+
// endpoint's batch-capable auth modes (typed error, never a password prompt).
|
|
834
|
+
let quickchrResolution: Awaited<
|
|
835
|
+
ReturnType<typeof resolveQuickchrTarget>
|
|
836
|
+
> | null = null;
|
|
837
|
+
if (request.quickchr !== undefined) {
|
|
838
|
+
assertNoQuickchrOverrideConflict(request, request.quickchr);
|
|
839
|
+
quickchrResolution = await resolveQuickchrTarget(request.quickchr);
|
|
840
|
+
}
|
|
821
841
|
// Fan-out passes the pre-resolved CDB record for a member (the CDB is loaded
|
|
822
|
-
// once in `
|
|
823
|
-
const cdbResolution =
|
|
824
|
-
|
|
825
|
-
(
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
842
|
+
// once in `expandSelection`); single-target resolves it here.
|
|
843
|
+
const cdbResolution = quickchrResolution
|
|
844
|
+
? undefined
|
|
845
|
+
: (options.cdbResolution ??
|
|
846
|
+
(await resolveCdb(
|
|
847
|
+
{
|
|
848
|
+
targetInput: request.targetInput,
|
|
849
|
+
cdbFile: request.cdbFile,
|
|
850
|
+
cdbPassword: request.cdbPassword,
|
|
851
|
+
},
|
|
852
|
+
env,
|
|
853
|
+
config,
|
|
854
|
+
)));
|
|
834
855
|
|
|
835
856
|
const format = resolveTransferFormat(request, env, config);
|
|
836
857
|
const validate = resolveBooleanSetting(
|
|
@@ -842,15 +863,6 @@ export async function resolveTransferRequest(
|
|
|
842
863
|
cdbResolution?.overrides.validate,
|
|
843
864
|
config,
|
|
844
865
|
);
|
|
845
|
-
const insecure = resolveBooleanSetting(
|
|
846
|
-
request.insecure,
|
|
847
|
-
env,
|
|
848
|
-
"CENTRS_INSECURE",
|
|
849
|
-
false,
|
|
850
|
-
"insecure",
|
|
851
|
-
cdbResolution?.overrides.insecure,
|
|
852
|
-
config,
|
|
853
|
-
);
|
|
854
866
|
const timeoutMs = resolveTransferTimeout(
|
|
855
867
|
request.timeout,
|
|
856
868
|
env,
|
|
@@ -858,30 +870,59 @@ export async function resolveTransferRequest(
|
|
|
858
870
|
cdbResolution,
|
|
859
871
|
config,
|
|
860
872
|
);
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
873
|
+
const connection = quickchrResolution
|
|
874
|
+
? quickchrConnection(quickchrResolution, protocol)
|
|
875
|
+
: null;
|
|
876
|
+
// A quickchr TLS/SSH endpoint is trusted by provenance (self-signed VM cert /
|
|
877
|
+
// ephemeral host key) — `insecure` is forced with provider provenance and the
|
|
878
|
+
// provider-trust warning replaces the generic `--insecure` anomaly warning.
|
|
879
|
+
const insecure =
|
|
880
|
+
connection?.insecure === true
|
|
881
|
+
? {
|
|
882
|
+
value: true,
|
|
883
|
+
source: {
|
|
884
|
+
kind: "provider" as const,
|
|
885
|
+
key: `quickchr:${request.quickchr}`,
|
|
886
|
+
},
|
|
887
|
+
}
|
|
888
|
+
: resolveBooleanSetting(
|
|
889
|
+
request.insecure,
|
|
890
|
+
env,
|
|
891
|
+
"CENTRS_INSECURE",
|
|
892
|
+
false,
|
|
893
|
+
"insecure",
|
|
894
|
+
cdbResolution?.overrides.insecure,
|
|
895
|
+
config,
|
|
896
|
+
);
|
|
897
|
+
const target = connection
|
|
898
|
+
? connection.target
|
|
899
|
+
: resolveTarget(
|
|
900
|
+
{
|
|
901
|
+
targetInput: request.targetInput,
|
|
902
|
+
host: request.host,
|
|
903
|
+
port: request.port,
|
|
904
|
+
},
|
|
905
|
+
env,
|
|
906
|
+
protocol,
|
|
907
|
+
cdbResolution,
|
|
908
|
+
config,
|
|
909
|
+
);
|
|
910
|
+
const auth = connection
|
|
911
|
+
? connection.auth
|
|
912
|
+
: resolveAuth(
|
|
913
|
+
{
|
|
914
|
+
username: request.username,
|
|
915
|
+
password: request.password,
|
|
916
|
+
sshKey: request.sshKey,
|
|
917
|
+
},
|
|
918
|
+
env,
|
|
919
|
+
cdbResolution,
|
|
920
|
+
config,
|
|
921
|
+
);
|
|
882
922
|
|
|
883
923
|
const warnings: Warning[] = [
|
|
884
924
|
...((cdbResolution?.warnings ?? []) as readonly Warning[]),
|
|
925
|
+
...(connection?.warnings ?? []),
|
|
885
926
|
];
|
|
886
927
|
// Report the auto hop: a large upload cannot ride the REST family's 60 KB
|
|
887
928
|
// `/file/set` cap, so auto-selection moved it to sftp.
|
|
@@ -892,8 +933,10 @@ export async function resolveTransferRequest(
|
|
|
892
933
|
"Large upload (>60 KB) auto-selected sftp; the REST family cannot write past the 60 KB `/file/set` cap.",
|
|
893
934
|
});
|
|
894
935
|
}
|
|
895
|
-
// A self-signed-accepting run is an anomaly worth surfacing on every
|
|
896
|
-
|
|
936
|
+
// A self-signed-accepting run is an anomaly worth surfacing on every
|
|
937
|
+
// transport — unless the trust came from the provider, whose own
|
|
938
|
+
// provider-trust warning already rides above.
|
|
939
|
+
if (insecure.value && connection?.insecure !== true) {
|
|
897
940
|
warnings.push({
|
|
898
941
|
code: "transport/insecure-trust",
|
|
899
942
|
message:
|