@datarails-ai/dr-cli 1.6.123 → 1.6.124
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/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@datarails-ai/dr-cli",
|
|
11
|
-
version: "1.6.
|
|
11
|
+
version: "1.6.124",
|
|
12
12
|
description: "Datarails CLI",
|
|
13
13
|
type: "module",
|
|
14
14
|
engines: {
|
|
@@ -69,7 +69,7 @@ var package_default = {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
// src/buildInfo.ts
|
|
72
|
-
var BUILD_TIME = "2026-10-12T11:
|
|
72
|
+
var BUILD_TIME = "2026-10-12T11:50:53.998Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -4474,13 +4474,14 @@ function isBatchTimeoutClamped(callCount) {
|
|
|
4474
4474
|
const n = Number.isFinite(callCount) ? Math.max(1, Math.floor(callCount)) : 1;
|
|
4475
4475
|
return DEFAULT_CALL_TIMEOUT_MS + n * BATCH_PER_TARGET_TIMEOUT_MS > BATCH_MAX_TIMEOUT_MS;
|
|
4476
4476
|
}
|
|
4477
|
-
|
|
4477
|
+
var TIMEOUT_FLAG_REMEDY = "re-run it with --timeout <seconds> (a floor, so a larger value only ever widens the budget)";
|
|
4478
|
+
function extendForBatch(extend, callCount, remedy = TIMEOUT_FLAG_REMEDY) {
|
|
4478
4479
|
extend?.(batchTimeoutMs(callCount));
|
|
4479
4480
|
if (!isBatchTimeoutClamped(callCount)) return;
|
|
4480
4481
|
const budgetS = BATCH_MAX_TIMEOUT_MS / 1e3;
|
|
4481
4482
|
const perCallMs = Math.floor(BATCH_MAX_TIMEOUT_MS / Math.floor(callCount));
|
|
4482
4483
|
process.stderr.write(
|
|
4483
|
-
`warning: ${callCount} calls exceed the ${budgetS}s ceiling on the auto-sized budget \u2014 about ${perCallMs}ms per call. If the run aborts part-way,
|
|
4484
|
+
`warning: ${callCount} calls exceed the ${budgetS}s ceiling on the auto-sized budget \u2014 about ${perCallMs}ms per call. If the run aborts part-way, ${remedy}.
|
|
4484
4485
|
`
|
|
4485
4486
|
);
|
|
4486
4487
|
}
|
|
@@ -5419,8 +5420,8 @@ async function openBrowser(url) {
|
|
|
5419
5420
|
async function readLine(stdin) {
|
|
5420
5421
|
return new Promise((resolve3, reject) => {
|
|
5421
5422
|
let data4 = "";
|
|
5422
|
-
const onData = (
|
|
5423
|
-
data4 +=
|
|
5423
|
+
const onData = (chunk2) => {
|
|
5424
|
+
data4 += chunk2.toString();
|
|
5424
5425
|
const i = data4.indexOf("\n");
|
|
5425
5426
|
if (i >= 0) {
|
|
5426
5427
|
cleanup();
|
|
@@ -5512,8 +5513,8 @@ async function readHiddenLineFromTty(prompt, stdin, out) {
|
|
|
5512
5513
|
cleanup();
|
|
5513
5514
|
reject(err);
|
|
5514
5515
|
};
|
|
5515
|
-
const onData = (
|
|
5516
|
-
const s = typeof
|
|
5516
|
+
const onData = (chunk2) => {
|
|
5517
|
+
const s = typeof chunk2 === "string" ? chunk2 : chunk2.toString("utf8");
|
|
5517
5518
|
for (const ch of s) {
|
|
5518
5519
|
if (ch === "\r" || ch === "\n") {
|
|
5519
5520
|
cleanup();
|
|
@@ -5561,8 +5562,8 @@ async function readFirstLineFromPipe(stdin) {
|
|
|
5561
5562
|
cleanup();
|
|
5562
5563
|
resolve3(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
5563
5564
|
};
|
|
5564
|
-
const onData = (
|
|
5565
|
-
data4 += typeof
|
|
5565
|
+
const onData = (chunk2) => {
|
|
5566
|
+
data4 += typeof chunk2 === "string" ? chunk2 : chunk2.toString("utf8");
|
|
5566
5567
|
const i = data4.indexOf("\n");
|
|
5567
5568
|
if (i >= 0) finish(data4.slice(0, i));
|
|
5568
5569
|
};
|
|
@@ -6371,15 +6372,15 @@ async function commandDelete(client, id, options) {
|
|
|
6371
6372
|
function strOr(s, fallback) {
|
|
6372
6373
|
return s === "" ? fallback : s;
|
|
6373
6374
|
}
|
|
6374
|
-
function parsePositiveIntsArgs(args) {
|
|
6375
|
+
function parsePositiveIntsArgs(args, label = "device id") {
|
|
6375
6376
|
const out = [];
|
|
6376
6377
|
for (const a of args) {
|
|
6377
6378
|
const n = Number(a);
|
|
6378
6379
|
if (!Number.isInteger(n) || String(n) !== a) {
|
|
6379
|
-
throw new Error(`invalid
|
|
6380
|
+
throw new Error(`invalid ${label} "${a}": not a number`);
|
|
6380
6381
|
}
|
|
6381
6382
|
if (n <= 0) {
|
|
6382
|
-
throw new Error(`invalid
|
|
6383
|
+
throw new Error(`invalid ${label} "${a}": must be positive`);
|
|
6383
6384
|
}
|
|
6384
6385
|
out.push(n);
|
|
6385
6386
|
}
|
|
@@ -6392,6 +6393,16 @@ function parsePositiveIntArg(raw, label) {
|
|
|
6392
6393
|
}
|
|
6393
6394
|
return n;
|
|
6394
6395
|
}
|
|
6396
|
+
function chunk(items, size) {
|
|
6397
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
6398
|
+
throw new Error("chunk: size must be a positive integer");
|
|
6399
|
+
}
|
|
6400
|
+
const out = [];
|
|
6401
|
+
for (let i = 0; i < items.length; i += size) {
|
|
6402
|
+
out.push(items.slice(i, i + size));
|
|
6403
|
+
}
|
|
6404
|
+
return out;
|
|
6405
|
+
}
|
|
6395
6406
|
|
|
6396
6407
|
// src/commands/_retry.ts
|
|
6397
6408
|
var AMBIGUOUS_STATUSES = /* @__PURE__ */ new Set([502, 504, 524]);
|
|
@@ -19121,6 +19132,22 @@ async function updateAutoCalculateSettings(client, docId, body, reqOptions) {
|
|
|
19121
19132
|
async function downloadVersion(client, versionId, reqOptions) {
|
|
19122
19133
|
return client.do("GET", `/api/documents/versions/${versionId}/file`, void 0, reqOptions);
|
|
19123
19134
|
}
|
|
19135
|
+
var KIND_EXPLANATION_PATH = "/api/workspace/documents/kind/explanation";
|
|
19136
|
+
var KIND_SUMMARY_PATH = "/api/workspace/documents/kind/summary";
|
|
19137
|
+
var KIND_EXPLAIN_MAX_IDS = 200;
|
|
19138
|
+
var KIND_EXPLAIN_MAX_IDS_WITH_HISTORY = 20;
|
|
19139
|
+
function kindExplanationPath(query) {
|
|
19140
|
+
let path12 = `${KIND_EXPLANATION_PATH}?ids=${query.ids.join(",")}`;
|
|
19141
|
+
if (query.asOf !== void 0) path12 += `&as_of=${encodeURIComponent(query.asOf)}`;
|
|
19142
|
+
if (query.history === true) path12 += "&history=true";
|
|
19143
|
+
return path12;
|
|
19144
|
+
}
|
|
19145
|
+
async function getKindExplanation(client, query, reqOptions) {
|
|
19146
|
+
return client.do("GET", kindExplanationPath(query), void 0, reqOptions);
|
|
19147
|
+
}
|
|
19148
|
+
async function getKindSummary(client, reqOptions) {
|
|
19149
|
+
return client.do("GET", KIND_SUMMARY_PATH, void 0, reqOptions);
|
|
19150
|
+
}
|
|
19124
19151
|
async function listBucketTemplates(client, bucketId, reqOptions) {
|
|
19125
19152
|
const out = await client.getJSON(
|
|
19126
19153
|
`/api/buckets/${bucketId}/templates`,
|
|
@@ -35462,6 +35489,678 @@ function registerFileboxAdminCommands(fb, setupCall2, addServerFlag2, addAccount
|
|
|
35462
35489
|
addAccountFlag2(vrSetCmd);
|
|
35463
35490
|
}
|
|
35464
35491
|
|
|
35492
|
+
// src/commands/_reconciliationDate.ts
|
|
35493
|
+
function validateDateString(orig, flagName) {
|
|
35494
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(orig)) {
|
|
35495
|
+
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
|
|
35496
|
+
}
|
|
35497
|
+
const parts = orig.split("-").map(Number);
|
|
35498
|
+
const [year, month1, day] = parts;
|
|
35499
|
+
const probe = new Date(Date.UTC(year, month1 - 1, day));
|
|
35500
|
+
if (probe.getUTCFullYear() !== year || probe.getUTCMonth() + 1 !== month1 || probe.getUTCDate() !== day) {
|
|
35501
|
+
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
|
|
35502
|
+
}
|
|
35503
|
+
return { year, month1, day };
|
|
35504
|
+
}
|
|
35505
|
+
function parseDateFlag(orig, flagName) {
|
|
35506
|
+
let normalized = orig;
|
|
35507
|
+
if (/^\d{4}-\d{2}$/.test(orig)) {
|
|
35508
|
+
normalized = `${orig}-01`;
|
|
35509
|
+
}
|
|
35510
|
+
let year;
|
|
35511
|
+
let month1;
|
|
35512
|
+
try {
|
|
35513
|
+
({ year, month1 } = validateDateString(normalized, flagName));
|
|
35514
|
+
} catch {
|
|
35515
|
+
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
|
|
35516
|
+
}
|
|
35517
|
+
return Math.floor(Date.UTC(year, month1, 0) / 1e3);
|
|
35518
|
+
}
|
|
35519
|
+
function parseDateFlagEpoch(orig, flagName) {
|
|
35520
|
+
let normalized = orig;
|
|
35521
|
+
if (/^\d{4}-\d{2}$/.test(orig)) {
|
|
35522
|
+
normalized = `${orig}-01`;
|
|
35523
|
+
}
|
|
35524
|
+
let year;
|
|
35525
|
+
let month1;
|
|
35526
|
+
try {
|
|
35527
|
+
({ year, month1 } = validateDateString(normalized, flagName));
|
|
35528
|
+
} catch {
|
|
35529
|
+
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
|
|
35530
|
+
}
|
|
35531
|
+
return Math.floor(Date.UTC(year, month1 - 1, 1, 12) / 1e3);
|
|
35532
|
+
}
|
|
35533
|
+
function parseDateFlagDay(orig, flagName) {
|
|
35534
|
+
const { year, month1, day } = validateDateString(orig, flagName);
|
|
35535
|
+
return Math.floor(Date.UTC(year, month1 - 1, day) / 1e3);
|
|
35536
|
+
}
|
|
35537
|
+
|
|
35538
|
+
// src/commands/filebox_kind.ts
|
|
35539
|
+
function parseKindExplainIds(args) {
|
|
35540
|
+
const parts = args.flatMap((arg) => arg.split(","));
|
|
35541
|
+
return [...new Set(parsePositiveIntsArgs(parts, "filebox id"))];
|
|
35542
|
+
}
|
|
35543
|
+
function parseKindAsOf(raw) {
|
|
35544
|
+
if (raw === void 0) return void 0;
|
|
35545
|
+
parseDateFlagDay(raw, "--as-of");
|
|
35546
|
+
return raw;
|
|
35547
|
+
}
|
|
35548
|
+
var SUPPORT_ONLY_CODES = /* @__PURE__ */ new Set([
|
|
35549
|
+
"kind_explanation_support_only",
|
|
35550
|
+
"kind_summary_support_only"
|
|
35551
|
+
]);
|
|
35552
|
+
function jsonObject(text2) {
|
|
35553
|
+
try {
|
|
35554
|
+
const parsed2 = JSON.parse(text2);
|
|
35555
|
+
if (typeof parsed2 === "object" && parsed2 !== null && !Array.isArray(parsed2)) {
|
|
35556
|
+
return parsed2;
|
|
35557
|
+
}
|
|
35558
|
+
} catch {
|
|
35559
|
+
}
|
|
35560
|
+
return void 0;
|
|
35561
|
+
}
|
|
35562
|
+
function describeKindErrorBody(text2) {
|
|
35563
|
+
const raw = truncate3(text2.trim(), 200);
|
|
35564
|
+
const body = jsonObject(text2);
|
|
35565
|
+
if (body === void 0) return raw;
|
|
35566
|
+
for (const key of ["message", "detail"]) {
|
|
35567
|
+
const value = body[key];
|
|
35568
|
+
if (typeof value === "string" && value !== "") return value;
|
|
35569
|
+
}
|
|
35570
|
+
const parts = Object.entries(body).map(([field, value]) => {
|
|
35571
|
+
const messages = (Array.isArray(value) ? value : [value]).map(
|
|
35572
|
+
(m) => typeof m === "string" ? m : JSON.stringify(m)
|
|
35573
|
+
);
|
|
35574
|
+
return field === "non_field_errors" ? messages.join("; ") : `${field}: ${messages.join("; ")}`;
|
|
35575
|
+
});
|
|
35576
|
+
return parts.length > 0 ? parts.join("; ") : raw;
|
|
35577
|
+
}
|
|
35578
|
+
function kindReadError(status2, text2, ctx) {
|
|
35579
|
+
const server = ctx.client.serverKey;
|
|
35580
|
+
const body = jsonObject(text2);
|
|
35581
|
+
const code = typeof body?.["code"] === "string" ? body["code"] : void 0;
|
|
35582
|
+
if (status2 === 405 && code !== void 0 && SUPPORT_ONLY_CODES.has(code)) {
|
|
35583
|
+
const scope = ctx.command === "kind-explain" ? "only the support seat of the fileboxes' own organization gets an answer" : "it answers the support seat of an organization, for that organization";
|
|
35584
|
+
return new Error(
|
|
35585
|
+
`${ctx.command}: the server refused ${ctx.client.email} (HTTP 405 ${code}: ${describeKindErrorBody(text2)}) \u2014 ${scope}. Log in as that organization's support seat, then run the command as it:
|
|
35586
|
+
dr login-as <support seat email> --server ${server}
|
|
35587
|
+
dr filebox ${ctx.command} \u2026 --server ${server} --account <support seat email>`
|
|
35588
|
+
);
|
|
35589
|
+
}
|
|
35590
|
+
if (status2 === 404) {
|
|
35591
|
+
return new Error(
|
|
35592
|
+
`GET ${ctx.route}: HTTP 404 \u2014 ${server} most likely does not have the filebox-type endpoints yet. They ship with dr-datarails-server PR #8010 (DR-53666); until that change is deployed on ${server}, ${ctx.command} has nothing to ask.`
|
|
35593
|
+
);
|
|
35594
|
+
}
|
|
35595
|
+
if (status2 === 400) {
|
|
35596
|
+
return new Error(
|
|
35597
|
+
`${ctx.command}: the server rejected the request (HTTP 400): ${describeKindErrorBody(text2)}`
|
|
35598
|
+
);
|
|
35599
|
+
}
|
|
35600
|
+
if (status2 === 403) {
|
|
35601
|
+
return new Error(
|
|
35602
|
+
`${ctx.command}: the server refused ${ctx.client.email} (HTTP 403): ${describeKindErrorBody(text2)} \u2014 a blocked seat gets this answer. Check the seat with \`dr whoami --server ${server} --account ${ctx.client.email}\`.`
|
|
35603
|
+
);
|
|
35604
|
+
}
|
|
35605
|
+
return new Error(`GET ${ctx.route}: HTTP ${status2}: ${truncate3(text2.trim(), 200)}`);
|
|
35606
|
+
}
|
|
35607
|
+
async function readKindAnswer(res, ctx) {
|
|
35608
|
+
const text2 = await res.text();
|
|
35609
|
+
if (res.status < 200 || res.status >= 300) throw kindReadError(res.status, text2, ctx);
|
|
35610
|
+
return text2;
|
|
35611
|
+
}
|
|
35612
|
+
function parseKindBody(text2, route) {
|
|
35613
|
+
try {
|
|
35614
|
+
return JSON.parse(text2);
|
|
35615
|
+
} catch (err) {
|
|
35616
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35617
|
+
throw new Error(`GET ${route}: decode: ${msg}`);
|
|
35618
|
+
}
|
|
35619
|
+
}
|
|
35620
|
+
function answeredAsOf(body) {
|
|
35621
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
35622
|
+
const asOf = body["as_of"];
|
|
35623
|
+
return typeof asOf === "string" ? asOf : void 0;
|
|
35624
|
+
}
|
|
35625
|
+
function withNewline(text2) {
|
|
35626
|
+
return text2.endsWith("\n") ? text2 : `${text2}
|
|
35627
|
+
`;
|
|
35628
|
+
}
|
|
35629
|
+
function mergeKindBodies(bodies) {
|
|
35630
|
+
const [first, ...rest] = bodies;
|
|
35631
|
+
if (rest.length === 0) return first;
|
|
35632
|
+
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
35633
|
+
if (!isObject(first)) throw new Error("kind-explain: the server answered with no JSON object");
|
|
35634
|
+
const merged = {};
|
|
35635
|
+
for (const [key, value] of Object.entries(first)) {
|
|
35636
|
+
const lists = [value, ...rest.map((body) => isObject(body) ? body[key] : void 0)];
|
|
35637
|
+
merged[key] = lists.every(Array.isArray) ? lists.flatMap((list28) => list28) : value;
|
|
35638
|
+
}
|
|
35639
|
+
return merged;
|
|
35640
|
+
}
|
|
35641
|
+
async function runFileboxKindExplain(cfg) {
|
|
35642
|
+
if (cfg.ids.length === 0) throw new Error("kind-explain: at least one filebox id is required");
|
|
35643
|
+
const ctx = {
|
|
35644
|
+
command: "kind-explain",
|
|
35645
|
+
route: KIND_EXPLANATION_PATH,
|
|
35646
|
+
client: cfg.client
|
|
35647
|
+
};
|
|
35648
|
+
const batches = chunk(
|
|
35649
|
+
cfg.ids,
|
|
35650
|
+
cfg.history ? KIND_EXPLAIN_MAX_IDS_WITH_HISTORY : KIND_EXPLAIN_MAX_IDS
|
|
35651
|
+
);
|
|
35652
|
+
if (batches.length > 1) {
|
|
35653
|
+
extendForBatch(
|
|
35654
|
+
cfg.extendTimeout,
|
|
35655
|
+
batches.length,
|
|
35656
|
+
"split the ids over several runs (kind-explain has no --timeout)"
|
|
35657
|
+
);
|
|
35658
|
+
}
|
|
35659
|
+
const needsParse = batches.length > 1 || !cfg.asJson;
|
|
35660
|
+
const texts = [];
|
|
35661
|
+
const bodies = [];
|
|
35662
|
+
let asOf = cfg.asOf;
|
|
35663
|
+
for (const [index, ids] of batches.entries()) {
|
|
35664
|
+
const res = await getKindExplanation(
|
|
35665
|
+
cfg.client,
|
|
35666
|
+
{ ids, asOf, history: cfg.history },
|
|
35667
|
+
{ signal: cfg.signal }
|
|
35668
|
+
);
|
|
35669
|
+
let text2;
|
|
35670
|
+
try {
|
|
35671
|
+
text2 = await readKindAnswer(res, ctx);
|
|
35672
|
+
} catch (err) {
|
|
35673
|
+
if (index === 0 || !(err instanceof Error)) throw err;
|
|
35674
|
+
throw new Error(
|
|
35675
|
+
`request ${index + 1} of ${batches.length} failed, nothing was printed: ${err.message}`
|
|
35676
|
+
);
|
|
35677
|
+
}
|
|
35678
|
+
texts.push(text2);
|
|
35679
|
+
if (!needsParse) continue;
|
|
35680
|
+
const body = parseKindBody(text2, KIND_EXPLANATION_PATH);
|
|
35681
|
+
bodies.push(body);
|
|
35682
|
+
asOf ??= answeredAsOf(body);
|
|
35683
|
+
}
|
|
35684
|
+
if (cfg.asJson) {
|
|
35685
|
+
cfg.out.write(
|
|
35686
|
+
batches.length === 1 ? withNewline(texts[0] ?? "") : `${JSON.stringify(mergeKindBodies(bodies))}
|
|
35687
|
+
`
|
|
35688
|
+
);
|
|
35689
|
+
return;
|
|
35690
|
+
}
|
|
35691
|
+
const answer = decodeShape(mergeKindBodies(bodies));
|
|
35692
|
+
renderKindExplanation(cfg.out, answer, { showAsOf: cfg.asOf !== void 0 });
|
|
35693
|
+
}
|
|
35694
|
+
var KIND_LABELS = {
|
|
35695
|
+
manual_input: "Manual input",
|
|
35696
|
+
model: "Model",
|
|
35697
|
+
report: "Report",
|
|
35698
|
+
lookup: "Lookup",
|
|
35699
|
+
integration: "Integration"
|
|
35700
|
+
};
|
|
35701
|
+
var FORMAT_LABELS = { excel: "Excel", ppt: "PPT", pdf: "PDF" };
|
|
35702
|
+
var NO_SOURCE = "source not recorded";
|
|
35703
|
+
var SOURCE_WORDS = {
|
|
35704
|
+
backfill: "migration",
|
|
35705
|
+
creation_flow: "flow",
|
|
35706
|
+
user: "user",
|
|
35707
|
+
support: "support",
|
|
35708
|
+
default: "default",
|
|
35709
|
+
"(none)": NO_SOURCE
|
|
35710
|
+
};
|
|
35711
|
+
var SOURCE_PHRASES = {
|
|
35712
|
+
backfill: "set by the migration",
|
|
35713
|
+
creation_flow: "set by a flow (at creation, or a connection added later)",
|
|
35714
|
+
user: "set by a user",
|
|
35715
|
+
support: "set by support",
|
|
35716
|
+
default: "set by default"
|
|
35717
|
+
};
|
|
35718
|
+
var SETTERS = {
|
|
35719
|
+
backfill: "the migration",
|
|
35720
|
+
creation_flow: "a flow",
|
|
35721
|
+
user: "a user",
|
|
35722
|
+
support: "support",
|
|
35723
|
+
default: "default, at creation"
|
|
35724
|
+
};
|
|
35725
|
+
var UNSURE_PHRASES = {
|
|
35726
|
+
non_latest_version_link_only: "Datarails data is linked only on an older version, not on the latest one",
|
|
35727
|
+
mapper_with_data_source: "a mapper and a data source, but nothing pulled out of Datarails: an intake file or an integration",
|
|
35728
|
+
empty_doc_type: "the file has no extension, so its format says nothing",
|
|
35729
|
+
broken_dimension_tags_json: "the filebox's tags could not be read",
|
|
35730
|
+
archived: "the filebox is archived",
|
|
35731
|
+
format_signal_cannot_identify_a_deck: "neither its connections nor its extension say what it is"
|
|
35732
|
+
};
|
|
35733
|
+
var HISTORY_REASONS = {
|
|
35734
|
+
lookup_attached_to_table: "a table attached it as a lookup",
|
|
35735
|
+
// The old "Filebox type" setting and the report builder both record these.
|
|
35736
|
+
use_type_set_to_lookup: "a LUT/FX label set",
|
|
35737
|
+
use_type_relabelled_out_of_lookup: "a LUT/FX label removed",
|
|
35738
|
+
data_source_connected: "a data source was connected while it was empty",
|
|
35739
|
+
data_source_connected_to_a_report: "a data source was connected to a Report",
|
|
35740
|
+
cloud_drive_connected: "a synced cloud-drive file was connected while it was empty",
|
|
35741
|
+
cloud_drive_connected_to_a_report: "a synced cloud-drive file was connected to a Report",
|
|
35742
|
+
email_upload_turned_on: "email upload turned on",
|
|
35743
|
+
api_upload_turned_on: "API upload turned on",
|
|
35744
|
+
report_format_from_first_file: "its first file set the Report's format"
|
|
35745
|
+
};
|
|
35746
|
+
var SELF_SERVE_KINDS = /* @__PURE__ */ new Set(["manual_input", "model", "report"]);
|
|
35747
|
+
var UPLOAD_CHANNEL_WORDS = { email: "email", api: "API" };
|
|
35748
|
+
var EVIDENCE_LABELS = [
|
|
35749
|
+
["mappers", "mapper"],
|
|
35750
|
+
["lookup_tables", "lookup"],
|
|
35751
|
+
["pulled_out", "pulled"],
|
|
35752
|
+
["read_into", "read into"],
|
|
35753
|
+
["data_query", "source"],
|
|
35754
|
+
["cloud_drive", "drive"],
|
|
35755
|
+
["budget_builder", "budget"],
|
|
35756
|
+
["upload_channels", "upload"]
|
|
35757
|
+
];
|
|
35758
|
+
var SEPARATOR = " | ";
|
|
35759
|
+
var LABEL_WIDTH = 10;
|
|
35760
|
+
var AGREES_COLUMN = 44;
|
|
35761
|
+
function kindLabel(token) {
|
|
35762
|
+
return KIND_LABELS[token] ?? token;
|
|
35763
|
+
}
|
|
35764
|
+
function kindsText(kinds, reportFormat) {
|
|
35765
|
+
if (kinds.length === 0) return "no type";
|
|
35766
|
+
return kinds.map(
|
|
35767
|
+
(kind) => kind === "report" && reportFormat !== null ? `Report (${FORMAT_LABELS[reportFormat] ?? reportFormat})` : kindLabel(kind)
|
|
35768
|
+
).join(" + ");
|
|
35769
|
+
}
|
|
35770
|
+
function setBy(source) {
|
|
35771
|
+
if (source === null || source === void 0) return NO_SOURCE;
|
|
35772
|
+
return SOURCE_PHRASES[source] ?? `set by ${source}`;
|
|
35773
|
+
}
|
|
35774
|
+
function storedText(stored) {
|
|
35775
|
+
if (stored === null) return "no type yet";
|
|
35776
|
+
const [firstSource] = stored.sources;
|
|
35777
|
+
if (stored.sources.every((source) => source === firstSource)) {
|
|
35778
|
+
return `${kindsText(stored.kinds, stored.report_format)}, ${setBy(firstSource)}`;
|
|
35779
|
+
}
|
|
35780
|
+
return stored.kinds.map((kind, i) => `${kindsText([kind], stored.report_format)}, ${setBy(stored.sources[i])}`).join("; ");
|
|
35781
|
+
}
|
|
35782
|
+
function flowNote(answer) {
|
|
35783
|
+
if (answer.history !== void 0 || !answer.stored?.sources.includes("creation_flow")) return [];
|
|
35784
|
+
return ["--history shows a connection added later; a creation records nothing"];
|
|
35785
|
+
}
|
|
35786
|
+
function slotsText(slots) {
|
|
35787
|
+
if (slots === null || slots.primary === null) return "no type";
|
|
35788
|
+
const kinds = [slots.primary, slots.secondary].filter((k) => k !== null);
|
|
35789
|
+
return kindsText(kinds, slots.report_format);
|
|
35790
|
+
}
|
|
35791
|
+
function momentText(iso) {
|
|
35792
|
+
const ms = Date.parse(iso);
|
|
35793
|
+
return Number.isFinite(ms) ? formatUnixSeconds(Math.floor(ms / 1e3)) : iso;
|
|
35794
|
+
}
|
|
35795
|
+
function splitReasons(text2) {
|
|
35796
|
+
return text2 === null || text2 === void 0 || text2 === "" ? [] : text2.split(SEPARATOR);
|
|
35797
|
+
}
|
|
35798
|
+
function reasonText(text2) {
|
|
35799
|
+
const colon = text2.indexOf(": ");
|
|
35800
|
+
const token = colon > 0 ? text2.slice(0, colon) : "";
|
|
35801
|
+
const head = Object.hasOwn(KIND_LABELS, token) ? `${kindLabel(token)}${text2.slice(colon)}` : text2;
|
|
35802
|
+
return head.replace(
|
|
35803
|
+
/(does not fit beside )([a-z_+]+)$/,
|
|
35804
|
+
(_match, lead2, slots) => `${lead2}${slots.split("+").map(kindLabel).join(" + ")}`
|
|
35805
|
+
);
|
|
35806
|
+
}
|
|
35807
|
+
function lookUp(phrases, code) {
|
|
35808
|
+
return (Object.hasOwn(phrases, code) ? phrases[code] : void 0) ?? code;
|
|
35809
|
+
}
|
|
35810
|
+
function isByHand(source) {
|
|
35811
|
+
return source === "user" || source === "support";
|
|
35812
|
+
}
|
|
35813
|
+
function settersText(stored) {
|
|
35814
|
+
const [firstSource] = stored.sources;
|
|
35815
|
+
if (stored.sources.every((source) => source === firstSource)) {
|
|
35816
|
+
return firstSource === null || firstSource === void 0 ? `, ${NO_SOURCE}` : ` by ${lookUp(SETTERS, firstSource)}`;
|
|
35817
|
+
}
|
|
35818
|
+
const slots = stored.kinds.map((kind, i) => {
|
|
35819
|
+
const source = stored.sources[i];
|
|
35820
|
+
return source === null || source === void 0 ? `${kindLabel(kind)} (${NO_SOURCE})` : `${kindLabel(kind)} by ${lookUp(SETTERS, source)}`;
|
|
35821
|
+
});
|
|
35822
|
+
return `, ${slots.join(" and ")}`;
|
|
35823
|
+
}
|
|
35824
|
+
function uploadText(channels) {
|
|
35825
|
+
return `${channels.map((channel) => lookUp(UPLOAD_CHANNEL_WORDS, channel)).join(" and ")} upload`;
|
|
35826
|
+
}
|
|
35827
|
+
function integrationNotes(integration) {
|
|
35828
|
+
if (integration === null || integration.counts_as_integration || integration.quiet !== true) {
|
|
35829
|
+
return [];
|
|
35830
|
+
}
|
|
35831
|
+
return ["A data source is connected but quiet, so the content decided the type."];
|
|
35832
|
+
}
|
|
35833
|
+
function uploadKeptModelNotes(stored, derived, integration) {
|
|
35834
|
+
const channels = integration?.upload_channels ?? [];
|
|
35835
|
+
if (channels.length === 0 || !stored.kinds.includes("model") || !derived.includes("integration")) {
|
|
35836
|
+
return [];
|
|
35837
|
+
}
|
|
35838
|
+
const upload2 = uploadText(channels);
|
|
35839
|
+
const several = channels.length > 1;
|
|
35840
|
+
return [
|
|
35841
|
+
`${upload2.charAt(0).toUpperCase()}${upload2.slice(1)} ${several ? "are" : "is"} on: the migration puts Integration in place of a Model, but turning ${several ? "an" : "the"} upload on after it was typed keeps the Model, so the stored type stays ${kindsText(stored.kinds, stored.report_format)}.`
|
|
35842
|
+
];
|
|
35843
|
+
}
|
|
35844
|
+
function kindMoveNeedsSupport(current, next) {
|
|
35845
|
+
const held = new Set(current);
|
|
35846
|
+
const wanted = new Set(next);
|
|
35847
|
+
const changed = [...held, ...wanted].filter((kind) => held.has(kind) !== wanted.has(kind));
|
|
35848
|
+
const integrationStays = held.has("integration") && wanted.has("integration");
|
|
35849
|
+
if (integrationStays && changed.every((kind) => kind === "report")) return false;
|
|
35850
|
+
return [...held, ...wanted].some((kind) => !SELF_SERVE_KINDS.has(kind));
|
|
35851
|
+
}
|
|
35852
|
+
function whoMayChange(stored, derived) {
|
|
35853
|
+
if (kindMoveNeedsSupport(stored, derived)) {
|
|
35854
|
+
return "To change it: the support seat only, in the filebox settings, as Lookup or Integration is held before or after the move.";
|
|
35855
|
+
}
|
|
35856
|
+
return stored.includes("integration") ? "To change it: the owner or the support seat, in the filebox settings: Integration stays, only Report or its format changes." : "To change it: the owner or the support seat, in the filebox settings.";
|
|
35857
|
+
}
|
|
35858
|
+
function meaningLines(answer) {
|
|
35859
|
+
const derived = kindsText(answer.derived.kinds, answer.derived.report_format);
|
|
35860
|
+
const notes = integrationNotes(answer.integration);
|
|
35861
|
+
if (answer.stored === null) {
|
|
35862
|
+
const why = answer.rollout ? "No type stored yet: the migration has not reached this filebox." : "No type stored: the organization is outside the rollout.";
|
|
35863
|
+
return [why, `The migration would make it ${derived}.`, ...notes];
|
|
35864
|
+
}
|
|
35865
|
+
const stored = `${kindsText(answer.stored.kinds, answer.stored.report_format)}${settersText(answer.stored)}`;
|
|
35866
|
+
const byHand = answer.stored.sources.some(isByHand);
|
|
35867
|
+
if (answer.agrees) {
|
|
35868
|
+
return [
|
|
35869
|
+
byHand ? `Set to ${stored}, and its connections say the same.` : `Typed ${stored}, and its connections still say so.`,
|
|
35870
|
+
...notes
|
|
35871
|
+
];
|
|
35872
|
+
}
|
|
35873
|
+
if (byHand) {
|
|
35874
|
+
return [
|
|
35875
|
+
`Set on purpose to ${stored}; its connections alone would make it ${derived}.`,
|
|
35876
|
+
"A type set by hand stays: the migration never overwrites it."
|
|
35877
|
+
];
|
|
35878
|
+
}
|
|
35879
|
+
return [
|
|
35880
|
+
`Typed ${stored}, but its connections now make it ${derived}.`,
|
|
35881
|
+
"The connections changed after it was typed, and a type does not follow them by itself.",
|
|
35882
|
+
...uploadKeptModelNotes(answer.stored, answer.derived.kinds, answer.integration),
|
|
35883
|
+
...timeNotes(answer.integration),
|
|
35884
|
+
whoMayChange(answer.stored.kinds, answer.derived.kinds),
|
|
35885
|
+
...notes
|
|
35886
|
+
];
|
|
35887
|
+
}
|
|
35888
|
+
function timeNotes(integration) {
|
|
35889
|
+
if (integration === null || integration.quiet === null) return [];
|
|
35890
|
+
if ((integration.upload_channels ?? []).length > 0) return [];
|
|
35891
|
+
return [
|
|
35892
|
+
`Time alone can change the answer too: a connected integration is quiet after ${integration.window_days} days with no file, sync attempt or schedule.`,
|
|
35893
|
+
"To judge it as the migration did: --as-of <backfill day>."
|
|
35894
|
+
];
|
|
35895
|
+
}
|
|
35896
|
+
function writeField(out, label, values, indent = " ") {
|
|
35897
|
+
if (values.length === 0) return;
|
|
35898
|
+
const head = label.length >= LABEL_WIDTH ? `${label} ` : label.padEnd(LABEL_WIDTH);
|
|
35899
|
+
const pad = " ".repeat(head.length);
|
|
35900
|
+
values.forEach((value, i) => out.write(`${indent}${i === 0 ? head : pad}${value}
|
|
35901
|
+
`));
|
|
35902
|
+
}
|
|
35903
|
+
function labelledLines(rows) {
|
|
35904
|
+
const width = Math.max(LABEL_WIDTH, ...rows.map(([label]) => label.length + 2));
|
|
35905
|
+
return rows.flatMap(
|
|
35906
|
+
([label, items]) => items.map((item, i) => `${(i === 0 ? label : "").padEnd(width)}${item}`)
|
|
35907
|
+
);
|
|
35908
|
+
}
|
|
35909
|
+
function evidenceLines(evidence) {
|
|
35910
|
+
const rows = EVIDENCE_LABELS.map(
|
|
35911
|
+
([field, label]) => [label, splitReasons(evidence?.[field])]
|
|
35912
|
+
).filter(([, items]) => items.length > 0);
|
|
35913
|
+
return rows.length === 0 ? ["none"] : labelledLines(rows);
|
|
35914
|
+
}
|
|
35915
|
+
function integrationLines(integration) {
|
|
35916
|
+
const channels = integration.upload_channels ?? [];
|
|
35917
|
+
const head = [
|
|
35918
|
+
...integration.quiet === null ? [] : [`${integration.quiet ? "quiet" : "live"} over the last ${integration.window_days} days`],
|
|
35919
|
+
...channels.length > 0 ? [`${uploadText(channels)} on, no quiet rule`] : [],
|
|
35920
|
+
integration.counts_as_integration ? "counts as Integration" : "does not count as Integration"
|
|
35921
|
+
].join(" \xB7 ");
|
|
35922
|
+
if (integration.quiet === null) return [head, "no data query or synced cloud drive connected"];
|
|
35923
|
+
return [
|
|
35924
|
+
head,
|
|
35925
|
+
...labelledLines([
|
|
35926
|
+
[
|
|
35927
|
+
"last file in window",
|
|
35928
|
+
[integration.last_file_in_window ? momentText(integration.last_file_in_window) : "none"]
|
|
35929
|
+
],
|
|
35930
|
+
[
|
|
35931
|
+
"last sync attempt",
|
|
35932
|
+
[integration.last_sync_attempt ? momentText(integration.last_sync_attempt) : "none"]
|
|
35933
|
+
],
|
|
35934
|
+
["schedule", [integration.schedule ?? "none"]],
|
|
35935
|
+
["auto sync", [integration.auto_sync ? "on" : "off"]]
|
|
35936
|
+
])
|
|
35937
|
+
];
|
|
35938
|
+
}
|
|
35939
|
+
function writeHistory(out, history) {
|
|
35940
|
+
if (history.length === 0) {
|
|
35941
|
+
writeField(out, "history", ["none recorded (or the audit server could not be read)"]);
|
|
35942
|
+
return;
|
|
35943
|
+
}
|
|
35944
|
+
out.write(" history\n");
|
|
35945
|
+
const indent = " ";
|
|
35946
|
+
const rows = [["WHEN", "BY", "OPERATOR", "BEFORE \u2192 AFTER", "REASON"]];
|
|
35947
|
+
for (const entry of history) {
|
|
35948
|
+
rows.push([
|
|
35949
|
+
entry.at ? momentText(entry.at) : "-",
|
|
35950
|
+
entry.by ?? "-",
|
|
35951
|
+
entry.operator ?? "-",
|
|
35952
|
+
`${slotsText(entry.before)} \u2192 ${slotsText(entry.after)}`,
|
|
35953
|
+
entry.reason ? lookUp(HISTORY_REASONS, entry.reason) : "retyped in the filebox settings"
|
|
35954
|
+
]);
|
|
35955
|
+
}
|
|
35956
|
+
writeRows(
|
|
35957
|
+
out,
|
|
35958
|
+
rows.map(([first, ...others]) => [`${indent}${first ?? ""}`, ...others])
|
|
35959
|
+
);
|
|
35960
|
+
}
|
|
35961
|
+
function writeFilebox(out, answer) {
|
|
35962
|
+
const rollout = answer.rollout ? "" : " (outside the rollout)";
|
|
35963
|
+
out.write(
|
|
35964
|
+
`Filebox ${answer.id} "${answer.name}" \xB7 organization ${answer.organization_id}${rollout}
|
|
35965
|
+
`
|
|
35966
|
+
);
|
|
35967
|
+
writeField(out, "meaning", meaningLines(answer));
|
|
35968
|
+
writeField(out, "stored", [storedText(answer.stored), ...flowNote(answer)]);
|
|
35969
|
+
const derived = kindsText(answer.derived.kinds, answer.derived.report_format);
|
|
35970
|
+
const mark = answer.agrees === null ? "" : answer.agrees ? "\u2713 agrees" : "\u2717 disagrees";
|
|
35971
|
+
writeField(out, "rules", [
|
|
35972
|
+
mark === "" ? derived : `${derived.padEnd(Math.max(AGREES_COLUMN, derived.length + 2))}${mark}`
|
|
35973
|
+
]);
|
|
35974
|
+
writeField(out, "rule", [answer.derived.rule]);
|
|
35975
|
+
if (answer.derived.unknown_reason) {
|
|
35976
|
+
writeField(out, "unsure", [lookUp(UNSURE_PHRASES, answer.derived.unknown_reason)]);
|
|
35977
|
+
}
|
|
35978
|
+
writeField(
|
|
35979
|
+
out,
|
|
35980
|
+
"why",
|
|
35981
|
+
[answer.why.primary, answer.why.secondary].filter((w) => !!w).map(reasonText)
|
|
35982
|
+
);
|
|
35983
|
+
writeField(out, "dropped", splitReasons(answer.why.dropped).map(reasonText));
|
|
35984
|
+
writeField(out, "why not", splitReasons(answer.why.not).map(reasonText));
|
|
35985
|
+
writeField(out, "evidence", evidenceLines(answer.evidence));
|
|
35986
|
+
if (answer.integration) writeField(out, "integration", integrationLines(answer.integration));
|
|
35987
|
+
if (answer.history !== void 0) writeHistory(out, answer.history);
|
|
35988
|
+
}
|
|
35989
|
+
var NOT_FOUND_HINT = "Not found also holds a filebox of another organization: only that organization's support seat gets its answer.\n";
|
|
35990
|
+
function renderKindExplanation(out, answer, opts) {
|
|
35991
|
+
let first = true;
|
|
35992
|
+
if (opts.showAsOf) {
|
|
35993
|
+
out.write(`As of ${answer.as_of}
|
|
35994
|
+
`);
|
|
35995
|
+
first = false;
|
|
35996
|
+
}
|
|
35997
|
+
for (const filebox of answer.fileboxes) {
|
|
35998
|
+
if (!first) out.write("\n");
|
|
35999
|
+
writeFilebox(out, filebox);
|
|
36000
|
+
first = false;
|
|
36001
|
+
}
|
|
36002
|
+
if (answer.not_found.length > 0) {
|
|
36003
|
+
if (!first) out.write("\n");
|
|
36004
|
+
for (const id of answer.not_found) out.write(`Not found: filebox ${id}
|
|
36005
|
+
`);
|
|
36006
|
+
out.write(NOT_FOUND_HINT);
|
|
36007
|
+
}
|
|
36008
|
+
}
|
|
36009
|
+
async function runFileboxKindSummary(cfg) {
|
|
36010
|
+
const ctx = {
|
|
36011
|
+
command: "kind-summary",
|
|
36012
|
+
route: KIND_SUMMARY_PATH,
|
|
36013
|
+
client: cfg.client
|
|
36014
|
+
};
|
|
36015
|
+
const res = await getKindSummary(cfg.client, { signal: cfg.signal });
|
|
36016
|
+
const text2 = await readKindAnswer(res, ctx);
|
|
36017
|
+
if (cfg.asJson) {
|
|
36018
|
+
cfg.out.write(withNewline(text2));
|
|
36019
|
+
return;
|
|
36020
|
+
}
|
|
36021
|
+
renderKindSummary(
|
|
36022
|
+
cfg.out,
|
|
36023
|
+
decodeShape(parseKindBody(text2, KIND_SUMMARY_PATH))
|
|
36024
|
+
);
|
|
36025
|
+
}
|
|
36026
|
+
function countText(n) {
|
|
36027
|
+
return n.toLocaleString("en-US");
|
|
36028
|
+
}
|
|
36029
|
+
function countsLine(entries) {
|
|
36030
|
+
return [...entries].sort(([a, x], [b, y]) => y - x || a.localeCompare(b)).map(([label, n]) => `${label} ${countText(n)}`).join(" \xB7 ");
|
|
36031
|
+
}
|
|
36032
|
+
var UNTYPED_KEY = "(untyped)";
|
|
36033
|
+
function renderKindSummary(out, summary) {
|
|
36034
|
+
const rollout = summary.rollout ? "" : " (outside the rollout)";
|
|
36035
|
+
out.write(`Organization ${summary.organization_id}${rollout}
|
|
36036
|
+
`);
|
|
36037
|
+
const header = ["fileboxes", "typed", "untyped"];
|
|
36038
|
+
const rows = ["live", "archived"].map(
|
|
36039
|
+
(state) => [
|
|
36040
|
+
state,
|
|
36041
|
+
[summary[state].fileboxes, summary[state].typed, summary[state].untyped].map(countText)
|
|
36042
|
+
]
|
|
36043
|
+
);
|
|
36044
|
+
const widths = header.map(
|
|
36045
|
+
(h, i) => Math.max(h.length, ...rows.map(([, v]) => (v[i] ?? "").length))
|
|
36046
|
+
);
|
|
36047
|
+
const line = (cells) => cells.map((cell, i) => cell.padStart(widths[i] ?? 0)).join(" ");
|
|
36048
|
+
out.write(`${" ".repeat(2 + LABEL_WIDTH)}${line(header)}
|
|
36049
|
+
`);
|
|
36050
|
+
for (const [state, values] of rows) out.write(` ${state.padEnd(LABEL_WIDTH)}${line(values)}
|
|
36051
|
+
`);
|
|
36052
|
+
const sources = Object.entries(summary.sources).map(
|
|
36053
|
+
([source, n]) => [lookUp(SOURCE_WORDS, source), n]
|
|
36054
|
+
);
|
|
36055
|
+
writeField(out, "set by", [sources.length === 0 ? "none" : countsLine(sources)]);
|
|
36056
|
+
const kinds = Object.entries(summary.kinds);
|
|
36057
|
+
const combinations = (pairs) => countsLine(
|
|
36058
|
+
kinds.filter(([combination]) => combination.includes("+") === pairs).map(([combination, n]) => [kindsText(combination.split("+"), null), n])
|
|
36059
|
+
);
|
|
36060
|
+
const typeLines = [combinations(false), combinations(true)].filter((l) => l !== "");
|
|
36061
|
+
writeField(out, "types", typeLines.length === 0 ? ["none"] : typeLines);
|
|
36062
|
+
if (summary.upload_channel_kinds === void 0) return;
|
|
36063
|
+
const uploads = Object.entries(summary.upload_channel_kinds).map(
|
|
36064
|
+
([combination, n]) => [
|
|
36065
|
+
combination === UNTYPED_KEY ? "untyped" : kindsText(combination.split("+"), null),
|
|
36066
|
+
n
|
|
36067
|
+
]
|
|
36068
|
+
);
|
|
36069
|
+
writeField(
|
|
36070
|
+
out,
|
|
36071
|
+
"uploads",
|
|
36072
|
+
uploads.length === 0 ? ["no live filebox has email or API upload on"] : ["live fileboxes with email / API upload on, by stored type", countsLine(uploads)]
|
|
36073
|
+
);
|
|
36074
|
+
}
|
|
36075
|
+
var SUPPORT_SEAT_HELP = [
|
|
36076
|
+
"",
|
|
36077
|
+
"Support seat only. The server answers the support seat of the organization",
|
|
36078
|
+
"asked about and refuses any other seat (HTTP 405), even a support person",
|
|
36079
|
+
"working in a customer user's seat. Log in as that seat, then run as it:",
|
|
36080
|
+
" dr login-as <support seat email> --server <S>"
|
|
36081
|
+
];
|
|
36082
|
+
function registerFileboxKindCommands(fb) {
|
|
36083
|
+
const explainCmd = fb.command("kind-explain").description(
|
|
36084
|
+
"Why fileboxes have their type: the stored type and who set it, what the migration's rules make of it, whether the two agree, the rule, why, and the evidence (read-only, support seat only)"
|
|
36085
|
+
).argument("<filebox_ids...>", 'filebox ids (positive integers; "12,34" also accepted)').option(
|
|
36086
|
+
"--history",
|
|
36087
|
+
`Also list the recorded type changes (${KIND_EXPLAIN_MAX_IDS_WITH_HISTORY} ids per request)`,
|
|
36088
|
+
false
|
|
36089
|
+
).option(
|
|
36090
|
+
"--as-of <date>",
|
|
36091
|
+
"Judge a quiet integration as of this day (YYYY-MM-DD, its end UTC); every other signal is read as it is now"
|
|
36092
|
+
).option("--json", "Print the server's answer instead of the readable form (see below)", false).addHelpText(
|
|
36093
|
+
"after",
|
|
36094
|
+
[
|
|
36095
|
+
...SUPPORT_SEAT_HELP,
|
|
36096
|
+
" dr filebox kind-explain 48213 48999 --server <S> --account <support seat email>",
|
|
36097
|
+
"A filebox of another organization, a folder, or an id that does not exist is",
|
|
36098
|
+
'listed as "Not found". It changes nothing and answers whatever the',
|
|
36099
|
+
"organization's feature flag; each filebox says whether it is in the rollout.",
|
|
36100
|
+
"",
|
|
36101
|
+
`More than ${KIND_EXPLAIN_MAX_IDS} ids (${KIND_EXPLAIN_MAX_IDS_WITH_HISTORY} with --history) are sent as several requests, one`,
|
|
36102
|
+
"after another, the later ones pinned to the first answer's as_of. --json",
|
|
36103
|
+
"prints a single request's body unchanged; for several it prints one body in",
|
|
36104
|
+
"the server's shape: fileboxes and not_found concatenated in request order,",
|
|
36105
|
+
"as_of from the first. Either way --json keeps the HTML entities the server",
|
|
36106
|
+
'sends (a name reads "P&L"): unlike `filebox list --json`, nothing is decoded.'
|
|
36107
|
+
].join("\n")
|
|
36108
|
+
).action(
|
|
36109
|
+
async (idArgs, opts) => {
|
|
36110
|
+
const ids = parseKindExplainIds(idArgs);
|
|
36111
|
+
const asOf = parseKindAsOf(opts.asOf);
|
|
36112
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36113
|
+
try {
|
|
36114
|
+
await runFileboxKindExplain({
|
|
36115
|
+
client: setup.client,
|
|
36116
|
+
ids,
|
|
36117
|
+
asOf,
|
|
36118
|
+
history: opts.history === true,
|
|
36119
|
+
asJson: opts.json === true,
|
|
36120
|
+
signal: setup.signal,
|
|
36121
|
+
out: process.stdout,
|
|
36122
|
+
extendTimeout: setup.extendTimeout
|
|
36123
|
+
});
|
|
36124
|
+
} finally {
|
|
36125
|
+
setup.cleanup();
|
|
36126
|
+
}
|
|
36127
|
+
}
|
|
36128
|
+
);
|
|
36129
|
+
addServerFlag(explainCmd);
|
|
36130
|
+
addAccountFlag(explainCmd);
|
|
36131
|
+
const summaryCmd = fb.command("kind-summary").description(
|
|
36132
|
+
"How far the seat's organization is typed: live and archived fileboxes, typed and untyped, who set the types, and every stored combination (read-only, support seat only)"
|
|
36133
|
+
).option("--json", "Print the server's answer unchanged", false).addHelpText(
|
|
36134
|
+
"after",
|
|
36135
|
+
[
|
|
36136
|
+
...SUPPORT_SEAT_HELP,
|
|
36137
|
+
" dr filebox kind-summary --server <S> --account <support seat email>",
|
|
36138
|
+
'It answers for the organization of that seat. "set by" and "types" count',
|
|
36139
|
+
'live and archived fileboxes together; in "set by", "flow" is a flow that',
|
|
36140
|
+
"typed the filebox at creation or when a connection was added later (a table",
|
|
36141
|
+
"attaching it as a lookup, a LUT/FX label, a data source or synced drive",
|
|
36142
|
+
'connected, email / API upload turned on). "uploads" counts live fileboxes with',
|
|
36143
|
+
"email or API upload on by their stored type: the rules make each an",
|
|
36144
|
+
"Integration, dropping a Model, but turning an upload on keeps a Model, so a",
|
|
36145
|
+
"Model counted there is expected drift (kind-explain says why)."
|
|
36146
|
+
].join("\n")
|
|
36147
|
+
).action(async (opts) => {
|
|
36148
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36149
|
+
try {
|
|
36150
|
+
await runFileboxKindSummary({
|
|
36151
|
+
client: setup.client,
|
|
36152
|
+
asJson: opts.json === true,
|
|
36153
|
+
signal: setup.signal,
|
|
36154
|
+
out: process.stdout
|
|
36155
|
+
});
|
|
36156
|
+
} finally {
|
|
36157
|
+
setup.cleanup();
|
|
36158
|
+
}
|
|
36159
|
+
});
|
|
36160
|
+
addServerFlag(summaryCmd);
|
|
36161
|
+
addAccountFlag(summaryCmd);
|
|
36162
|
+
}
|
|
36163
|
+
|
|
35465
36164
|
// src/commands/_typedError.ts
|
|
35466
36165
|
function isTypedError(err) {
|
|
35467
36166
|
return !(err instanceof Error) || err.constructor !== Error || "code" in err;
|
|
@@ -36148,11 +36847,11 @@ function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw
|
|
|
36148
36847
|
}
|
|
36149
36848
|
return out;
|
|
36150
36849
|
}
|
|
36151
|
-
async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal) {
|
|
36850
|
+
async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal, preRead) {
|
|
36152
36851
|
try {
|
|
36153
36852
|
const [versions, dataQuery] = await Promise.all([
|
|
36154
36853
|
listVersions(client, fileboxId, {}, { signal }),
|
|
36155
|
-
getDataQueryRaw(client, fileboxId, { signal })
|
|
36854
|
+
preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal })
|
|
36156
36855
|
]);
|
|
36157
36856
|
return buildDataQuerySyncBaseline(periods, versions, dataQuery);
|
|
36158
36857
|
} catch (err) {
|
|
@@ -36252,7 +36951,8 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
|
|
|
36252
36951
|
})),
|
|
36253
36952
|
unmatched_new_versions: progress.unmatchedNewVersions,
|
|
36254
36953
|
error: progress.error ?? null,
|
|
36255
|
-
waited_seconds: waitedSec
|
|
36954
|
+
waited_seconds: waitedSec,
|
|
36955
|
+
warnings: cfg.warnings ?? []
|
|
36256
36956
|
});
|
|
36257
36957
|
return;
|
|
36258
36958
|
}
|
|
@@ -36310,6 +37010,63 @@ function failureMessage(fileboxId, progress, outcome, waitedSec) {
|
|
|
36310
37010
|
return `data-query sync on filebox ${fileboxId} did not finish within ${waitedSec}s \u2014 ${pending.length} period(s) unfinished: ${pendingList}. Increase --timeout, or re-check with 'dr filebox versions ${fileboxId}'; do not blindly re-trigger.`;
|
|
36311
37011
|
}
|
|
36312
37012
|
|
|
37013
|
+
// src/commands/_aggregatorBalance.ts
|
|
37014
|
+
var AGGREGATOR_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
37015
|
+
"plaid",
|
|
37016
|
+
"quiltt",
|
|
37017
|
+
"saltedge",
|
|
37018
|
+
"flinks"
|
|
37019
|
+
]);
|
|
37020
|
+
function asRecord2(v) {
|
|
37021
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
37022
|
+
}
|
|
37023
|
+
function parsedRecord(raw) {
|
|
37024
|
+
if (typeof raw === "string") {
|
|
37025
|
+
try {
|
|
37026
|
+
return asRecord2(JSON.parse(raw));
|
|
37027
|
+
} catch {
|
|
37028
|
+
return void 0;
|
|
37029
|
+
}
|
|
37030
|
+
}
|
|
37031
|
+
return asRecord2(raw);
|
|
37032
|
+
}
|
|
37033
|
+
async function detectAggregatorBalanceQuery(client, fileboxId, signal) {
|
|
37034
|
+
let dataQueryRaw;
|
|
37035
|
+
try {
|
|
37036
|
+
dataQueryRaw = await getDataQueryRaw(client, fileboxId, { signal });
|
|
37037
|
+
} catch {
|
|
37038
|
+
if (signal.aborted) throw signal.reason ?? new Error("aborted");
|
|
37039
|
+
return void 0;
|
|
37040
|
+
}
|
|
37041
|
+
const dataQuery = asRecord2(dataQueryRaw);
|
|
37042
|
+
const config = parsedRecord(dataQuery?.["config"]);
|
|
37043
|
+
const objectType = config?.["objectType"] ?? parsedRecord(config?.["query"])?.["objectType"];
|
|
37044
|
+
const rawConnection = dataQuery?.["connection"];
|
|
37045
|
+
const connectionId = typeof rawConnection === "number" || typeof rawConnection === "string" ? Number(rawConnection) : NaN;
|
|
37046
|
+
if (typeof objectType !== "string" || objectType.toLowerCase() !== "balances" || !Number.isInteger(connectionId) || connectionId <= 0) {
|
|
37047
|
+
return { dataQueryRaw };
|
|
37048
|
+
}
|
|
37049
|
+
let type;
|
|
37050
|
+
try {
|
|
37051
|
+
const integration = await getById2(client, connectionId, { signal });
|
|
37052
|
+
type = asRecord2(integration.type);
|
|
37053
|
+
} catch {
|
|
37054
|
+
if (signal.aborted) throw signal.reason ?? new Error("aborted");
|
|
37055
|
+
return { dataQueryRaw };
|
|
37056
|
+
}
|
|
37057
|
+
const name = type?.["name"];
|
|
37058
|
+
if (typeof name !== "string" || !AGGREGATOR_TYPE_NAMES.has(name.toLowerCase())) {
|
|
37059
|
+
return { dataQueryRaw };
|
|
37060
|
+
}
|
|
37061
|
+
const display = type?.["display_name"];
|
|
37062
|
+
const aggregator = typeof display === "string" && display.trim() !== "" ? display.trim() : name;
|
|
37063
|
+
return { dataQueryRaw, aggregator };
|
|
37064
|
+
}
|
|
37065
|
+
function aggregatorBalanceWarning(fileboxId, aggregator, periodCount) {
|
|
37066
|
+
const what = periodCount > 1 ? `these ${periodCount} periods will all get today's balance (identical versions under different dates)` : "that period will get today's balance, labelled as if it were that period's";
|
|
37067
|
+
return `filebox ${fileboxId} syncs balances from ${aggregator}, an aggregator: it returns today's balance whatever --date asks for, so ${what}. Backfill only transactions; for balances, sync the current period only and let the nightly schedule build the history. Multi-period balance backfill works only on a direct bank API connection.`;
|
|
37068
|
+
}
|
|
37069
|
+
|
|
36313
37070
|
// src/commands/filebox.ts
|
|
36314
37071
|
async function fileToBlob(filePath) {
|
|
36315
37072
|
const buf = await fsPromises.readFile(filePath);
|
|
@@ -36623,7 +37380,7 @@ ${JSON.stringify(result)}
|
|
|
36623
37380
|
`
|
|
36624
37381
|
);
|
|
36625
37382
|
}
|
|
36626
|
-
function
|
|
37383
|
+
function asRecord3(v) {
|
|
36627
37384
|
return v !== null && typeof v === "object" ? v : void 0;
|
|
36628
37385
|
}
|
|
36629
37386
|
function numOf(r, k) {
|
|
@@ -36633,9 +37390,9 @@ function strOf(r, k) {
|
|
|
36633
37390
|
return r !== void 0 && typeof r[k] === "string" ? r[k] : void 0;
|
|
36634
37391
|
}
|
|
36635
37392
|
function extractUploadedVersionMeta(result) {
|
|
36636
|
-
const r =
|
|
36637
|
-
const dv =
|
|
36638
|
-
const docObj =
|
|
37393
|
+
const r = asRecord3(result);
|
|
37394
|
+
const dv = asRecord3(r?.["document_version"]);
|
|
37395
|
+
const docObj = asRecord3(r?.["document"]);
|
|
36639
37396
|
return {
|
|
36640
37397
|
id: numOf(r, "id") ?? numOf(dv, "id") ?? numOf(docObj, "latest_version_id") ?? 0,
|
|
36641
37398
|
version: numOf(r, "version") ?? numOf(dv, "version") ?? 0,
|
|
@@ -36794,7 +37551,7 @@ async function runFileboxUploadVersion(cfg) {
|
|
|
36794
37551
|
...cycleTagVerdictJSON(confirmation)
|
|
36795
37552
|
});
|
|
36796
37553
|
} else if (cfg.label !== void 0) {
|
|
36797
|
-
cfg.out.write(JSON.stringify({ ...
|
|
37554
|
+
cfg.out.write(JSON.stringify({ ...asRecord3(result), ...labelJSON }) + "\n");
|
|
36798
37555
|
} else {
|
|
36799
37556
|
cfg.out.write(JSON.stringify(result) + "\n");
|
|
36800
37557
|
}
|
|
@@ -38600,6 +39357,7 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38600
39357
|
const dateTargets = [];
|
|
38601
39358
|
const periodLabels = [];
|
|
38602
39359
|
const waitPeriods = [];
|
|
39360
|
+
const pastOrFuturePeriods = /* @__PURE__ */ new Set();
|
|
38603
39361
|
if (parsedDates.length > 0) {
|
|
38604
39362
|
const details = await getDetails(cfg.client, cfg.id, {
|
|
38605
39363
|
signal: cfg.signal
|
|
@@ -38620,6 +39378,11 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38620
39378
|
);
|
|
38621
39379
|
}
|
|
38622
39380
|
}
|
|
39381
|
+
const now = cfg.today ?? /* @__PURE__ */ new Date();
|
|
39382
|
+
const todays = [
|
|
39383
|
+
{ year: now.getFullYear(), month0: now.getMonth(), day: now.getDate() },
|
|
39384
|
+
{ year: now.getUTCFullYear(), month0: now.getUTCMonth(), day: now.getUTCDate() }
|
|
39385
|
+
].map((t) => dateTags.map((tag) => resolveDateForSubType(tagSubType(tag), t).value));
|
|
38623
39386
|
for (const parsed2 of parsedDates) {
|
|
38624
39387
|
const resolved = dateTags.map((tag) => {
|
|
38625
39388
|
const { value, label: label2 } = resolveDateForSubType(tagSubType(tag), parsed2);
|
|
@@ -38628,6 +39391,8 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38628
39391
|
dateTargets.push(
|
|
38629
39392
|
JSON.stringify(resolved.map(({ id, name, value }) => ({ id, name, value })))
|
|
38630
39393
|
);
|
|
39394
|
+
const isToday = todays.some((t) => resolved.every((r, i) => r.value === t[i]));
|
|
39395
|
+
if (!isToday) pastOrFuturePeriods.add(resolved.map((r) => r.value).join("|"));
|
|
38631
39396
|
const label = resolved.map((r) => r.label).join(" + ");
|
|
38632
39397
|
periodLabels.push(label);
|
|
38633
39398
|
waitPeriods.push({
|
|
@@ -38642,7 +39407,31 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38642
39407
|
if (waitPeriods.length === 0) {
|
|
38643
39408
|
waitPeriods.push({ label: "latest sync", tags: [] });
|
|
38644
39409
|
}
|
|
38645
|
-
const
|
|
39410
|
+
const warnings = [];
|
|
39411
|
+
let preReadDataQuery;
|
|
39412
|
+
if (pastOrFuturePeriods.size > 0) {
|
|
39413
|
+
const detected = await detectAggregatorBalanceQuery(cfg.client, cfg.id, cfg.signal);
|
|
39414
|
+
if (detected !== void 0) {
|
|
39415
|
+
preReadDataQuery = { raw: detected.dataQueryRaw };
|
|
39416
|
+
if (detected.aggregator !== void 0) {
|
|
39417
|
+
warnings.push(
|
|
39418
|
+
aggregatorBalanceWarning(cfg.id, detected.aggregator, pastOrFuturePeriods.size)
|
|
39419
|
+
);
|
|
39420
|
+
}
|
|
39421
|
+
}
|
|
39422
|
+
}
|
|
39423
|
+
const errOut = cfg.err ?? cfg.wait?.err ?? process.stderr;
|
|
39424
|
+
for (const w of warnings) {
|
|
39425
|
+
errOut.write(`warning: ${w}
|
|
39426
|
+
`);
|
|
39427
|
+
}
|
|
39428
|
+
const baseline = cfg.wait === void 0 ? void 0 : await captureDataQuerySyncBaseline(
|
|
39429
|
+
cfg.client,
|
|
39430
|
+
cfg.id,
|
|
39431
|
+
waitPeriods,
|
|
39432
|
+
cfg.signal,
|
|
39433
|
+
preReadDataQuery
|
|
39434
|
+
);
|
|
38646
39435
|
const multipleTags = [...rawTags, ...dateTargets];
|
|
38647
39436
|
const body = {};
|
|
38648
39437
|
if (multipleTags.length > 0) {
|
|
@@ -38685,6 +39474,7 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38685
39474
|
out: cfg.out,
|
|
38686
39475
|
errOut: cfg.wait.err ?? process.stderr,
|
|
38687
39476
|
signal: cfg.signal,
|
|
39477
|
+
warnings,
|
|
38688
39478
|
pollIntervalMs: cfg.wait.pollIntervalMs,
|
|
38689
39479
|
maxPollIntervalMs: cfg.wait.maxPollIntervalMs,
|
|
38690
39480
|
sleep: cfg.wait.sleep,
|
|
@@ -38792,9 +39582,9 @@ async function runFileboxDownload(cfg) {
|
|
|
38792
39582
|
const fh = await fsPromises.open(target, "w");
|
|
38793
39583
|
let total = 0;
|
|
38794
39584
|
try {
|
|
38795
|
-
for (const
|
|
38796
|
-
await fh.write(
|
|
38797
|
-
total +=
|
|
39585
|
+
for (const chunk2 of head) {
|
|
39586
|
+
await fh.write(chunk2);
|
|
39587
|
+
total += chunk2.length;
|
|
38798
39588
|
}
|
|
38799
39589
|
if (!streamDone) {
|
|
38800
39590
|
while (true) {
|
|
@@ -39978,7 +40768,7 @@ function registerFileboxCommand(root) {
|
|
|
39978
40768
|
addServerFlag(dqClearCmd);
|
|
39979
40769
|
addAccountFlag(dqClearCmd);
|
|
39980
40770
|
const dqSyncCmd = dataQuery.command("sync").description(
|
|
39981
|
-
`Manually trigger a sync of the filebox's data-query (async on server). --date YYYY-MM-DD (repeatable) scopes the sync to that date, truncated to whatever granularity the filebox's own date tag uses \u2014 e.g. --date 2026-07-01 syncs "July 2026" on a monthly filebox, "Q3 2026" on a quarterly one, or the exact day/ISO week on a daily/weekly one. Repeat to backfill several periods in one call.`
|
|
40771
|
+
`Manually trigger a sync of the filebox's data-query (async on server). --date YYYY-MM-DD (repeatable) scopes the sync to that date, truncated to whatever granularity the filebox's own date tag uses \u2014 e.g. --date 2026-07-01 syncs "July 2026" on a monthly filebox, "Q3 2026" on a quarterly one, or the exact day/ISO week on a daily/weekly one. Repeat to backfill several periods in one call. Balances from an aggregator (Plaid, Quiltt, Salt Edge, Flinks) are always today's balance whatever --date says, so backfilling them writes identical versions under different dates (the CLI warns): backfill only transactions, sync balances for the current period and let the nightly schedule build the history. Multi-period balance backfill works only on a direct bank API connection.`
|
|
39982
40772
|
).argument("<filebox_id>", "filebox id (positive integer)").option(
|
|
39983
40773
|
"--date <YYYY-MM-DD>",
|
|
39984
40774
|
"sync whatever date-tag period this date falls in, e.g. 2026-07-01. Repeatable.",
|
|
@@ -40025,6 +40815,7 @@ function registerFileboxCommand(root) {
|
|
|
40025
40815
|
addServerFlag(dqSyncCmd);
|
|
40026
40816
|
addAccountFlag(dqSyncCmd);
|
|
40027
40817
|
registerFileboxAdminCommands(fb, setupCall, addServerFlag, addAccountFlag);
|
|
40818
|
+
registerFileboxKindCommands(fb);
|
|
40028
40819
|
registerFileboxRescanVersionCommand(fb);
|
|
40029
40820
|
}
|
|
40030
40821
|
|
|
@@ -45680,52 +46471,6 @@ async function getAuditExportStatus(client, params, options) {
|
|
|
45680
46471
|
return out ?? { job_id: params.jobId, status: "PENDING" };
|
|
45681
46472
|
}
|
|
45682
46473
|
|
|
45683
|
-
// src/commands/_reconciliationDate.ts
|
|
45684
|
-
function validateDateString(orig, flagName) {
|
|
45685
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(orig)) {
|
|
45686
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
|
|
45687
|
-
}
|
|
45688
|
-
const parts = orig.split("-").map(Number);
|
|
45689
|
-
const [year, month1, day] = parts;
|
|
45690
|
-
const probe = new Date(Date.UTC(year, month1 - 1, day));
|
|
45691
|
-
if (probe.getUTCFullYear() !== year || probe.getUTCMonth() + 1 !== month1 || probe.getUTCDate() !== day) {
|
|
45692
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
|
|
45693
|
-
}
|
|
45694
|
-
return { year, month1, day };
|
|
45695
|
-
}
|
|
45696
|
-
function parseDateFlag(orig, flagName) {
|
|
45697
|
-
let normalized = orig;
|
|
45698
|
-
if (/^\d{4}-\d{2}$/.test(orig)) {
|
|
45699
|
-
normalized = `${orig}-01`;
|
|
45700
|
-
}
|
|
45701
|
-
let year;
|
|
45702
|
-
let month1;
|
|
45703
|
-
try {
|
|
45704
|
-
({ year, month1 } = validateDateString(normalized, flagName));
|
|
45705
|
-
} catch {
|
|
45706
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
|
|
45707
|
-
}
|
|
45708
|
-
return Math.floor(Date.UTC(year, month1, 0) / 1e3);
|
|
45709
|
-
}
|
|
45710
|
-
function parseDateFlagEpoch(orig, flagName) {
|
|
45711
|
-
let normalized = orig;
|
|
45712
|
-
if (/^\d{4}-\d{2}$/.test(orig)) {
|
|
45713
|
-
normalized = `${orig}-01`;
|
|
45714
|
-
}
|
|
45715
|
-
let year;
|
|
45716
|
-
let month1;
|
|
45717
|
-
try {
|
|
45718
|
-
({ year, month1 } = validateDateString(normalized, flagName));
|
|
45719
|
-
} catch {
|
|
45720
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
|
|
45721
|
-
}
|
|
45722
|
-
return Math.floor(Date.UTC(year, month1 - 1, 1, 12) / 1e3);
|
|
45723
|
-
}
|
|
45724
|
-
function parseDateFlagDay(orig, flagName) {
|
|
45725
|
-
const { year, month1, day } = validateDateString(orig, flagName);
|
|
45726
|
-
return Math.floor(Date.UTC(year, month1 - 1, day) / 1e3);
|
|
45727
|
-
}
|
|
45728
|
-
|
|
45729
46474
|
// src/commands/_reconciliationSettingsFile.ts
|
|
45730
46475
|
var ALLOWED_FREQUENCIES = /* @__PURE__ */ new Set(["Monthly", "Quarterly", "Yearly", "Exclude"]);
|
|
45731
46476
|
var REQUIRED_FIELDS = [
|
|
@@ -47122,17 +47867,17 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
|
47122
47867
|
execute: async () => {
|
|
47123
47868
|
extendForBatch(cfg.extendTimeout, chunks.length);
|
|
47124
47869
|
let applied = 0;
|
|
47125
|
-
for (const
|
|
47870
|
+
for (const chunk2 of chunks) {
|
|
47126
47871
|
try {
|
|
47127
|
-
await updateAccountSettings(cfg.client,
|
|
47872
|
+
await updateAccountSettings(cfg.client, chunk2, { signal: cfg.signal });
|
|
47128
47873
|
} catch (err) {
|
|
47129
47874
|
const msg = err instanceof Error ? err.message : String(err);
|
|
47130
47875
|
throw new Error(
|
|
47131
|
-
`PATCH failed after ${applied} of ${changed.length} rows were written (rows ${applied + 1}\u2013${applied +
|
|
47876
|
+
`PATCH failed after ${applied} of ${changed.length} rows were written (rows ${applied + 1}\u2013${applied + chunk2.length} may be partially applied): ${msg}
|
|
47132
47877
|
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
47133
47878
|
);
|
|
47134
47879
|
}
|
|
47135
|
-
applied +=
|
|
47880
|
+
applied += chunk2.length;
|
|
47136
47881
|
}
|
|
47137
47882
|
cfg.out.write(
|
|
47138
47883
|
`Updated ${applied} account setting${plural(applied)} (${unchanged} unchanged).
|
|
@@ -47725,10 +48470,10 @@ async function writeMemberChunks(ctx, planned, items, chunkSize) {
|
|
|
47725
48470
|
extendForBatch(ctx.extendTimeout, requests * 2);
|
|
47726
48471
|
let applied = 0;
|
|
47727
48472
|
for (let i = 0; i < items.length; i += chunkSize) {
|
|
47728
|
-
const
|
|
48473
|
+
const chunk2 = items.slice(i, i + chunkSize);
|
|
47729
48474
|
let drift;
|
|
47730
48475
|
try {
|
|
47731
|
-
drift = await chunkDrift(ctx, planned,
|
|
48476
|
+
drift = await chunkDrift(ctx, planned, chunk2);
|
|
47732
48477
|
} catch (err) {
|
|
47733
48478
|
const msg = err instanceof Error ? err.message : String(err);
|
|
47734
48479
|
throw new Error(
|
|
@@ -47744,23 +48489,23 @@ The tenant changed since the plan. Re-run the same command without --confirm-tok
|
|
|
47744
48489
|
);
|
|
47745
48490
|
}
|
|
47746
48491
|
try {
|
|
47747
|
-
await updateAccountMembers(ctx.client,
|
|
48492
|
+
await updateAccountMembers(ctx.client, chunk2, { signal: ctx.signal });
|
|
47748
48493
|
} catch (err) {
|
|
47749
48494
|
const msg = err instanceof Error ? err.message : String(err);
|
|
47750
48495
|
throw new Error(
|
|
47751
|
-
`PATCH failed after ${applied} of ${items.length} account${plural(items.length)} were written (accounts ${i + 1}\u2013${i +
|
|
48496
|
+
`PATCH failed after ${applied} of ${items.length} account${plural(items.length)} were written (accounts ${i + 1}\u2013${i + chunk2.length} may be partially applied): ${msg}
|
|
47752
48497
|
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
47753
48498
|
);
|
|
47754
48499
|
}
|
|
47755
|
-
applied +=
|
|
48500
|
+
applied += chunk2.length;
|
|
47756
48501
|
}
|
|
47757
48502
|
return applied;
|
|
47758
48503
|
}
|
|
47759
|
-
async function chunkDrift(cfg, planned,
|
|
48504
|
+
async function chunkDrift(cfg, planned, chunk2) {
|
|
47760
48505
|
const now = await getAccounts(cfg.client, { date: cfg.date }, { signal: cfg.signal });
|
|
47761
48506
|
const nowById = new Map(now.map((a) => [a.account_id, a]));
|
|
47762
48507
|
const drift = [];
|
|
47763
|
-
for (const { account_id: id } of
|
|
48508
|
+
for (const { account_id: id } of chunk2) {
|
|
47764
48509
|
const before = planned.get(id);
|
|
47765
48510
|
const after = nowById.get(id);
|
|
47766
48511
|
const label = `${before.account_name} (${id})`;
|
|
@@ -58742,8 +59487,8 @@ function captureVersion() {
|
|
|
58742
59487
|
return new Promise((resolve3) => {
|
|
58743
59488
|
const child2 = spawn2("dr", ["--version"], { shell: true });
|
|
58744
59489
|
let out = "";
|
|
58745
|
-
child2.stdout.on("data", (
|
|
58746
|
-
out +=
|
|
59490
|
+
child2.stdout.on("data", (chunk2) => {
|
|
59491
|
+
out += chunk2.toString();
|
|
58747
59492
|
});
|
|
58748
59493
|
child2.on("close", (code) => {
|
|
58749
59494
|
if (code !== 0) {
|
|
@@ -62975,7 +63720,7 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
|
62975
63720
|
if (outstanding.length === 0) break;
|
|
62976
63721
|
if (now() > deadline || inner.signal.aborted) break;
|
|
62977
63722
|
for (let i = 0; i < outstanding.length; i += MAX_BULK_STATUS_IDS) {
|
|
62978
|
-
const
|
|
63723
|
+
const chunk2 = outstanding.slice(i, i + MAX_BULK_STATUS_IDS);
|
|
62979
63724
|
const tickAc = new AbortController();
|
|
62980
63725
|
const onInnerAbort = () => tickAc.abort(inner.signal.reason);
|
|
62981
63726
|
if (inner.signal.aborted) {
|
|
@@ -62984,13 +63729,13 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
|
62984
63729
|
inner.signal.addEventListener("abort", onInnerAbort, { once: true });
|
|
62985
63730
|
}
|
|
62986
63731
|
try {
|
|
62987
|
-
const resp = await getBulkProcessStatus(client,
|
|
63732
|
+
const resp = await getBulkProcessStatus(client, chunk2, {
|
|
62988
63733
|
signal: tickAc.signal
|
|
62989
63734
|
});
|
|
62990
|
-
for (let j = 0; j <
|
|
63735
|
+
for (let j = 0; j < chunk2.length; j++) {
|
|
62991
63736
|
const entry = resp.results[j];
|
|
62992
63737
|
if (entry !== void 0 && isTerminalAiStatus(entry.ai_processing_status)) {
|
|
62993
|
-
resolved.set(
|
|
63738
|
+
resolved.set(chunk2[j], entry);
|
|
62994
63739
|
}
|
|
62995
63740
|
}
|
|
62996
63741
|
} catch (err) {
|
|
@@ -64751,10 +65496,10 @@ async function extractSync(cfg, parts) {
|
|
|
64751
65496
|
const chunked = chunkContracts([...parts], MAX_FILES_PER_BATCH);
|
|
64752
65497
|
let abortedMidRun = false;
|
|
64753
65498
|
for (let ci = 0; ci < chunked.length; ci++) {
|
|
64754
|
-
const
|
|
65499
|
+
const chunk2 = chunked[ci];
|
|
64755
65500
|
try {
|
|
64756
65501
|
const results = await retryAsync(
|
|
64757
|
-
() => uploadFilesWithAi(cfg.client,
|
|
65502
|
+
() => uploadFilesWithAi(cfg.client, chunk2, {
|
|
64758
65503
|
isAdditional: cfg.isAdditional,
|
|
64759
65504
|
signal: cfg.signal
|
|
64760
65505
|
}),
|
|
@@ -64772,7 +65517,7 @@ async function extractSync(cfg, parts) {
|
|
|
64772
65517
|
}
|
|
64773
65518
|
);
|
|
64774
65519
|
allResults.push(...results);
|
|
64775
|
-
for (const part of
|
|
65520
|
+
for (const part of chunk2) {
|
|
64776
65521
|
sourceFiles.push(part.sourcePath);
|
|
64777
65522
|
outcomes.push({ path: part.sourcePath, ok: true, detail: "extracted" });
|
|
64778
65523
|
}
|
|
@@ -64780,7 +65525,7 @@ async function extractSync(cfg, parts) {
|
|
|
64780
65525
|
const msg = err instanceof Error ? err.message : String(err);
|
|
64781
65526
|
const kind = classifyFailure(err);
|
|
64782
65527
|
const detail = kind === "ambiguous" ? `${msg} \u2014 upstream may still be processing; check before re-sending` : msg;
|
|
64783
|
-
for (const part of
|
|
65528
|
+
for (const part of chunk2) {
|
|
64784
65529
|
outcomes.push({ path: part.sourcePath, ok: false, detail });
|
|
64785
65530
|
}
|
|
64786
65531
|
if (kind === "abort") {
|
|
@@ -67787,8 +68532,8 @@ Pipe it (printf '%s\\n' "$APP_PASSWORD" | dr desk smtp set \u2026) or use --pass
|
|
|
67787
68532
|
}
|
|
67788
68533
|
const chunks = [];
|
|
67789
68534
|
let total = 0;
|
|
67790
|
-
for await (const
|
|
67791
|
-
const buf = Buffer.isBuffer(
|
|
68535
|
+
for await (const chunk2 of process.stdin) {
|
|
68536
|
+
const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(String(chunk2));
|
|
67792
68537
|
total += buf.length;
|
|
67793
68538
|
if (total > maxBytes) {
|
|
67794
68539
|
throw new Error(
|