@datarails-ai/dr-cli 1.6.122 → 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/README.md +1 -1
- package/dist/cli.js +1072 -234
- package/dist/scopes-map.json +17 -1
- package/dist/skills/dr-cli/SKILL.md +11 -3
- package/dist/skills/dr-cli/reference/filebox-documents.md +144 -4
- package/dist/skills/dr-filebox-api-setup/SKILL.md +2 -0
- package/package.json +1 -1
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-
|
|
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);
|
|
@@ -891,7 +891,7 @@ async function exchangeApiKey(host, apiKey, options) {
|
|
|
891
891
|
}
|
|
892
892
|
function describeApiKeyExchangeFailure(err, identity2) {
|
|
893
893
|
if (err.httpStatus === 401) {
|
|
894
|
-
return `the server refused the API key for ${identity2} \u2014 it may be revoked or expired, API keys may not be enabled for your organization (ask an admin), or the server may be temporarily unable to evaluate that.`;
|
|
894
|
+
return `the server refused the API key for ${identity2} \u2014 it may be revoked or expired, API keys may not be enabled for your organization (ask an admin), or the server may be temporarily unable to evaluate that. If the key was revoked or expired, start a new session with a new key; retrying with this one will not help.`;
|
|
895
895
|
}
|
|
896
896
|
if (err.httpStatus === 404) {
|
|
897
897
|
return `${identity2}: this server does not support API keys (the exchange endpoint answered 404).`;
|
|
@@ -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
|
}
|
|
@@ -37600,22 +38357,68 @@ async function runFileboxSetVersionWorkflow(cfg) {
|
|
|
37600
38357
|
`document ${cfg.docId} has no WORKFLOW tag in its config (available: ${availMsg})`
|
|
37601
38358
|
);
|
|
37602
38359
|
}
|
|
37603
|
-
const
|
|
37604
|
-
|
|
37605
|
-
|
|
37606
|
-
const
|
|
37607
|
-
|
|
37608
|
-
cfg.
|
|
37609
|
-
|
|
37610
|
-
|
|
37611
|
-
|
|
37612
|
-
|
|
37613
|
-
|
|
37614
|
-
|
|
38360
|
+
const err = cfg.err ?? process.stderr;
|
|
38361
|
+
const { target } = await loadTargetVersion(cfg.client, cfg.docId, cfg.versionId, cfg.signal);
|
|
38362
|
+
const current = tagValueOf(target.tags, tag.id);
|
|
38363
|
+
const previous = typeof current === "string" ? current : null;
|
|
38364
|
+
const writer = new FullTagSetWriter({
|
|
38365
|
+
client: cfg.client,
|
|
38366
|
+
docId: cfg.docId,
|
|
38367
|
+
versionId: cfg.versionId,
|
|
38368
|
+
snapshot: target.tags,
|
|
38369
|
+
tag: { id: tag.id, value: cfg.status, name: tag.name, label: cfg.status },
|
|
38370
|
+
action: "set version workflow",
|
|
38371
|
+
signal: cfg.signal,
|
|
38372
|
+
sleep: cfg.sleep,
|
|
38373
|
+
settleMs: cfg.tagSettleDelayMs
|
|
38374
|
+
});
|
|
38375
|
+
await writer.write();
|
|
38376
|
+
const kept = await keepDateTagValue({
|
|
38377
|
+
writer,
|
|
38378
|
+
seen: await writer.readBack(),
|
|
38379
|
+
dateBefore: target.date_tag_value,
|
|
38380
|
+
versionId: cfg.versionId,
|
|
38381
|
+
docId: cfg.docId,
|
|
38382
|
+
what: "WORKFLOW tag",
|
|
38383
|
+
signal: cfg.signal,
|
|
38384
|
+
err
|
|
38385
|
+
});
|
|
38386
|
+
const { seen, dateAfter, datePreserved } = kept;
|
|
38387
|
+
const now = seen === null ? void 0 : tagValueOf(seen.tags, tag.id);
|
|
38388
|
+
const verified = seen !== null && now === cfg.status;
|
|
38389
|
+
if (seen === null) {
|
|
38390
|
+
err.write(
|
|
38391
|
+
`warning: could not read version ${cfg.versionId} back to verify its WORKFLOW tag \u2014 confirm with \`dr filebox versions ${cfg.docId}\`
|
|
38392
|
+
`
|
|
38393
|
+
);
|
|
38394
|
+
} else if (!verified) {
|
|
38395
|
+
err.write(
|
|
38396
|
+
`warning: version ${cfg.versionId} reads WORKFLOW ${JSON.stringify(now ?? null)} after the write, not "${cfg.status}" \u2014 confirm with \`dr filebox versions ${cfg.docId}\`
|
|
38397
|
+
`
|
|
38398
|
+
);
|
|
38399
|
+
}
|
|
38400
|
+
if (cfg.asJson) {
|
|
38401
|
+
writeJSONIndented(cfg.out, {
|
|
38402
|
+
version_id: cfg.versionId,
|
|
38403
|
+
doc_id: cfg.docId,
|
|
38404
|
+
tag_id: tag.id,
|
|
38405
|
+
previous,
|
|
38406
|
+
status: cfg.status,
|
|
38407
|
+
verified,
|
|
38408
|
+
reapplied: kept.reapplied,
|
|
38409
|
+
date_tag_value: dateAfter,
|
|
38410
|
+
date_tag_value_preserved: datePreserved
|
|
38411
|
+
});
|
|
37615
38412
|
return;
|
|
37616
38413
|
}
|
|
37617
|
-
const
|
|
37618
|
-
|
|
38414
|
+
const verdict = !verified ? "unverified" : kept.reapplied ? "verified after one re-apply" : "verified";
|
|
38415
|
+
cfg.out.write(
|
|
38416
|
+
`Set WORKFLOW tag on version ${cfg.versionId}: ${JSON.stringify(previous ?? "(unset)")} -> "${cfg.status}" (${verdict}).
|
|
38417
|
+
`
|
|
38418
|
+
);
|
|
38419
|
+
if (datePreserved === false) {
|
|
38420
|
+
cfg.out.write(dateNotPreservedLine(dateAfter, target.date_tag_value));
|
|
38421
|
+
}
|
|
37619
38422
|
}
|
|
37620
38423
|
var FILE_STATUS_VALUES = ["Draft", "Final"];
|
|
37621
38424
|
function parseFileStatus(raw) {
|
|
@@ -37716,6 +38519,110 @@ function fullTagSetWith(stored, write) {
|
|
|
37716
38519
|
}
|
|
37717
38520
|
return [...kept, write].sort((a, b) => a.id - b.id);
|
|
37718
38521
|
}
|
|
38522
|
+
var FullTagSetWriter = class {
|
|
38523
|
+
constructor(cfg) {
|
|
38524
|
+
this.cfg = cfg;
|
|
38525
|
+
this.base = cfg.snapshot ?? [];
|
|
38526
|
+
}
|
|
38527
|
+
cfg;
|
|
38528
|
+
base;
|
|
38529
|
+
/** PUT the full set; a non-2xx answer throws `TagWriteRefusedError`. */
|
|
38530
|
+
async write() {
|
|
38531
|
+
const { cfg } = this;
|
|
38532
|
+
const res = await setVersionTagsAndLabel(
|
|
38533
|
+
cfg.client,
|
|
38534
|
+
cfg.versionId,
|
|
38535
|
+
{ tags_to_update: JSON.stringify(fullTagSetWith(this.base, cfg.tag)) },
|
|
38536
|
+
{ signal: cfg.signal }
|
|
38537
|
+
);
|
|
38538
|
+
if (res.status < 200 || res.status >= 300) {
|
|
38539
|
+
const body = (await res.text()).trim();
|
|
38540
|
+
throw new TagWriteRefusedError(
|
|
38541
|
+
`${cfg.action} refused (HTTP ${res.status}): ${body}`,
|
|
38542
|
+
`(HTTP ${res.status}): ${body}`
|
|
38543
|
+
);
|
|
38544
|
+
}
|
|
38545
|
+
}
|
|
38546
|
+
/** Wait the settle delay, then read the version back (`null` = unreadable). */
|
|
38547
|
+
async readBack() {
|
|
38548
|
+
const { cfg } = this;
|
|
38549
|
+
const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
|
|
38550
|
+
await sleep3(cfg.settleMs ?? CYCLE_TAG_SETTLE_DELAY_MS, cfg.signal);
|
|
38551
|
+
return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
|
|
38552
|
+
}
|
|
38553
|
+
/** Build the next write from this read-back instead of the earlier snapshot. */
|
|
38554
|
+
refreshBase(v) {
|
|
38555
|
+
if (v !== null && v.tags.length > 0) {
|
|
38556
|
+
this.base = v.tags;
|
|
38557
|
+
}
|
|
38558
|
+
}
|
|
38559
|
+
};
|
|
38560
|
+
async function keepDateTagValue(cfg) {
|
|
38561
|
+
const { dateBefore, err } = cfg;
|
|
38562
|
+
let seen = cfg.seen;
|
|
38563
|
+
const dateMoved = (v) => v !== null && typeof dateBefore === "number" && v.row.date_tag_value !== void 0 && v.row.date_tag_value !== dateBefore;
|
|
38564
|
+
let reapplied = false;
|
|
38565
|
+
let dateUnknown = false;
|
|
38566
|
+
if (dateMoved(seen)) {
|
|
38567
|
+
err.write(
|
|
38568
|
+
`warning: date_tag_value on version ${cfg.versionId} changed (${JSON.stringify(dateBefore)} -> ${JSON.stringify(seen.row.date_tag_value ?? null)}) when its ${cfg.what} was set; re-applying the full tag set once
|
|
38569
|
+
`
|
|
38570
|
+
);
|
|
38571
|
+
reapplied = true;
|
|
38572
|
+
cfg.writer.refreshBase(seen);
|
|
38573
|
+
let refused = false;
|
|
38574
|
+
try {
|
|
38575
|
+
await cfg.writer.write();
|
|
38576
|
+
} catch (e) {
|
|
38577
|
+
if (cfg.signal.aborted) {
|
|
38578
|
+
throw e;
|
|
38579
|
+
}
|
|
38580
|
+
if (e instanceof TagWriteRefusedError) {
|
|
38581
|
+
refused = true;
|
|
38582
|
+
err.write(`warning: re-applying the full tag set was refused ${e.reason}
|
|
38583
|
+
`);
|
|
38584
|
+
} else {
|
|
38585
|
+
err.write(
|
|
38586
|
+
`warning: re-applying the full tag set failed (${e instanceof Error ? e.message : String(e)}); its outcome is unknown, reading the version again
|
|
38587
|
+
`
|
|
38588
|
+
);
|
|
38589
|
+
}
|
|
38590
|
+
}
|
|
38591
|
+
const after = refused ? seen : await cfg.writer.readBack();
|
|
38592
|
+
if (after === null) {
|
|
38593
|
+
dateUnknown = true;
|
|
38594
|
+
err.write(
|
|
38595
|
+
`warning: could not read version ${cfg.versionId} back to confirm its date_tag_value after re-applying the full tag set \u2014 check it with \`dr filebox versions ${cfg.docId} --json\`
|
|
38596
|
+
`
|
|
38597
|
+
);
|
|
38598
|
+
} else {
|
|
38599
|
+
seen = after;
|
|
38600
|
+
}
|
|
38601
|
+
}
|
|
38602
|
+
const dateAfter = dateUnknown || seen === null || seen.row.date_tag_value === void 0 ? null : seen.row.date_tag_value;
|
|
38603
|
+
const datePreserved = dateUnknown || seen === null || dateBefore === void 0 || seen.row.date_tag_value === void 0 ? null : !dateMoved(seen);
|
|
38604
|
+
if (datePreserved === false) {
|
|
38605
|
+
err.write(
|
|
38606
|
+
`warning: date_tag_value on version ${cfg.versionId} is still ${JSON.stringify(dateAfter)} (was ${JSON.stringify(dateBefore)}) after one re-apply; its tags still carry the date. Restore it with \`dr filebox set-version-date-tag ${cfg.versionId} --doc ${cfg.docId} --<dimension> <date>\` (it sends the date tag alone; on a planning filebox use \`dr filebox set-version-cycle ${cfg.versionId} --doc ${cfg.docId} --date <date> --scenario <name>\` instead, which keeps the cycle), then check \`dr filebox versions ${cfg.docId} --json\`
|
|
38607
|
+
`
|
|
38608
|
+
);
|
|
38609
|
+
}
|
|
38610
|
+
return { seen, reapplied, dateAfter, datePreserved };
|
|
38611
|
+
}
|
|
38612
|
+
function dateNotPreservedLine(dateAfter, dateBefore) {
|
|
38613
|
+
return `date_tag_value NOT preserved: it reads ${JSON.stringify(dateAfter)}, was ${JSON.stringify(dateBefore)} (see the warning above).
|
|
38614
|
+
`;
|
|
38615
|
+
}
|
|
38616
|
+
async function loadTargetVersion(client, docId, versionId, signal) {
|
|
38617
|
+
const rows = await listVersions(client, docId, { includeAutoSaves: true }, { signal });
|
|
38618
|
+
const target = rows.find((r) => r.id === versionId);
|
|
38619
|
+
if (target === void 0) {
|
|
38620
|
+
throw new Error(
|
|
38621
|
+
`version ${versionId} is not a version of filebox ${docId} \u2014 list them with \`dr filebox versions ${docId} --all\``
|
|
38622
|
+
);
|
|
38623
|
+
}
|
|
38624
|
+
return { rows, target };
|
|
38625
|
+
}
|
|
37719
38626
|
async function runFileboxSetVersionFileStatus(cfg) {
|
|
37720
38627
|
if (cfg.docId <= 0) {
|
|
37721
38628
|
throw new Error("document id must be positive");
|
|
@@ -37724,8 +38631,6 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
37724
38631
|
throw new Error("version id must be positive");
|
|
37725
38632
|
}
|
|
37726
38633
|
const err = cfg.err ?? process.stderr;
|
|
37727
|
-
const sleep3 = cfg.sleep ?? defaultCycleTagSleep;
|
|
37728
|
-
const settleMs = cfg.tagSettleDelayMs ?? CYCLE_TAG_SETTLE_DELAY_MS;
|
|
37729
38634
|
const details = await getDetails(cfg.client, cfg.docId, { signal: cfg.signal });
|
|
37730
38635
|
const tag = (details.tags_config ?? []).find(isFileStatusTag);
|
|
37731
38636
|
if (tag === void 0) {
|
|
@@ -37739,18 +38644,12 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
37739
38644
|
`File Status on document ${cfg.docId} does not allow "${cfg.status}" (allowed: ${tag.values.map((v) => JSON.stringify(v)).join(" | ")})`
|
|
37740
38645
|
);
|
|
37741
38646
|
}
|
|
37742
|
-
const rows = await
|
|
38647
|
+
const { rows, target } = await loadTargetVersion(
|
|
37743
38648
|
cfg.client,
|
|
37744
38649
|
cfg.docId,
|
|
37745
|
-
|
|
37746
|
-
|
|
38650
|
+
cfg.versionId,
|
|
38651
|
+
cfg.signal
|
|
37747
38652
|
);
|
|
37748
|
-
const target = rows.find((r) => r.id === cfg.versionId);
|
|
37749
|
-
if (target === void 0) {
|
|
37750
|
-
throw new Error(
|
|
37751
|
-
`version ${cfg.versionId} is not a version of filebox ${cfg.docId} \u2014 list them with \`dr filebox versions ${cfg.docId} --all\``
|
|
37752
|
-
);
|
|
37753
|
-
}
|
|
37754
38653
|
const current = tagValueOf(target.tags, tag.id);
|
|
37755
38654
|
const previous = typeof current === "string" ? current : null;
|
|
37756
38655
|
const options = tag.options ?? {};
|
|
@@ -37803,45 +38702,25 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
37803
38702
|
);
|
|
37804
38703
|
}
|
|
37805
38704
|
}
|
|
37806
|
-
const
|
|
37807
|
-
|
|
37808
|
-
|
|
37809
|
-
|
|
37810
|
-
|
|
37811
|
-
|
|
37812
|
-
|
|
37813
|
-
|
|
37814
|
-
|
|
37815
|
-
|
|
37816
|
-
|
|
37817
|
-
|
|
37818
|
-
const write = async () => {
|
|
37819
|
-
const res = await setVersionTagsAndLabel(
|
|
37820
|
-
cfg.client,
|
|
37821
|
-
cfg.versionId,
|
|
37822
|
-
{ tags_to_update: JSON.stringify(fullTagSetWith(base, statusWrite)) },
|
|
37823
|
-
{ signal: cfg.signal }
|
|
37824
|
-
);
|
|
37825
|
-
if (res.status < 200 || res.status >= 300) {
|
|
37826
|
-
const body = await res.text();
|
|
37827
|
-
throw new TagWriteRefusedError(
|
|
37828
|
-
`set version file status refused (HTTP ${res.status}): ${body.trim()}`,
|
|
37829
|
-
`(HTTP ${res.status}): ${body.trim()}`
|
|
37830
|
-
);
|
|
37831
|
-
}
|
|
37832
|
-
};
|
|
37833
|
-
const readBack = async () => {
|
|
37834
|
-
await sleep3(settleMs, cfg.signal);
|
|
37835
|
-
return readVersionForConfirm(cfg.client, cfg.versionId, cfg.docId, cfg.signal);
|
|
37836
|
-
};
|
|
37837
|
-
await write();
|
|
38705
|
+
const writer = new FullTagSetWriter({
|
|
38706
|
+
client: cfg.client,
|
|
38707
|
+
docId: cfg.docId,
|
|
38708
|
+
versionId: cfg.versionId,
|
|
38709
|
+
snapshot: target.tags,
|
|
38710
|
+
tag: { id: tag.id, value: cfg.status, name: tag.name, label: cfg.status },
|
|
38711
|
+
action: "set version file status",
|
|
38712
|
+
signal: cfg.signal,
|
|
38713
|
+
sleep: cfg.sleep,
|
|
38714
|
+
settleMs: cfg.tagSettleDelayMs
|
|
38715
|
+
});
|
|
38716
|
+
await writer.write();
|
|
37838
38717
|
let reapplied = false;
|
|
37839
|
-
let seen = await readBack();
|
|
38718
|
+
let seen = await writer.readBack();
|
|
37840
38719
|
if (seen !== null && tagValueOf(seen.tags, tag.id) !== cfg.status) {
|
|
37841
38720
|
reapplied = true;
|
|
37842
|
-
refreshBase(seen);
|
|
37843
|
-
await write();
|
|
37844
|
-
seen = await readBack();
|
|
38721
|
+
writer.refreshBase(seen);
|
|
38722
|
+
await writer.write();
|
|
38723
|
+
seen = await writer.readBack();
|
|
37845
38724
|
const now = seen === null ? void 0 : tagValueOf(seen.tags, tag.id);
|
|
37846
38725
|
if (seen !== null && now !== cfg.status) {
|
|
37847
38726
|
throw new Error(
|
|
@@ -37849,53 +38728,19 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
37849
38728
|
);
|
|
37850
38729
|
}
|
|
37851
38730
|
}
|
|
37852
|
-
const
|
|
37853
|
-
|
|
37854
|
-
|
|
37855
|
-
|
|
37856
|
-
|
|
37857
|
-
|
|
37858
|
-
|
|
37859
|
-
|
|
37860
|
-
|
|
37861
|
-
|
|
37862
|
-
|
|
37863
|
-
|
|
37864
|
-
|
|
37865
|
-
} catch (e) {
|
|
37866
|
-
if (cfg.signal.aborted) {
|
|
37867
|
-
throw e;
|
|
37868
|
-
}
|
|
37869
|
-
if (e instanceof TagWriteRefusedError) {
|
|
37870
|
-
refused = true;
|
|
37871
|
-
err.write(`warning: re-applying the full tag set was refused ${e.reason}
|
|
37872
|
-
`);
|
|
37873
|
-
} else {
|
|
37874
|
-
err.write(
|
|
37875
|
-
`warning: re-applying the full tag set failed (${e instanceof Error ? e.message : String(e)}); its outcome is unknown, reading the version again
|
|
37876
|
-
`
|
|
37877
|
-
);
|
|
37878
|
-
}
|
|
37879
|
-
}
|
|
37880
|
-
const after = refused ? seen : await readBack();
|
|
37881
|
-
if (after === null) {
|
|
37882
|
-
dateUnknown = true;
|
|
37883
|
-
err.write(
|
|
37884
|
-
`warning: could not read version ${cfg.versionId} back to confirm its date_tag_value after re-applying the full tag set \u2014 check it with \`dr filebox versions ${cfg.docId} --json\`
|
|
37885
|
-
`
|
|
37886
|
-
);
|
|
37887
|
-
} else {
|
|
37888
|
-
seen = after;
|
|
37889
|
-
}
|
|
37890
|
-
}
|
|
37891
|
-
const dateAfter = dateUnknown || seen === null || seen.row.date_tag_value === void 0 ? null : seen.row.date_tag_value;
|
|
37892
|
-
const datePreserved = dateUnknown || seen === null || dateBefore === void 0 || seen.row.date_tag_value === void 0 ? null : !dateMoved(seen);
|
|
37893
|
-
if (datePreserved === false) {
|
|
37894
|
-
err.write(
|
|
37895
|
-
`warning: date_tag_value on version ${cfg.versionId} is still ${JSON.stringify(dateAfter)} (was ${JSON.stringify(dateBefore)}) after one re-apply; its tags still carry the date. Restore it with \`dr filebox set-version-date-tag ${cfg.versionId} --doc ${cfg.docId} --<dimension> <date>\` (it sends the date tag alone; on a planning filebox use \`dr filebox set-version-cycle ${cfg.versionId} --doc ${cfg.docId} --date <date> --scenario <name>\` instead, which keeps the cycle), then check \`dr filebox versions ${cfg.docId} --json\`
|
|
37896
|
-
`
|
|
37897
|
-
);
|
|
37898
|
-
}
|
|
38731
|
+
const kept = await keepDateTagValue({
|
|
38732
|
+
writer,
|
|
38733
|
+
seen,
|
|
38734
|
+
dateBefore: target.date_tag_value,
|
|
38735
|
+
versionId: cfg.versionId,
|
|
38736
|
+
docId: cfg.docId,
|
|
38737
|
+
what: "File Status",
|
|
38738
|
+
signal: cfg.signal,
|
|
38739
|
+
err
|
|
38740
|
+
});
|
|
38741
|
+
seen = kept.seen;
|
|
38742
|
+
reapplied ||= kept.reapplied;
|
|
38743
|
+
const { dateAfter, datePreserved } = kept;
|
|
37899
38744
|
if (seen === null) {
|
|
37900
38745
|
err.write(
|
|
37901
38746
|
`warning: could not read version ${cfg.versionId} back to verify its File Status \u2014 confirm with \`dr filebox versions ${cfg.docId}\`
|
|
@@ -37934,10 +38779,7 @@ async function runFileboxSetVersionFileStatus(cfg) {
|
|
|
37934
38779
|
`
|
|
37935
38780
|
);
|
|
37936
38781
|
if (datePreserved === false) {
|
|
37937
|
-
cfg.out.write(
|
|
37938
|
-
`date_tag_value NOT preserved: it reads ${JSON.stringify(dateAfter)}, was ${JSON.stringify(dateBefore)} (see the warning above).
|
|
37939
|
-
`
|
|
37940
|
-
);
|
|
38782
|
+
cfg.out.write(dateNotPreservedLine(dateAfter, target.date_tag_value));
|
|
37941
38783
|
}
|
|
37942
38784
|
if (demotedIds.length > 0) {
|
|
37943
38785
|
cfg.out.write(
|
|
@@ -38515,6 +39357,7 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38515
39357
|
const dateTargets = [];
|
|
38516
39358
|
const periodLabels = [];
|
|
38517
39359
|
const waitPeriods = [];
|
|
39360
|
+
const pastOrFuturePeriods = /* @__PURE__ */ new Set();
|
|
38518
39361
|
if (parsedDates.length > 0) {
|
|
38519
39362
|
const details = await getDetails(cfg.client, cfg.id, {
|
|
38520
39363
|
signal: cfg.signal
|
|
@@ -38535,6 +39378,11 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38535
39378
|
);
|
|
38536
39379
|
}
|
|
38537
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));
|
|
38538
39386
|
for (const parsed2 of parsedDates) {
|
|
38539
39387
|
const resolved = dateTags.map((tag) => {
|
|
38540
39388
|
const { value, label: label2 } = resolveDateForSubType(tagSubType(tag), parsed2);
|
|
@@ -38543,6 +39391,8 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38543
39391
|
dateTargets.push(
|
|
38544
39392
|
JSON.stringify(resolved.map(({ id, name, value }) => ({ id, name, value })))
|
|
38545
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("|"));
|
|
38546
39396
|
const label = resolved.map((r) => r.label).join(" + ");
|
|
38547
39397
|
periodLabels.push(label);
|
|
38548
39398
|
waitPeriods.push({
|
|
@@ -38557,7 +39407,31 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38557
39407
|
if (waitPeriods.length === 0) {
|
|
38558
39408
|
waitPeriods.push({ label: "latest sync", tags: [] });
|
|
38559
39409
|
}
|
|
38560
|
-
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
|
+
);
|
|
38561
39435
|
const multipleTags = [...rawTags, ...dateTargets];
|
|
38562
39436
|
const body = {};
|
|
38563
39437
|
if (multipleTags.length > 0) {
|
|
@@ -38600,6 +39474,7 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
38600
39474
|
out: cfg.out,
|
|
38601
39475
|
errOut: cfg.wait.err ?? process.stderr,
|
|
38602
39476
|
signal: cfg.signal,
|
|
39477
|
+
warnings,
|
|
38603
39478
|
pollIntervalMs: cfg.wait.pollIntervalMs,
|
|
38604
39479
|
maxPollIntervalMs: cfg.wait.maxPollIntervalMs,
|
|
38605
39480
|
sleep: cfg.wait.sleep,
|
|
@@ -38707,9 +39582,9 @@ async function runFileboxDownload(cfg) {
|
|
|
38707
39582
|
const fh = await fsPromises.open(target, "w");
|
|
38708
39583
|
let total = 0;
|
|
38709
39584
|
try {
|
|
38710
|
-
for (const
|
|
38711
|
-
await fh.write(
|
|
38712
|
-
total +=
|
|
39585
|
+
for (const chunk2 of head) {
|
|
39586
|
+
await fh.write(chunk2);
|
|
39587
|
+
total += chunk2.length;
|
|
38713
39588
|
}
|
|
38714
39589
|
if (!streamDone) {
|
|
38715
39590
|
while (true) {
|
|
@@ -39635,33 +40510,41 @@ function registerFileboxCommand(root) {
|
|
|
39635
40510
|
).requiredOption(
|
|
39636
40511
|
"--status <s>",
|
|
39637
40512
|
"New WORKFLOW value: Not Started | In Progress | Pending | Approved | Rejected. Setting Approved requires the `approve_workflow` permission server-side."
|
|
39638
|
-
).
|
|
39639
|
-
|
|
39640
|
-
|
|
39641
|
-
|
|
39642
|
-
|
|
39643
|
-
|
|
39644
|
-
|
|
39645
|
-
|
|
39646
|
-
|
|
39647
|
-
|
|
39648
|
-
);
|
|
39649
|
-
|
|
39650
|
-
|
|
39651
|
-
|
|
39652
|
-
|
|
39653
|
-
|
|
39654
|
-
|
|
39655
|
-
|
|
39656
|
-
|
|
39657
|
-
|
|
39658
|
-
|
|
39659
|
-
|
|
39660
|
-
|
|
39661
|
-
|
|
39662
|
-
|
|
40513
|
+
).option(
|
|
40514
|
+
"--json",
|
|
40515
|
+
"Output a JSON record describing the write instead of the human confirmation",
|
|
40516
|
+
false
|
|
40517
|
+
).action(
|
|
40518
|
+
async (idStr, opts) => {
|
|
40519
|
+
const versionId = parseFileboxIdArg(idStr);
|
|
40520
|
+
if (opts.doc === void 0 || opts.doc === "") {
|
|
40521
|
+
throw new Error("--doc is required");
|
|
40522
|
+
}
|
|
40523
|
+
const docId = parseFileboxIdArg(opts.doc);
|
|
40524
|
+
const raw = opts.status ?? "";
|
|
40525
|
+
if (!WORKFLOW_STATUSES.includes(raw)) {
|
|
40526
|
+
throw new Error(
|
|
40527
|
+
`invalid --status "${raw}" (expected one of: ${WORKFLOW_STATUSES.map((s) => `"${s}"`).join(" | ")})`
|
|
40528
|
+
);
|
|
40529
|
+
}
|
|
40530
|
+
const status2 = raw;
|
|
40531
|
+
const setup = await setupCall(opts.server, opts.account, { timeoutMs: 5 * 6e4 });
|
|
40532
|
+
try {
|
|
40533
|
+
await runFileboxSetVersionWorkflow({
|
|
40534
|
+
client: setup.client,
|
|
40535
|
+
docId,
|
|
40536
|
+
versionId,
|
|
40537
|
+
status: status2,
|
|
40538
|
+
asJson: opts.json === true,
|
|
40539
|
+
signal: setup.signal,
|
|
40540
|
+
out: process.stdout,
|
|
40541
|
+
err: process.stderr
|
|
40542
|
+
});
|
|
40543
|
+
} finally {
|
|
40544
|
+
setup.cleanup();
|
|
40545
|
+
}
|
|
39663
40546
|
}
|
|
39664
|
-
|
|
40547
|
+
);
|
|
39665
40548
|
addServerFlag(setVersionWorkflowCmd);
|
|
39666
40549
|
addAccountFlag(setVersionWorkflowCmd);
|
|
39667
40550
|
const setVersionFileStatusCmd = fb.command("set-version-file-status").description(
|
|
@@ -39885,7 +40768,7 @@ function registerFileboxCommand(root) {
|
|
|
39885
40768
|
addServerFlag(dqClearCmd);
|
|
39886
40769
|
addAccountFlag(dqClearCmd);
|
|
39887
40770
|
const dqSyncCmd = dataQuery.command("sync").description(
|
|
39888
|
-
`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.`
|
|
39889
40772
|
).argument("<filebox_id>", "filebox id (positive integer)").option(
|
|
39890
40773
|
"--date <YYYY-MM-DD>",
|
|
39891
40774
|
"sync whatever date-tag period this date falls in, e.g. 2026-07-01. Repeatable.",
|
|
@@ -39932,6 +40815,7 @@ function registerFileboxCommand(root) {
|
|
|
39932
40815
|
addServerFlag(dqSyncCmd);
|
|
39933
40816
|
addAccountFlag(dqSyncCmd);
|
|
39934
40817
|
registerFileboxAdminCommands(fb, setupCall, addServerFlag, addAccountFlag);
|
|
40818
|
+
registerFileboxKindCommands(fb);
|
|
39935
40819
|
registerFileboxRescanVersionCommand(fb);
|
|
39936
40820
|
}
|
|
39937
40821
|
|
|
@@ -45587,52 +46471,6 @@ async function getAuditExportStatus(client, params, options) {
|
|
|
45587
46471
|
return out ?? { job_id: params.jobId, status: "PENDING" };
|
|
45588
46472
|
}
|
|
45589
46473
|
|
|
45590
|
-
// src/commands/_reconciliationDate.ts
|
|
45591
|
-
function validateDateString(orig, flagName) {
|
|
45592
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(orig)) {
|
|
45593
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
|
|
45594
|
-
}
|
|
45595
|
-
const parts = orig.split("-").map(Number);
|
|
45596
|
-
const [year, month1, day] = parts;
|
|
45597
|
-
const probe = new Date(Date.UTC(year, month1 - 1, day));
|
|
45598
|
-
if (probe.getUTCFullYear() !== year || probe.getUTCMonth() + 1 !== month1 || probe.getUTCDate() !== day) {
|
|
45599
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD)`);
|
|
45600
|
-
}
|
|
45601
|
-
return { year, month1, day };
|
|
45602
|
-
}
|
|
45603
|
-
function parseDateFlag(orig, flagName) {
|
|
45604
|
-
let normalized = orig;
|
|
45605
|
-
if (/^\d{4}-\d{2}$/.test(orig)) {
|
|
45606
|
-
normalized = `${orig}-01`;
|
|
45607
|
-
}
|
|
45608
|
-
let year;
|
|
45609
|
-
let month1;
|
|
45610
|
-
try {
|
|
45611
|
-
({ year, month1 } = validateDateString(normalized, flagName));
|
|
45612
|
-
} catch {
|
|
45613
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
|
|
45614
|
-
}
|
|
45615
|
-
return Math.floor(Date.UTC(year, month1, 0) / 1e3);
|
|
45616
|
-
}
|
|
45617
|
-
function parseDateFlagEpoch(orig, flagName) {
|
|
45618
|
-
let normalized = orig;
|
|
45619
|
-
if (/^\d{4}-\d{2}$/.test(orig)) {
|
|
45620
|
-
normalized = `${orig}-01`;
|
|
45621
|
-
}
|
|
45622
|
-
let year;
|
|
45623
|
-
let month1;
|
|
45624
|
-
try {
|
|
45625
|
-
({ year, month1 } = validateDateString(normalized, flagName));
|
|
45626
|
-
} catch {
|
|
45627
|
-
throw new Error(`invalid ${flagName} "${orig}" (expected YYYY-MM-DD or YYYY-MM)`);
|
|
45628
|
-
}
|
|
45629
|
-
return Math.floor(Date.UTC(year, month1 - 1, 1, 12) / 1e3);
|
|
45630
|
-
}
|
|
45631
|
-
function parseDateFlagDay(orig, flagName) {
|
|
45632
|
-
const { year, month1, day } = validateDateString(orig, flagName);
|
|
45633
|
-
return Math.floor(Date.UTC(year, month1 - 1, day) / 1e3);
|
|
45634
|
-
}
|
|
45635
|
-
|
|
45636
46474
|
// src/commands/_reconciliationSettingsFile.ts
|
|
45637
46475
|
var ALLOWED_FREQUENCIES = /* @__PURE__ */ new Set(["Monthly", "Quarterly", "Yearly", "Exclude"]);
|
|
45638
46476
|
var REQUIRED_FIELDS = [
|
|
@@ -47029,17 +47867,17 @@ async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
|
47029
47867
|
execute: async () => {
|
|
47030
47868
|
extendForBatch(cfg.extendTimeout, chunks.length);
|
|
47031
47869
|
let applied = 0;
|
|
47032
|
-
for (const
|
|
47870
|
+
for (const chunk2 of chunks) {
|
|
47033
47871
|
try {
|
|
47034
|
-
await updateAccountSettings(cfg.client,
|
|
47872
|
+
await updateAccountSettings(cfg.client, chunk2, { signal: cfg.signal });
|
|
47035
47873
|
} catch (err) {
|
|
47036
47874
|
const msg = err instanceof Error ? err.message : String(err);
|
|
47037
47875
|
throw new Error(
|
|
47038
|
-
`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}
|
|
47039
47877
|
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
47040
47878
|
);
|
|
47041
47879
|
}
|
|
47042
|
-
applied +=
|
|
47880
|
+
applied += chunk2.length;
|
|
47043
47881
|
}
|
|
47044
47882
|
cfg.out.write(
|
|
47045
47883
|
`Updated ${applied} account setting${plural(applied)} (${unchanged} unchanged).
|
|
@@ -47632,10 +48470,10 @@ async function writeMemberChunks(ctx, planned, items, chunkSize) {
|
|
|
47632
48470
|
extendForBatch(ctx.extendTimeout, requests * 2);
|
|
47633
48471
|
let applied = 0;
|
|
47634
48472
|
for (let i = 0; i < items.length; i += chunkSize) {
|
|
47635
|
-
const
|
|
48473
|
+
const chunk2 = items.slice(i, i + chunkSize);
|
|
47636
48474
|
let drift;
|
|
47637
48475
|
try {
|
|
47638
|
-
drift = await chunkDrift(ctx, planned,
|
|
48476
|
+
drift = await chunkDrift(ctx, planned, chunk2);
|
|
47639
48477
|
} catch (err) {
|
|
47640
48478
|
const msg = err instanceof Error ? err.message : String(err);
|
|
47641
48479
|
throw new Error(
|
|
@@ -47651,23 +48489,23 @@ The tenant changed since the plan. Re-run the same command without --confirm-tok
|
|
|
47651
48489
|
);
|
|
47652
48490
|
}
|
|
47653
48491
|
try {
|
|
47654
|
-
await updateAccountMembers(ctx.client,
|
|
48492
|
+
await updateAccountMembers(ctx.client, chunk2, { signal: ctx.signal });
|
|
47655
48493
|
} catch (err) {
|
|
47656
48494
|
const msg = err instanceof Error ? err.message : String(err);
|
|
47657
48495
|
throw new Error(
|
|
47658
|
-
`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}
|
|
47659
48497
|
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
47660
48498
|
);
|
|
47661
48499
|
}
|
|
47662
|
-
applied +=
|
|
48500
|
+
applied += chunk2.length;
|
|
47663
48501
|
}
|
|
47664
48502
|
return applied;
|
|
47665
48503
|
}
|
|
47666
|
-
async function chunkDrift(cfg, planned,
|
|
48504
|
+
async function chunkDrift(cfg, planned, chunk2) {
|
|
47667
48505
|
const now = await getAccounts(cfg.client, { date: cfg.date }, { signal: cfg.signal });
|
|
47668
48506
|
const nowById = new Map(now.map((a) => [a.account_id, a]));
|
|
47669
48507
|
const drift = [];
|
|
47670
|
-
for (const { account_id: id } of
|
|
48508
|
+
for (const { account_id: id } of chunk2) {
|
|
47671
48509
|
const before = planned.get(id);
|
|
47672
48510
|
const after = nowById.get(id);
|
|
47673
48511
|
const label = `${before.account_name} (${id})`;
|
|
@@ -58649,8 +59487,8 @@ function captureVersion() {
|
|
|
58649
59487
|
return new Promise((resolve3) => {
|
|
58650
59488
|
const child2 = spawn2("dr", ["--version"], { shell: true });
|
|
58651
59489
|
let out = "";
|
|
58652
|
-
child2.stdout.on("data", (
|
|
58653
|
-
out +=
|
|
59490
|
+
child2.stdout.on("data", (chunk2) => {
|
|
59491
|
+
out += chunk2.toString();
|
|
58654
59492
|
});
|
|
58655
59493
|
child2.on("close", (code) => {
|
|
58656
59494
|
if (code !== 0) {
|
|
@@ -62882,7 +63720,7 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
|
62882
63720
|
if (outstanding.length === 0) break;
|
|
62883
63721
|
if (now() > deadline || inner.signal.aborted) break;
|
|
62884
63722
|
for (let i = 0; i < outstanding.length; i += MAX_BULK_STATUS_IDS) {
|
|
62885
|
-
const
|
|
63723
|
+
const chunk2 = outstanding.slice(i, i + MAX_BULK_STATUS_IDS);
|
|
62886
63724
|
const tickAc = new AbortController();
|
|
62887
63725
|
const onInnerAbort = () => tickAc.abort(inner.signal.reason);
|
|
62888
63726
|
if (inner.signal.aborted) {
|
|
@@ -62891,13 +63729,13 @@ async function pollExtractionStatus(client, contractFileIds, options = {}) {
|
|
|
62891
63729
|
inner.signal.addEventListener("abort", onInnerAbort, { once: true });
|
|
62892
63730
|
}
|
|
62893
63731
|
try {
|
|
62894
|
-
const resp = await getBulkProcessStatus(client,
|
|
63732
|
+
const resp = await getBulkProcessStatus(client, chunk2, {
|
|
62895
63733
|
signal: tickAc.signal
|
|
62896
63734
|
});
|
|
62897
|
-
for (let j = 0; j <
|
|
63735
|
+
for (let j = 0; j < chunk2.length; j++) {
|
|
62898
63736
|
const entry = resp.results[j];
|
|
62899
63737
|
if (entry !== void 0 && isTerminalAiStatus(entry.ai_processing_status)) {
|
|
62900
|
-
resolved.set(
|
|
63738
|
+
resolved.set(chunk2[j], entry);
|
|
62901
63739
|
}
|
|
62902
63740
|
}
|
|
62903
63741
|
} catch (err) {
|
|
@@ -64658,10 +65496,10 @@ async function extractSync(cfg, parts) {
|
|
|
64658
65496
|
const chunked = chunkContracts([...parts], MAX_FILES_PER_BATCH);
|
|
64659
65497
|
let abortedMidRun = false;
|
|
64660
65498
|
for (let ci = 0; ci < chunked.length; ci++) {
|
|
64661
|
-
const
|
|
65499
|
+
const chunk2 = chunked[ci];
|
|
64662
65500
|
try {
|
|
64663
65501
|
const results = await retryAsync(
|
|
64664
|
-
() => uploadFilesWithAi(cfg.client,
|
|
65502
|
+
() => uploadFilesWithAi(cfg.client, chunk2, {
|
|
64665
65503
|
isAdditional: cfg.isAdditional,
|
|
64666
65504
|
signal: cfg.signal
|
|
64667
65505
|
}),
|
|
@@ -64679,7 +65517,7 @@ async function extractSync(cfg, parts) {
|
|
|
64679
65517
|
}
|
|
64680
65518
|
);
|
|
64681
65519
|
allResults.push(...results);
|
|
64682
|
-
for (const part of
|
|
65520
|
+
for (const part of chunk2) {
|
|
64683
65521
|
sourceFiles.push(part.sourcePath);
|
|
64684
65522
|
outcomes.push({ path: part.sourcePath, ok: true, detail: "extracted" });
|
|
64685
65523
|
}
|
|
@@ -64687,7 +65525,7 @@ async function extractSync(cfg, parts) {
|
|
|
64687
65525
|
const msg = err instanceof Error ? err.message : String(err);
|
|
64688
65526
|
const kind = classifyFailure(err);
|
|
64689
65527
|
const detail = kind === "ambiguous" ? `${msg} \u2014 upstream may still be processing; check before re-sending` : msg;
|
|
64690
|
-
for (const part of
|
|
65528
|
+
for (const part of chunk2) {
|
|
64691
65529
|
outcomes.push({ path: part.sourcePath, ok: false, detail });
|
|
64692
65530
|
}
|
|
64693
65531
|
if (kind === "abort") {
|
|
@@ -67694,8 +68532,8 @@ Pipe it (printf '%s\\n' "$APP_PASSWORD" | dr desk smtp set \u2026) or use --pass
|
|
|
67694
68532
|
}
|
|
67695
68533
|
const chunks = [];
|
|
67696
68534
|
let total = 0;
|
|
67697
|
-
for await (const
|
|
67698
|
-
const buf = Buffer.isBuffer(
|
|
68535
|
+
for await (const chunk2 of process.stdin) {
|
|
68536
|
+
const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(String(chunk2));
|
|
67699
68537
|
total += buf.length;
|
|
67700
68538
|
if (total > maxBytes) {
|
|
67701
68539
|
throw new Error(
|