@datarails-ai/dr-cli 1.6.130 → 1.6.132
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 +1066 -97
- package/dist/scopes-map.json +57 -1
- package/dist/skills/dr-cli/SKILL.md +10 -6
- package/dist/skills/dr-cli/reference/filebox-documents.md +29 -8
- package/dist/skills/dr-cli/reference/integrations.md +36 -0
- package/dist/skills/dr-error-enrichment/reference/core.md +8 -0
- package/dist/skills/dr-error-enrichment-author/SKILL.md +3 -2
- package/dist/skills/dr-error-enrichment-reconcile/SKILL.md +4 -1
- package/package.json +2 -2
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.132",
|
|
12
12
|
description: "Datarails CLI",
|
|
13
13
|
type: "module",
|
|
14
14
|
engines: {
|
|
@@ -40,7 +40,7 @@ var package_default = {
|
|
|
40
40
|
postinstall: "node scripts/postinstall.mjs"
|
|
41
41
|
},
|
|
42
42
|
dependencies: {
|
|
43
|
-
"@datarails-ai/dr-appspace-cli": "0.12.
|
|
43
|
+
"@datarails-ai/dr-appspace-cli": "0.12.9",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
45
|
"@napi-rs/keyring": "^1.3.0",
|
|
46
46
|
commander: "^12.1.0",
|
|
@@ -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-14T19:31:40.601Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -2268,6 +2268,13 @@ var AuthorizedClient = class _AuthorizedClient {
|
|
|
2268
2268
|
async postJSON(path12, payload, options) {
|
|
2269
2269
|
return this.requestJson("POST", path12, payload, options);
|
|
2270
2270
|
}
|
|
2271
|
+
/**
|
|
2272
|
+
* {@link AuthorizedClient.postJSON}, also returning the final response's headers. Same
|
|
2273
|
+
* recovery ladder and error messages.
|
|
2274
|
+
*/
|
|
2275
|
+
async postJSONWithHeaders(path12, payload, options) {
|
|
2276
|
+
return this.requestJsonWithHeaders("POST", path12, payload, options);
|
|
2277
|
+
}
|
|
2271
2278
|
/**
|
|
2272
2279
|
* Issues a `PUT` with `payload` JSON-encoded as the body and decodes the
|
|
2273
2280
|
* JSON response into `T`. Mirrors `client.go:148` `PutJSON`.
|
|
@@ -2498,6 +2505,10 @@ var AuthorizedClient = class _AuthorizedClient {
|
|
|
2498
2505
|
* replayable); they go through `do()` directly. See Task 1.14.
|
|
2499
2506
|
*/
|
|
2500
2507
|
async requestJson(method, path12, payload, options) {
|
|
2508
|
+
return (await this.requestJsonWithHeaders(method, path12, payload, options)).data;
|
|
2509
|
+
}
|
|
2510
|
+
/** {@link AuthorizedClient.requestJson}, also returning the final response's headers. */
|
|
2511
|
+
async requestJsonWithHeaders(method, path12, payload, options) {
|
|
2501
2512
|
const body = payload === void 0 ? void 0 : JSON.stringify(payload);
|
|
2502
2513
|
const sentBearer = this.hasBearer();
|
|
2503
2514
|
let res = await this.doWithAuth(method, path12, body, options, true);
|
|
@@ -2545,9 +2556,9 @@ var AuthorizedClient = class _AuthorizedClient {
|
|
|
2545
2556
|
await this._store.touch(this._serverKey, this._email);
|
|
2546
2557
|
} catch {
|
|
2547
2558
|
}
|
|
2548
|
-
if (text2 === "" || text2 === '""') return null;
|
|
2559
|
+
if (text2 === "" || text2 === '""') return { data: null, headers: res.headers };
|
|
2549
2560
|
try {
|
|
2550
|
-
return JSON.parse(text2);
|
|
2561
|
+
return { data: JSON.parse(text2), headers: res.headers };
|
|
2551
2562
|
} catch (err) {
|
|
2552
2563
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2553
2564
|
throw new Error(`${method} ${path12}: decode: ${msg}`);
|
|
@@ -3699,6 +3710,9 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
|
|
|
3699
3710
|
// the pre-write `export` backup restores any rule the batch got wrong.
|
|
3700
3711
|
"error-enrichment-batch|create",
|
|
3701
3712
|
"error-enrichment-batch|update",
|
|
3713
|
+
// A new classification type is additive: it changes no existing rule and
|
|
3714
|
+
// only widens the names a rule may be filed under.
|
|
3715
|
+
"error-enrichment-type|create",
|
|
3702
3716
|
// cash (dr cash …). rules delete / delete-all / hidden-rules delete → Tier A.
|
|
3703
3717
|
"cash-rule|update",
|
|
3704
3718
|
"cash-rules|reorder",
|
|
@@ -3754,6 +3768,16 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
|
|
|
3754
3768
|
"filebox|set-sync-frequency",
|
|
3755
3769
|
"filebox|lock",
|
|
3756
3770
|
"filebox-date-tag|lock",
|
|
3771
|
+
// SuiteQL custom reports (dr filebox custom-reports create / apply). Both rewrite one
|
|
3772
|
+
// filebox's query, as the ungated `filebox data-query set` does, and are undone by
|
|
3773
|
+
// re-applying the previous query; create also adds a report row the UI can delete.
|
|
3774
|
+
"filebox-custom-report|create",
|
|
3775
|
+
"filebox-custom-report|apply",
|
|
3776
|
+
// NetSuite readiness stamp (dr integrations netsuite readiness). Writes one config key,
|
|
3777
|
+
// bundle_version 3, on one connection, only after a CONFIRMED probe; the server refuses a
|
|
3778
|
+
// connection that is not a 3.0 migration candidate (409), and the ungated
|
|
3779
|
+
// `integrations update` can rewrite the key.
|
|
3780
|
+
"netsuite-bundle-version|stamp",
|
|
3757
3781
|
// collection-processes (dr collection-processes …).
|
|
3758
3782
|
"collection-process|create",
|
|
3759
3783
|
"collection-process|set-tag-config",
|
|
@@ -3871,7 +3895,27 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
|
|
|
3871
3895
|
"org-export|delete",
|
|
3872
3896
|
// connectors poll item skip (dr connectors poll skip). The item is never landed,
|
|
3873
3897
|
// but `dr connectors poll retry` queues it again, so the token alone is enough.
|
|
3874
|
-
"connector-poll-item|skip"
|
|
3898
|
+
"connector-poll-item|skip",
|
|
3899
|
+
// connectors poll seeding (dr connectors poll seed, and a `poll set` whose answers seed
|
|
3900
|
+
// and land nothing: `--on-policy-change seed`, with or without `--widen seed`). DR-55448.
|
|
3901
|
+
// A seed marks items already in the source as seen, so a cut-over does not land them a
|
|
3902
|
+
// second time beside the versions the old feed (Workato) already landed. It lands
|
|
3903
|
+
// nothing, deletes nothing and touches no version in the filebox. It is a routine step of
|
|
3904
|
+
// every cut-over (bind, seed --until <cut-over>, enable, pause the old feed), run once per
|
|
3905
|
+
// filebox, often by an agent over hundreds of fileboxes, and the word dialog stalled every
|
|
3906
|
+
// one of them. The token still binds the filebox, server, account, control-plane URL and
|
|
3907
|
+
// the exact --until / settings, and the control-plane audits each call
|
|
3908
|
+
// (`poll.seed` / `poll.put`: user, org, query).
|
|
3909
|
+
// Judged, not free: a seeded item is not undone by `poll retry` (that covers failed,
|
|
3910
|
+
// waiting and skipped items), so a cutoff set too late leaves files unlanded until a
|
|
3911
|
+
// Tier-A land path (`set --on-policy-change land`, `resume --source land`) or an upload
|
|
3912
|
+
// brings them in. Accepted because those files stay in the source and nothing in the
|
|
3913
|
+
// filebox changes; the plan shows the filebox and the cutoff before the token is
|
|
3914
|
+
// redeemed, and a separate `seed --dry-run` counts the already-discovered items it would
|
|
3915
|
+
// mark (the plan does not). If a seed ever lands, deletes or rewrites a version,
|
|
3916
|
+
// these keys move back to Tier A.
|
|
3917
|
+
"connector-poll|seed",
|
|
3918
|
+
"connector-poll|set-seed"
|
|
3875
3919
|
]);
|
|
3876
3920
|
var PROD_GATE_TIER_A = /* @__PURE__ */ new Set([
|
|
3877
3921
|
// workflow / mec — soft-delete + cascade deletes. `archive` also covers the
|
|
@@ -4007,13 +4051,13 @@ var PROD_GATE_TIER_A = /* @__PURE__ */ new Set([
|
|
|
4007
4051
|
// deletes whole organizations via the org-delete chord. The single most
|
|
4008
4052
|
// destructive verb in the CLI; the word gate stays on prod unconditionally.
|
|
4009
4053
|
"org-import|delete-orgs",
|
|
4010
|
-
// connectors polled ingestion (dr connectors poll
|
|
4011
|
-
//
|
|
4012
|
-
//
|
|
4013
|
-
// `resume --source land` lands everything a changed source holds. Versions
|
|
4014
|
-
// a customer filebox
|
|
4015
|
-
|
|
4016
|
-
"connector-poll|set-land
|
|
4054
|
+
// connectors polled ingestion writes that LAND files (dr connectors poll set / resume
|
|
4055
|
+
// --source land). `set-land` is a `poll set` with any answer that lands: a wider window
|
|
4056
|
+
// with `--widen land`, or `--on-policy-change land` (a seed alongside a land is still
|
|
4057
|
+
// `set-land`). `resume --source land` lands everything a changed source holds. Versions
|
|
4058
|
+
// landed in a customer filebox cannot be taken back from the CLI. Seeding is Tier B
|
|
4059
|
+
// (`connector-poll|seed` / `|set-seed`, DR-55448).
|
|
4060
|
+
"connector-poll|set-land",
|
|
4017
4061
|
"connector-poll|resume-land"
|
|
4018
4062
|
]);
|
|
4019
4063
|
function classifyTier(action) {
|
|
@@ -5843,6 +5887,52 @@ function isBadGatewayError(err) {
|
|
|
5843
5887
|
if (/^\S+ \S+: HTTP 502(?::|\s|$)/.test(err.message)) return true;
|
|
5844
5888
|
return /\(HTTP 502\)$/.test(err.message);
|
|
5845
5889
|
}
|
|
5890
|
+
async function readRawJson(res) {
|
|
5891
|
+
const rawText = await res.text();
|
|
5892
|
+
let body = null;
|
|
5893
|
+
try {
|
|
5894
|
+
body = rawText ? JSON.parse(rawText) : null;
|
|
5895
|
+
} catch {
|
|
5896
|
+
body = null;
|
|
5897
|
+
}
|
|
5898
|
+
return { status: res.status, body, rawText };
|
|
5899
|
+
}
|
|
5900
|
+
var DETAIL_MAX_CHARS = 300;
|
|
5901
|
+
function isSuccess(status2) {
|
|
5902
|
+
return status2 >= 200 && status2 < 300;
|
|
5903
|
+
}
|
|
5904
|
+
function rawErrorDetail(res) {
|
|
5905
|
+
return truncate4(uncappedErrorDetail(res), DETAIL_MAX_CHARS);
|
|
5906
|
+
}
|
|
5907
|
+
function uncappedErrorDetail(res) {
|
|
5908
|
+
const { body } = res;
|
|
5909
|
+
if (typeof body === "string") return body;
|
|
5910
|
+
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
5911
|
+
const detail = body.detail;
|
|
5912
|
+
if (typeof detail === "string") return detail;
|
|
5913
|
+
const fields = [];
|
|
5914
|
+
for (const [field, value] of Object.entries(body)) {
|
|
5915
|
+
const messages = Array.isArray(value) ? value : [value];
|
|
5916
|
+
if (messages.length > 0 && messages.every((m) => typeof m === "string")) {
|
|
5917
|
+
fields.push(`${field}: ${messages.join(" ")}`);
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
if (fields.length > 0) return fields.join("; ");
|
|
5921
|
+
}
|
|
5922
|
+
return res.rawText.trim();
|
|
5923
|
+
}
|
|
5924
|
+
var FAILURE_PHRASES = {
|
|
5925
|
+
400: "rejected",
|
|
5926
|
+
403: "refused",
|
|
5927
|
+
404: "not found, or not visible to this account",
|
|
5928
|
+
409: "refused",
|
|
5929
|
+
502: "failed upstream"
|
|
5930
|
+
};
|
|
5931
|
+
function describeRawFailure(what, res, overrides = {}) {
|
|
5932
|
+
const detail = decodeShape(rawErrorDetail(res));
|
|
5933
|
+
const phrase = overrides[res.status] ?? FAILURE_PHRASES[res.status];
|
|
5934
|
+
return phrase === void 0 ? `${what}: unexpected status ${res.status}: ${detail}` : `${what} ${phrase} (${res.status}): ${detail}`;
|
|
5935
|
+
}
|
|
5846
5936
|
|
|
5847
5937
|
// src/api/users.ts
|
|
5848
5938
|
async function list2(client, options) {
|
|
@@ -19114,12 +19204,52 @@ async function clearDataQuery(client, id, reqOptions) {
|
|
|
19114
19204
|
return client.do("DELETE", `/api/documents/${id}/data_query`, void 0, reqOptions);
|
|
19115
19205
|
}
|
|
19116
19206
|
async function syncDataQuery(client, id, body, reqOptions) {
|
|
19117
|
-
|
|
19207
|
+
return (await syncDataQueryTimed(client, id, body, reqOptions)).body;
|
|
19208
|
+
}
|
|
19209
|
+
async function syncDataQueryTimed(client, id, body, reqOptions) {
|
|
19210
|
+
const { data: data4, headers } = await client.postJSONWithHeaders(
|
|
19118
19211
|
`/api/documents/${id}/data_query_sync`,
|
|
19119
19212
|
body,
|
|
19120
19213
|
reqOptions
|
|
19121
19214
|
);
|
|
19122
|
-
|
|
19215
|
+
const serverTimeMs = Date.parse(headers.get("date") ?? "");
|
|
19216
|
+
return {
|
|
19217
|
+
body: data4 !== null && typeof data4 === "object" && !Array.isArray(data4) ? data4 : {},
|
|
19218
|
+
serverTimeMs: Number.isFinite(serverTimeMs) ? serverTimeMs : void 0
|
|
19219
|
+
};
|
|
19220
|
+
}
|
|
19221
|
+
async function listDocumentEvents(client, id, action, page, reqOptions) {
|
|
19222
|
+
return client.getJSON(
|
|
19223
|
+
`/api/documents/${id}/logs?action=${encodeURIComponent(action)}&skip=${page.skip}&limit=${page.limit}`,
|
|
19224
|
+
reqOptions
|
|
19225
|
+
);
|
|
19226
|
+
}
|
|
19227
|
+
async function listCustomReports(client, id, reqOptions) {
|
|
19228
|
+
const res = await client.do(
|
|
19229
|
+
"GET",
|
|
19230
|
+
`/api/documents/${id}/data_query_custom_reports`,
|
|
19231
|
+
void 0,
|
|
19232
|
+
reqOptions
|
|
19233
|
+
);
|
|
19234
|
+
return readRawJson(res);
|
|
19235
|
+
}
|
|
19236
|
+
async function createCustomReport(client, id, body, reqOptions) {
|
|
19237
|
+
const res = await client.do(
|
|
19238
|
+
"POST",
|
|
19239
|
+
`/api/documents/${id}/data_query_custom_reports`,
|
|
19240
|
+
JSON.stringify(body),
|
|
19241
|
+
reqOptions
|
|
19242
|
+
);
|
|
19243
|
+
return readRawJson(res);
|
|
19244
|
+
}
|
|
19245
|
+
async function applyCustomReport(client, id, reportId, reqOptions) {
|
|
19246
|
+
const res = await client.do(
|
|
19247
|
+
"POST",
|
|
19248
|
+
`/api/documents/${id}/data_query_custom_reports/${reportId}/apply`,
|
|
19249
|
+
JSON.stringify({}),
|
|
19250
|
+
reqOptions
|
|
19251
|
+
);
|
|
19252
|
+
return readRawJson(res);
|
|
19123
19253
|
}
|
|
19124
19254
|
async function getAutoCalculateSettings(client, docId, reqOptions) {
|
|
19125
19255
|
const path12 = `/api/documents/${docId}/auto_calculate_settings`;
|
|
@@ -28878,6 +29008,7 @@ import * as path5 from "path";
|
|
|
28878
29008
|
|
|
28879
29009
|
// src/api/errorEnrichments.ts
|
|
28880
29010
|
var BASE = "/api/v3/data_integrations/error-enrichments/";
|
|
29011
|
+
var TYPES_BASE = "/api/v3/data_integrations/error-enrichment-types/";
|
|
28881
29012
|
function filtersQuery(filters) {
|
|
28882
29013
|
if (filters === void 0) return "";
|
|
28883
29014
|
const params = new URLSearchParams();
|
|
@@ -28964,9 +29095,31 @@ async function testRegex(client, regex, samples, options) {
|
|
|
28964
29095
|
}
|
|
28965
29096
|
return out;
|
|
28966
29097
|
}
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
29098
|
+
async function listTypes(client, options) {
|
|
29099
|
+
const res = await client.do("GET", TYPES_BASE, void 0, options);
|
|
29100
|
+
const text2 = await res.text();
|
|
29101
|
+
if (res.status === 404) return null;
|
|
29102
|
+
if (res.status !== 200) {
|
|
29103
|
+
throw new Error(`GET ${TYPES_BASE}: HTTP ${res.status}: ${text2.trim().slice(0, 200)}`);
|
|
29104
|
+
}
|
|
29105
|
+
let parsed2;
|
|
29106
|
+
try {
|
|
29107
|
+
parsed2 = JSON.parse(text2);
|
|
29108
|
+
} catch {
|
|
29109
|
+
parsed2 = void 0;
|
|
29110
|
+
}
|
|
29111
|
+
if (!Array.isArray(parsed2)) {
|
|
29112
|
+
throw new Error(`GET ${TYPES_BASE}: unexpected response: ${text2.trim().slice(0, 200)}`);
|
|
29113
|
+
}
|
|
29114
|
+
return parsed2;
|
|
29115
|
+
}
|
|
29116
|
+
async function createType(client, type, options) {
|
|
29117
|
+
const out = await client.postJSON(TYPES_BASE, type, options);
|
|
29118
|
+
if (out === null) {
|
|
29119
|
+
throw new Error("error-enrichment-types create: empty response");
|
|
29120
|
+
}
|
|
29121
|
+
return out;
|
|
29122
|
+
}
|
|
28970
29123
|
|
|
28971
29124
|
// src/commands/errorEnrichments.ts
|
|
28972
29125
|
function writeTable22(out, rows) {
|
|
@@ -29176,7 +29329,10 @@ async function runEnrichmentsList(cfg) {
|
|
|
29176
29329
|
const results = await sweep(
|
|
29177
29330
|
cfg.servers,
|
|
29178
29331
|
cfg.makeSetup,
|
|
29179
|
-
(client, signal) =>
|
|
29332
|
+
async (client, signal) => {
|
|
29333
|
+
await requireKnownClassifications(client, cfg.filters, signal);
|
|
29334
|
+
return list19(client, cfg.filters, { signal });
|
|
29335
|
+
},
|
|
29180
29336
|
cfg.errOut
|
|
29181
29337
|
);
|
|
29182
29338
|
const single = cfg.servers.length === 1;
|
|
@@ -29204,7 +29360,10 @@ async function runEnrichmentsStats(cfg) {
|
|
|
29204
29360
|
const results = await sweep(
|
|
29205
29361
|
cfg.servers,
|
|
29206
29362
|
cfg.makeSetup,
|
|
29207
|
-
(client, signal) =>
|
|
29363
|
+
async (client, signal) => {
|
|
29364
|
+
await requireKnownClassifications(client, cfg.filters, signal);
|
|
29365
|
+
return stats(client, cfg.filters, { signal });
|
|
29366
|
+
},
|
|
29208
29367
|
cfg.errOut
|
|
29209
29368
|
);
|
|
29210
29369
|
const single = cfg.servers.length === 1;
|
|
@@ -29232,6 +29391,47 @@ async function runEnrichmentsStats(cfg) {
|
|
|
29232
29391
|
}
|
|
29233
29392
|
requireSweepSuccess(results);
|
|
29234
29393
|
}
|
|
29394
|
+
function renderTypesTable(out, types) {
|
|
29395
|
+
if (types.length === 0) {
|
|
29396
|
+
out.write("(no types)\n");
|
|
29397
|
+
return;
|
|
29398
|
+
}
|
|
29399
|
+
const rows = [{ cells: ["NAME", "DESCRIPTION"] }];
|
|
29400
|
+
for (const t of types) rows.push({ cells: [t.name, oneLine2(t.description, 80)] });
|
|
29401
|
+
writeTable22(out, rows);
|
|
29402
|
+
}
|
|
29403
|
+
async function runEnrichmentTypesList(cfg) {
|
|
29404
|
+
const results = await sweep(
|
|
29405
|
+
cfg.servers,
|
|
29406
|
+
cfg.makeSetup,
|
|
29407
|
+
async (client, signal) => {
|
|
29408
|
+
const types = await listTypes(client, { signal });
|
|
29409
|
+
if (types === null) throw new Error("this server does not serve error-enrichment types yet");
|
|
29410
|
+
return types;
|
|
29411
|
+
},
|
|
29412
|
+
cfg.errOut
|
|
29413
|
+
);
|
|
29414
|
+
const single = cfg.servers.length === 1;
|
|
29415
|
+
const only = results[0];
|
|
29416
|
+
if (cfg.asJson) {
|
|
29417
|
+
if (single) {
|
|
29418
|
+
if (only?.status === "ok") writeJSONIndented(cfg.out, only.value ?? []);
|
|
29419
|
+
} else {
|
|
29420
|
+
writeJSONIndented(cfg.out, sweepJson(results, "types"));
|
|
29421
|
+
}
|
|
29422
|
+
} else {
|
|
29423
|
+
for (const r of results) {
|
|
29424
|
+
if (!single) {
|
|
29425
|
+
const suffix = r.status === "ok" ? `${(r.value ?? []).length} type${plural((r.value ?? []).length)}` : `${r.status}: ${r.message ?? ""}`;
|
|
29426
|
+
cfg.out.write(`== ${r.server} \u2014 ${suffix} ==
|
|
29427
|
+
`);
|
|
29428
|
+
}
|
|
29429
|
+
if (r.status === "ok") renderTypesTable(cfg.out, r.value ?? []);
|
|
29430
|
+
if (!single) cfg.out.write("\n");
|
|
29431
|
+
}
|
|
29432
|
+
}
|
|
29433
|
+
requireSweepSuccess(results);
|
|
29434
|
+
}
|
|
29235
29435
|
function oneLine2(s, max) {
|
|
29236
29436
|
return truncate3(s.replace(/\s+/g, " "), max);
|
|
29237
29437
|
}
|
|
@@ -29726,6 +29926,45 @@ async function runEnrichmentsImport(cfg) {
|
|
|
29726
29926
|
}
|
|
29727
29927
|
});
|
|
29728
29928
|
}
|
|
29929
|
+
function requireTypeName(name) {
|
|
29930
|
+
if (name.trim() === "") {
|
|
29931
|
+
throw new Error("--name must not be empty");
|
|
29932
|
+
}
|
|
29933
|
+
}
|
|
29934
|
+
async function runEnrichmentTypesCreate(cfg) {
|
|
29935
|
+
requireTypeName(cfg.name);
|
|
29936
|
+
const action = {
|
|
29937
|
+
resource: "error-enrichment-type",
|
|
29938
|
+
id: cfg.name,
|
|
29939
|
+
verb: "create"
|
|
29940
|
+
};
|
|
29941
|
+
await preflightConfirmToken(action, cfg.token);
|
|
29942
|
+
const description = cfg.description === void 0 ? "" : ` --description ${shellQuote(cfg.description)}`;
|
|
29943
|
+
await twoStepConfirm({
|
|
29944
|
+
out: cfg.out,
|
|
29945
|
+
action,
|
|
29946
|
+
token: cfg.token,
|
|
29947
|
+
server: cfg.serverFlag,
|
|
29948
|
+
confirmCmd: `dr error-enrichments types create --name ${shellQuote(cfg.name)}${description} ${cmdTail(cfg.serverFlag, cfg.accountFlag, void 0)}`,
|
|
29949
|
+
describe: async () => [
|
|
29950
|
+
`type: ${cfg.name}`,
|
|
29951
|
+
...cfg.description === void 0 ? [] : [`description: ${truncate3(cfg.description, 120)}`],
|
|
29952
|
+
`adds a classification type on ${cfg.serverFlag}; this CLI has no verb to rename or remove it`
|
|
29953
|
+
],
|
|
29954
|
+
execute: async () => {
|
|
29955
|
+
const created = await createType(
|
|
29956
|
+
cfg.client,
|
|
29957
|
+
{
|
|
29958
|
+
name: cfg.name,
|
|
29959
|
+
...cfg.description === void 0 ? {} : { description: cfg.description }
|
|
29960
|
+
},
|
|
29961
|
+
{ signal: cfg.signal }
|
|
29962
|
+
);
|
|
29963
|
+
cfg.out.write(`Created error-enrichment type ${created.name} on ${cfg.serverFlag}.
|
|
29964
|
+
`);
|
|
29965
|
+
}
|
|
29966
|
+
});
|
|
29967
|
+
}
|
|
29729
29968
|
function collectArray6(value, previous) {
|
|
29730
29969
|
return [...previous, value];
|
|
29731
29970
|
}
|
|
@@ -29908,18 +30147,20 @@ function resolveUpdateTargets(idArgs, entries, rule) {
|
|
|
29908
30147
|
}
|
|
29909
30148
|
return { patches: ids.map((id) => ({ id, patch: rule })), mode: "shared" };
|
|
29910
30149
|
}
|
|
29911
|
-
function requireKnownClassifications(
|
|
29912
|
-
const
|
|
29913
|
-
|
|
29914
|
-
);
|
|
30150
|
+
async function requireKnownClassifications(client, filters, signal) {
|
|
30151
|
+
const wanted = filters.classifications ?? [];
|
|
30152
|
+
if (wanted.length === 0) return;
|
|
30153
|
+
const types = await listTypes(client, { signal });
|
|
30154
|
+
if (types === null) return;
|
|
30155
|
+
const known = new Set(types.map((t) => t.name));
|
|
30156
|
+
const unknown = wanted.filter((v) => !known.has(v));
|
|
29915
30157
|
if (unknown.length > 0) {
|
|
29916
30158
|
throw new Error(
|
|
29917
|
-
`unknown --classification ${unknown.map((v) => JSON.stringify(v)).join(", ")}. Valid: ${
|
|
30159
|
+
`unknown --classification ${unknown.map((v) => JSON.stringify(v)).join(", ")}. Valid: ${types.map((t) => t.name).join(", ")} (see dr error-enrichments types list)`
|
|
29918
30160
|
);
|
|
29919
30161
|
}
|
|
29920
30162
|
}
|
|
29921
30163
|
function filtersFromOpts(opts) {
|
|
29922
|
-
requireKnownClassifications(opts.classification);
|
|
29923
30164
|
return {
|
|
29924
30165
|
integrationTypes: opts.integrationType,
|
|
29925
30166
|
classifications: opts.classification,
|
|
@@ -29938,7 +30179,10 @@ function addRuleFieldFlags(cmd, payloadHelp) {
|
|
|
29938
30179
|
'Raw error example (repeatable). To clear all examples, use --payload-file with {"examples": []}',
|
|
29939
30180
|
collectArray6,
|
|
29940
30181
|
[]
|
|
29941
|
-
).option(
|
|
30182
|
+
).option(
|
|
30183
|
+
"--classification <c>",
|
|
30184
|
+
"Classification type name (see `dr error-enrichments types list`)"
|
|
30185
|
+
).option("--approved <bool>", "true | false");
|
|
29942
30186
|
}
|
|
29943
30187
|
function addSweepFlags(cmd) {
|
|
29944
30188
|
return cmd.option(
|
|
@@ -29953,7 +30197,12 @@ function addFilterFlags(cmd) {
|
|
|
29953
30197
|
"Filter by integration type (repeatable)",
|
|
29954
30198
|
collectArray6,
|
|
29955
30199
|
[]
|
|
29956
|
-
).option(
|
|
30200
|
+
).option(
|
|
30201
|
+
"--classification <c>",
|
|
30202
|
+
"Filter by classification type name (repeatable; see `dr error-enrichments types list`)",
|
|
30203
|
+
collectArray6,
|
|
30204
|
+
[]
|
|
30205
|
+
).option("--approved <bool>", "Filter by approved state (true | false)").option("--search <q>", "Search pattern / description / type name");
|
|
29957
30206
|
}
|
|
29958
30207
|
function defaultSweepSetup(accountFlag) {
|
|
29959
30208
|
return (serverKey) => setupCall(serverKey, accountFlag);
|
|
@@ -30204,6 +30453,49 @@ function registerErrorEnrichmentsCommand(root) {
|
|
|
30204
30453
|
});
|
|
30205
30454
|
addServerFlag(importCmd);
|
|
30206
30455
|
addAccountFlag(importCmd);
|
|
30456
|
+
const types = group.command("types").description("Manage the classification types rules can be filed under (per-server)");
|
|
30457
|
+
const typesListCmd = types.command("list").description("List classification types on one server or across servers").option("--json", "Output raw JSON instead of tables", false).action(
|
|
30458
|
+
async (opts) => {
|
|
30459
|
+
const servers = resolveTargetServers({
|
|
30460
|
+
server: opts.server,
|
|
30461
|
+
allServers: opts.allServers === true,
|
|
30462
|
+
serversCsv: opts.servers,
|
|
30463
|
+
includeTest: opts.includeTest === true
|
|
30464
|
+
});
|
|
30465
|
+
await runEnrichmentTypesList({
|
|
30466
|
+
servers,
|
|
30467
|
+
makeSetup: defaultSweepSetup(normaliseAccount(opts.account)),
|
|
30468
|
+
asJson: opts.json === true,
|
|
30469
|
+
out: process.stdout,
|
|
30470
|
+
errOut: process.stderr
|
|
30471
|
+
});
|
|
30472
|
+
}
|
|
30473
|
+
);
|
|
30474
|
+
addServerFlag(typesListCmd);
|
|
30475
|
+
addAccountFlag(typesListCmd);
|
|
30476
|
+
addSweepFlags(typesListCmd);
|
|
30477
|
+
const typesCreateCmd = types.command("create").description("Add a classification type (two-step confirm)").requiredOption("--name <name>", "Type name, e.g. Data-source:Timeout").option("--description <text>", "What the type covers").option("--confirm-token <token>", "Token from the plan step").action(
|
|
30478
|
+
async (opts) => {
|
|
30479
|
+
requireTypeName(opts.name);
|
|
30480
|
+
const setup = await setupCall(opts.server, normaliseAccount(opts.account));
|
|
30481
|
+
try {
|
|
30482
|
+
await runEnrichmentTypesCreate({
|
|
30483
|
+
client: setup.client,
|
|
30484
|
+
name: opts.name,
|
|
30485
|
+
description: opts.description,
|
|
30486
|
+
token: opts.confirmToken,
|
|
30487
|
+
serverFlag: resolveServer(opts.server),
|
|
30488
|
+
accountFlag: normaliseAccount(opts.account),
|
|
30489
|
+
signal: setup.signal,
|
|
30490
|
+
out: process.stdout
|
|
30491
|
+
});
|
|
30492
|
+
} finally {
|
|
30493
|
+
setup.cleanup();
|
|
30494
|
+
}
|
|
30495
|
+
}
|
|
30496
|
+
);
|
|
30497
|
+
addServerFlag(typesCreateCmd);
|
|
30498
|
+
addAccountFlag(typesCreateCmd);
|
|
30207
30499
|
}
|
|
30208
30500
|
|
|
30209
30501
|
// src/commands/skill.ts
|
|
@@ -34143,6 +34435,8 @@ function registerDocumentsCommand(root) {
|
|
|
34143
34435
|
|
|
34144
34436
|
// src/commands/integrations.ts
|
|
34145
34437
|
import { Option as Option2 } from "commander";
|
|
34438
|
+
import { constants as fsConstants } from "fs";
|
|
34439
|
+
import { lstat, open as open2 } from "fs/promises";
|
|
34146
34440
|
|
|
34147
34441
|
// src/api/integrations.ts
|
|
34148
34442
|
async function list23(client, opts, reqOptions) {
|
|
@@ -34167,7 +34461,7 @@ async function getDataSourcesPage(client, page, reqOptions) {
|
|
|
34167
34461
|
const out = await client.getJSON(path12, reqOptions);
|
|
34168
34462
|
return out ?? { next: null, results: [] };
|
|
34169
34463
|
}
|
|
34170
|
-
async function
|
|
34464
|
+
async function listTypes2(client, search, reqOptions) {
|
|
34171
34465
|
let path12 = "/api/v3/data_integrations/types/";
|
|
34172
34466
|
const s = (search ?? "").trim();
|
|
34173
34467
|
if (s !== "") {
|
|
@@ -34272,14 +34566,25 @@ async function sftpProvision(client, fileboxId, body, reqOptions) {
|
|
|
34272
34566
|
JSON.stringify(body),
|
|
34273
34567
|
reqOptions
|
|
34274
34568
|
);
|
|
34275
|
-
|
|
34276
|
-
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34569
|
+
return readRawJson(res);
|
|
34570
|
+
}
|
|
34571
|
+
async function netsuiteBundleReadiness(client, id, dryRun, reqOptions) {
|
|
34572
|
+
const res = await client.do(
|
|
34573
|
+
"POST",
|
|
34574
|
+
`/api/v3/data_integrations/data-sources/${id}/netsuite/bundle-readiness`,
|
|
34575
|
+
JSON.stringify({ dry_run: dryRun }),
|
|
34576
|
+
reqOptions
|
|
34577
|
+
);
|
|
34578
|
+
return readRawJson(res);
|
|
34579
|
+
}
|
|
34580
|
+
async function netsuiteSavedSearchSql(client, id, savedSearchId, reqOptions) {
|
|
34581
|
+
const res = await client.do(
|
|
34582
|
+
"GET",
|
|
34583
|
+
`/api/v3/data_integrations/data-sources/${id}/netsuite/saved-search-sql?saved_search_id=${encodeURIComponent(savedSearchId)}`,
|
|
34584
|
+
void 0,
|
|
34585
|
+
reqOptions
|
|
34586
|
+
);
|
|
34587
|
+
return readRawJson(res);
|
|
34283
34588
|
}
|
|
34284
34589
|
async function shareUsers2(client, id, emails, reqOptions) {
|
|
34285
34590
|
const out = await client.postJSON(
|
|
@@ -34356,7 +34661,7 @@ async function itResync(client, id, reqOptions) {
|
|
|
34356
34661
|
// src/commands/filebox.ts
|
|
34357
34662
|
import { promises as fsPromises, existsSync as existsSync2 } from "fs";
|
|
34358
34663
|
import * as nodePath from "path";
|
|
34359
|
-
import { createHash as
|
|
34664
|
+
import { createHash as createHash10 } from "crypto";
|
|
34360
34665
|
|
|
34361
34666
|
// src/commands/filebox_admin.ts
|
|
34362
34667
|
import { promises as fs15 } from "fs";
|
|
@@ -36420,6 +36725,290 @@ function registerFileboxRescanVersionCommand(fb) {
|
|
|
36420
36725
|
addAccountFlag(cmd);
|
|
36421
36726
|
}
|
|
36422
36727
|
|
|
36728
|
+
// src/commands/filebox_customReports.ts
|
|
36729
|
+
import { createHash as createHash9 } from "crypto";
|
|
36730
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
36731
|
+
function shortHash4(input) {
|
|
36732
|
+
return createHash9("sha256").update(input).digest("hex").slice(0, 12);
|
|
36733
|
+
}
|
|
36734
|
+
function isCustomReport(v) {
|
|
36735
|
+
if (v === null || typeof v !== "object") return false;
|
|
36736
|
+
const r = v;
|
|
36737
|
+
return typeof r.id === "number" && typeof r.name === "string";
|
|
36738
|
+
}
|
|
36739
|
+
async function fetchCustomReports(client, fileboxId, signal) {
|
|
36740
|
+
const res = await listCustomReports(client, fileboxId, { signal });
|
|
36741
|
+
if (!isSuccess(res.status)) {
|
|
36742
|
+
throw new Error(describeRawFailure("list custom reports", res));
|
|
36743
|
+
}
|
|
36744
|
+
const body = decodeShape(res.body);
|
|
36745
|
+
const reports = body?.custom_reports;
|
|
36746
|
+
if (!Array.isArray(reports) || !reports.every(isCustomReport)) {
|
|
36747
|
+
throw new Error(
|
|
36748
|
+
`list custom reports: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
36749
|
+
);
|
|
36750
|
+
}
|
|
36751
|
+
return { body, reports };
|
|
36752
|
+
}
|
|
36753
|
+
function usedBy(n) {
|
|
36754
|
+
const count = typeof n === "number" ? n : 0;
|
|
36755
|
+
return `${count} filebox${count === 1 ? "" : "es"}`;
|
|
36756
|
+
}
|
|
36757
|
+
function writeLinkResult(cfg, what, res, headline) {
|
|
36758
|
+
if (!isSuccess(res.status)) {
|
|
36759
|
+
throw new Error(describeRawFailure(what, res));
|
|
36760
|
+
}
|
|
36761
|
+
const body = decodeShape(res.body);
|
|
36762
|
+
if (body === null || typeof body !== "object" || !isCustomReport(body.custom_report)) {
|
|
36763
|
+
throw new Error(
|
|
36764
|
+
`${what}: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
36765
|
+
);
|
|
36766
|
+
}
|
|
36767
|
+
if (cfg.asJson) {
|
|
36768
|
+
writeJSONIndented(cfg.out, body);
|
|
36769
|
+
return;
|
|
36770
|
+
}
|
|
36771
|
+
const report = body.custom_report;
|
|
36772
|
+
cfg.out.write(`${headline(report)} (used by ${usedBy(report.usage_count)}).
|
|
36773
|
+
`);
|
|
36774
|
+
}
|
|
36775
|
+
function describeCurrentQuery(raw) {
|
|
36776
|
+
let config = raw?.config;
|
|
36777
|
+
if (typeof config === "string") {
|
|
36778
|
+
try {
|
|
36779
|
+
config = JSON.parse(config);
|
|
36780
|
+
} catch {
|
|
36781
|
+
config = null;
|
|
36782
|
+
}
|
|
36783
|
+
}
|
|
36784
|
+
if (config === null || typeof config !== "object") return "no data query";
|
|
36785
|
+
const c = config;
|
|
36786
|
+
const type = typeof c["objectType"] === "string" && c["objectType"] !== "" ? c["objectType"] : "-";
|
|
36787
|
+
const key = c["source_query_key"];
|
|
36788
|
+
const reportId = c["custom_report_id"];
|
|
36789
|
+
const source = typeof key === "string" && key !== "" ? `library ${key}` : typeof reportId === "number" ? `custom report #${reportId}` : "custom";
|
|
36790
|
+
const query = c["query"];
|
|
36791
|
+
const length = typeof query === "string" ? `${query.length} chars` : "none";
|
|
36792
|
+
return `objectType ${type}, source ${source}, query ${length}`;
|
|
36793
|
+
}
|
|
36794
|
+
async function describeFileboxTarget(client, fileboxId, signal) {
|
|
36795
|
+
const details = decodeShape(await getDetails(client, fileboxId, { signal }));
|
|
36796
|
+
const dataQuery = decodeShape(await getDataQueryRaw(client, fileboxId, { signal }));
|
|
36797
|
+
return [`filebox: ${fileboxId} "${details.name}"`, `current: ${describeCurrentQuery(dataQuery)}`];
|
|
36798
|
+
}
|
|
36799
|
+
async function runFileboxCustomReportsList(cfg) {
|
|
36800
|
+
const { body, reports } = await fetchCustomReports(cfg.client, cfg.fileboxId, cfg.signal);
|
|
36801
|
+
if (cfg.asJson) {
|
|
36802
|
+
writeJSONIndented(cfg.out, body);
|
|
36803
|
+
return;
|
|
36804
|
+
}
|
|
36805
|
+
if (reports.length === 0) {
|
|
36806
|
+
cfg.out.write(`No custom reports on the connection of filebox ${cfg.fileboxId}.
|
|
36807
|
+
`);
|
|
36808
|
+
return;
|
|
36809
|
+
}
|
|
36810
|
+
const rows = [
|
|
36811
|
+
["ID", "NAME", "USED BY"],
|
|
36812
|
+
...reports.map((r) => [String(r.id), r.name, usedBy(r.usage_count)])
|
|
36813
|
+
];
|
|
36814
|
+
const widths = [0, 1].map((i) => Math.max(...rows.map((row) => (row[i] ?? "").length)));
|
|
36815
|
+
for (const row of rows) {
|
|
36816
|
+
const [id = "", name = "", used = ""] = row;
|
|
36817
|
+
cfg.out.write(
|
|
36818
|
+
`${id.padEnd((widths[0] ?? 0) + 2)}${name.padEnd((widths[1] ?? 0) + 2)}${used}
|
|
36819
|
+
`
|
|
36820
|
+
);
|
|
36821
|
+
}
|
|
36822
|
+
cfg.out.write(`${reports.length} custom report${plural(reports.length)}
|
|
36823
|
+
`);
|
|
36824
|
+
}
|
|
36825
|
+
function customReportCreateAction(fileboxId, name, query) {
|
|
36826
|
+
const hash = shortHash4(JSON.stringify([name.trim(), query]));
|
|
36827
|
+
return { resource: "filebox-custom-report", id: `${fileboxId}:${hash}`, verb: "create" };
|
|
36828
|
+
}
|
|
36829
|
+
async function readCustomReportQueryFile(path12) {
|
|
36830
|
+
if (path12.trim() === "") {
|
|
36831
|
+
throw new Error("--query-file is required");
|
|
36832
|
+
}
|
|
36833
|
+
const query = await readFile5(path12, "utf8");
|
|
36834
|
+
if (query.trim() === "") {
|
|
36835
|
+
throw new Error(`--query-file ${path12} is empty`);
|
|
36836
|
+
}
|
|
36837
|
+
return query;
|
|
36838
|
+
}
|
|
36839
|
+
async function runFileboxCustomReportsCreate(cfg) {
|
|
36840
|
+
const name = cfg.name.trim();
|
|
36841
|
+
if (name === "") {
|
|
36842
|
+
throw new Error("--name is required");
|
|
36843
|
+
}
|
|
36844
|
+
const action = customReportCreateAction(cfg.fileboxId, name, cfg.query);
|
|
36845
|
+
await preflightConfirmToken(action, cfg.token);
|
|
36846
|
+
const firstLine = cfg.query.trim().split("\n")[0] ?? "";
|
|
36847
|
+
await twoStepConfirm({
|
|
36848
|
+
out: cfg.out,
|
|
36849
|
+
action,
|
|
36850
|
+
token: cfg.token,
|
|
36851
|
+
server: cfg.serverFlag,
|
|
36852
|
+
confirmCmd: `dr filebox custom-reports create ${cfg.fileboxId} --name ${shellQuote(name)} --query-file ${shellQuote(cfg.queryFile)} --server ${cfg.serverFlag}`,
|
|
36853
|
+
describe: async () => [
|
|
36854
|
+
...await describeFileboxTarget(cfg.client, cfg.fileboxId, cfg.signal),
|
|
36855
|
+
`name: "${name}" (unique on the filebox's NetSuite connection)`,
|
|
36856
|
+
`query: ${cfg.query.length} chars, starting "${truncate3(firstLine, 60)}"`,
|
|
36857
|
+
"effect: creates the custom report and links this filebox to it, replacing the filebox's query"
|
|
36858
|
+
],
|
|
36859
|
+
execute: async () => {
|
|
36860
|
+
const res = await createCustomReport(
|
|
36861
|
+
cfg.client,
|
|
36862
|
+
cfg.fileboxId,
|
|
36863
|
+
{ name, query: cfg.query },
|
|
36864
|
+
{ signal: cfg.signal }
|
|
36865
|
+
);
|
|
36866
|
+
writeLinkResult(
|
|
36867
|
+
cfg,
|
|
36868
|
+
"create custom report",
|
|
36869
|
+
res,
|
|
36870
|
+
(r) => `Created custom report #${r.id} "${r.name}" and linked filebox ${cfg.fileboxId} to it`
|
|
36871
|
+
);
|
|
36872
|
+
}
|
|
36873
|
+
});
|
|
36874
|
+
}
|
|
36875
|
+
function customReportApplyAction(fileboxId, reportId, query) {
|
|
36876
|
+
return {
|
|
36877
|
+
resource: "filebox-custom-report",
|
|
36878
|
+
id: `${fileboxId}:${reportId}:${shortHash4(query)}`,
|
|
36879
|
+
verb: "apply"
|
|
36880
|
+
};
|
|
36881
|
+
}
|
|
36882
|
+
async function runFileboxCustomReportsApply(cfg) {
|
|
36883
|
+
const { reports } = await fetchCustomReports(cfg.client, cfg.fileboxId, cfg.signal);
|
|
36884
|
+
const report = reports.find((r) => r.id === cfg.reportId);
|
|
36885
|
+
if (report === void 0) {
|
|
36886
|
+
throw new Error(
|
|
36887
|
+
`custom report ${cfg.reportId} is not on the connection of filebox ${cfg.fileboxId}; see dr filebox custom-reports list ${cfg.fileboxId}`
|
|
36888
|
+
);
|
|
36889
|
+
}
|
|
36890
|
+
const query = typeof report.query === "string" ? report.query : "";
|
|
36891
|
+
const action = customReportApplyAction(cfg.fileboxId, cfg.reportId, query);
|
|
36892
|
+
await preflightConfirmToken(action, cfg.token);
|
|
36893
|
+
await twoStepConfirm({
|
|
36894
|
+
out: cfg.out,
|
|
36895
|
+
action,
|
|
36896
|
+
token: cfg.token,
|
|
36897
|
+
server: cfg.serverFlag,
|
|
36898
|
+
confirmCmd: `dr filebox custom-reports apply ${cfg.fileboxId} ${cfg.reportId} --server ${cfg.serverFlag}`,
|
|
36899
|
+
describe: async () => [
|
|
36900
|
+
...await describeFileboxTarget(cfg.client, cfg.fileboxId, cfg.signal),
|
|
36901
|
+
`report: #${report.id} "${report.name}" (used by ${usedBy(report.usage_count)})`,
|
|
36902
|
+
`sql: ${query.length} chars, sha256 ${shortHash4(query)}`,
|
|
36903
|
+
"effect: links this filebox to the report, replacing the filebox's query with the report's"
|
|
36904
|
+
],
|
|
36905
|
+
execute: async () => {
|
|
36906
|
+
const res = await applyCustomReport(cfg.client, cfg.fileboxId, cfg.reportId, {
|
|
36907
|
+
signal: cfg.signal
|
|
36908
|
+
});
|
|
36909
|
+
writeLinkResult(
|
|
36910
|
+
cfg,
|
|
36911
|
+
"apply custom report",
|
|
36912
|
+
res,
|
|
36913
|
+
(r) => `Linked filebox ${cfg.fileboxId} to custom report #${r.id} "${r.name}"`
|
|
36914
|
+
);
|
|
36915
|
+
}
|
|
36916
|
+
});
|
|
36917
|
+
}
|
|
36918
|
+
function registerFileboxCustomReportsCommands(fb) {
|
|
36919
|
+
const reports = fb.command("custom-reports").description(
|
|
36920
|
+
"SuiteQL custom reports of a filebox's NetSuite connection: named queries its fileboxes share"
|
|
36921
|
+
);
|
|
36922
|
+
const listCmd = reports.command("list").description(
|
|
36923
|
+
"List the custom reports of the filebox's NetSuite connection, with how many fileboxes use each"
|
|
36924
|
+
).argument("<filebox_id>", "filebox id (positive integer)").option("--json", "Output the raw response as JSON (decoded)", false).action(async (idStr, opts) => {
|
|
36925
|
+
const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
|
|
36926
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36927
|
+
try {
|
|
36928
|
+
await runFileboxCustomReportsList({
|
|
36929
|
+
client: setup.client,
|
|
36930
|
+
fileboxId,
|
|
36931
|
+
asJson: opts.json === true,
|
|
36932
|
+
signal: setup.signal,
|
|
36933
|
+
out: process.stdout
|
|
36934
|
+
});
|
|
36935
|
+
} finally {
|
|
36936
|
+
setup.cleanup();
|
|
36937
|
+
}
|
|
36938
|
+
});
|
|
36939
|
+
addServerFlag(listCmd);
|
|
36940
|
+
addAccountFlag(listCmd);
|
|
36941
|
+
const createCmd = reports.command("create").description(
|
|
36942
|
+
"Create a custom report on the filebox's NetSuite connection and link the filebox to it, replacing its query (two-step confirmation). The filebox's data query must already be SuiteQL. 409 means the name is taken."
|
|
36943
|
+
).argument("<filebox_id>", "filebox id (positive integer)").requiredOption("--name <name>", "Report name, unique on the connection (required)").requiredOption("--query-file <path>", "File holding the SuiteQL query (required)").option(
|
|
36944
|
+
"--confirm-token <token>",
|
|
36945
|
+
"Token from a prior plan run - required to actually create",
|
|
36946
|
+
""
|
|
36947
|
+
).option("--json", "Output the raw response as JSON (decoded)", false).action(
|
|
36948
|
+
async (idStr, opts) => {
|
|
36949
|
+
const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
|
|
36950
|
+
if (opts.name.trim() === "") {
|
|
36951
|
+
throw new Error("--name is required");
|
|
36952
|
+
}
|
|
36953
|
+
const query = await readCustomReportQueryFile(opts.queryFile);
|
|
36954
|
+
await preflightConfirmToken(
|
|
36955
|
+
customReportCreateAction(fileboxId, opts.name, query),
|
|
36956
|
+
opts.confirmToken
|
|
36957
|
+
);
|
|
36958
|
+
const serverKey = resolveServer(opts.server);
|
|
36959
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36960
|
+
try {
|
|
36961
|
+
await runFileboxCustomReportsCreate({
|
|
36962
|
+
client: setup.client,
|
|
36963
|
+
fileboxId,
|
|
36964
|
+
name: opts.name,
|
|
36965
|
+
query,
|
|
36966
|
+
token: opts.confirmToken,
|
|
36967
|
+
serverFlag: serverKey,
|
|
36968
|
+
queryFile: opts.queryFile,
|
|
36969
|
+
asJson: opts.json === true,
|
|
36970
|
+
signal: setup.signal,
|
|
36971
|
+
out: process.stdout
|
|
36972
|
+
});
|
|
36973
|
+
} finally {
|
|
36974
|
+
setup.cleanup();
|
|
36975
|
+
}
|
|
36976
|
+
}
|
|
36977
|
+
);
|
|
36978
|
+
addServerFlag(createCmd);
|
|
36979
|
+
addAccountFlag(createCmd);
|
|
36980
|
+
const applyCmd = reports.command("apply").description(
|
|
36981
|
+
"Link the filebox to an existing custom report of its connection, replacing its query with the report's (two-step confirmation)"
|
|
36982
|
+
).argument("<filebox_id>", "filebox id (positive integer)").argument("<report_id>", "custom report id (from custom-reports list)").option(
|
|
36983
|
+
"--confirm-token <token>",
|
|
36984
|
+
"Token from a prior plan run - required to actually apply",
|
|
36985
|
+
""
|
|
36986
|
+
).option("--json", "Output the raw response as JSON (decoded)", false).action(
|
|
36987
|
+
async (idStr, reportIdStr, opts) => {
|
|
36988
|
+
const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
|
|
36989
|
+
const reportId = parsePositiveIntArg(reportIdStr, "<report_id>");
|
|
36990
|
+
const serverKey = resolveServer(opts.server);
|
|
36991
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36992
|
+
try {
|
|
36993
|
+
await runFileboxCustomReportsApply({
|
|
36994
|
+
client: setup.client,
|
|
36995
|
+
fileboxId,
|
|
36996
|
+
reportId,
|
|
36997
|
+
token: opts.confirmToken,
|
|
36998
|
+
serverFlag: serverKey,
|
|
36999
|
+
asJson: opts.json === true,
|
|
37000
|
+
signal: setup.signal,
|
|
37001
|
+
out: process.stdout
|
|
37002
|
+
});
|
|
37003
|
+
} finally {
|
|
37004
|
+
setup.cleanup();
|
|
37005
|
+
}
|
|
37006
|
+
}
|
|
37007
|
+
);
|
|
37008
|
+
addServerFlag(applyCmd);
|
|
37009
|
+
addAccountFlag(applyCmd);
|
|
37010
|
+
}
|
|
37011
|
+
|
|
36423
37012
|
// src/api/forecast.ts
|
|
36424
37013
|
var SCENARIO_PARAM = "Forecast";
|
|
36425
37014
|
async function listScenarios(client, filter = {}, options) {
|
|
@@ -36804,6 +37393,11 @@ var DQ_WAIT_POLL_INTERVAL_MS = 5e3;
|
|
|
36804
37393
|
var DQ_WAIT_POLL_MAX_INTERVAL_MS = 3e4;
|
|
36805
37394
|
var DQ_WAIT_DEFAULT_TIMEOUT_SEC = 1800;
|
|
36806
37395
|
var DQ_WAIT_TIMEOUT_MAX_SEC = 86400;
|
|
37396
|
+
var SYNC_EVENT_ACTION = "integration_query_status_changed";
|
|
37397
|
+
var EMPTY_SYNC_EVENT_PREFIX = "Sync completed successfully, but no new data was retrieved due to an empty response";
|
|
37398
|
+
var SYNC_EVENT_LIMIT = 50;
|
|
37399
|
+
var SYNC_EVENT_MAX_PAGES = 10;
|
|
37400
|
+
var DATE_HEADER_RESOLUTION_MS = 1e3;
|
|
36807
37401
|
var DQ_WAIT_SETUP_OVERHEAD_MS = 6e4;
|
|
36808
37402
|
function dataQuerySyncCallTimeoutMs(wait, timeoutSec) {
|
|
36809
37403
|
if (!wait) return void 0;
|
|
@@ -36838,7 +37432,7 @@ function maxId(rows) {
|
|
|
36838
37432
|
function ref(v) {
|
|
36839
37433
|
return { id: v.id, version: v.version };
|
|
36840
37434
|
}
|
|
36841
|
-
function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw) {
|
|
37435
|
+
function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw, eventsRaw = null) {
|
|
36842
37436
|
const dataQuery = asRecord(dataQueryRaw);
|
|
36843
37437
|
return {
|
|
36844
37438
|
periods: periods.map((p) => {
|
|
@@ -36849,7 +37443,8 @@ function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw) {
|
|
|
36849
37443
|
};
|
|
36850
37444
|
}),
|
|
36851
37445
|
maxVersionId: maxId(versions),
|
|
36852
|
-
updateTime: stamp(dataQuery["update_time"])
|
|
37446
|
+
updateTime: stamp(dataQuery["update_time"]),
|
|
37447
|
+
emptyEventIds: Array.isArray(eventsRaw) ? new Set(emptySyncEvents(eventsRaw).map((e) => e.id)) : null
|
|
36853
37448
|
};
|
|
36854
37449
|
}
|
|
36855
37450
|
function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw) {
|
|
@@ -36882,11 +37477,12 @@ function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw
|
|
|
36882
37477
|
}
|
|
36883
37478
|
async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal, preRead) {
|
|
36884
37479
|
try {
|
|
36885
|
-
const [versions, dataQuery] = await Promise.all([
|
|
37480
|
+
const [versions, dataQuery, events] = await Promise.all([
|
|
36886
37481
|
listVersions(client, fileboxId, {}, { signal }),
|
|
36887
|
-
preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal })
|
|
37482
|
+
preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal }),
|
|
37483
|
+
readSyncEvents(client, fileboxId, signal)
|
|
36888
37484
|
]);
|
|
36889
|
-
return buildDataQuerySyncBaseline(periods, versions, dataQuery);
|
|
37485
|
+
return buildDataQuerySyncBaseline(periods, versions, dataQuery, events);
|
|
36890
37486
|
} catch (err) {
|
|
36891
37487
|
const msg = err instanceof Error ? err.message : String(err);
|
|
36892
37488
|
throw new Error(
|
|
@@ -36894,6 +37490,61 @@ async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal,
|
|
|
36894
37490
|
);
|
|
36895
37491
|
}
|
|
36896
37492
|
}
|
|
37493
|
+
function emptySyncEvents(eventsRaw) {
|
|
37494
|
+
const out = [];
|
|
37495
|
+
for (const raw of eventsRaw) {
|
|
37496
|
+
const e = asRecord(raw);
|
|
37497
|
+
const id = e["_id"];
|
|
37498
|
+
const text2 = e["text"];
|
|
37499
|
+
if (typeof id !== "string" || id === "" || typeof text2 !== "string") continue;
|
|
37500
|
+
const decoded = decodeShape(text2);
|
|
37501
|
+
if (!decoded.startsWith(EMPTY_SYNC_EVENT_PREFIX)) continue;
|
|
37502
|
+
const at = decoded.lastIndexOf(" for ");
|
|
37503
|
+
const label = at >= 0 && decoded.endsWith(".") ? decoded.slice(at + " for ".length, -1) : void 0;
|
|
37504
|
+
const datetime = typeof e["datetime"] === "number" ? e["datetime"] : void 0;
|
|
37505
|
+
out.push({ id, datetime, label });
|
|
37506
|
+
}
|
|
37507
|
+
return out;
|
|
37508
|
+
}
|
|
37509
|
+
function findEmptyPeriods(periods, baseline, progress, eventsRaw, sinceMs) {
|
|
37510
|
+
const empty = /* @__PURE__ */ new Set();
|
|
37511
|
+
const known = baseline.emptyEventIds;
|
|
37512
|
+
if (known === null || sinceMs === void 0 || !Array.isArray(eventsRaw)) return empty;
|
|
37513
|
+
if (progress.error !== void 0 || progress.unmatchedNewVersions.length > 0) return empty;
|
|
37514
|
+
const fresh = emptySyncEvents(eventsRaw).filter(
|
|
37515
|
+
(e) => !known.has(e.id) && e.datetime !== void 0 && e.datetime >= sinceMs
|
|
37516
|
+
);
|
|
37517
|
+
periods.forEach((p, i) => {
|
|
37518
|
+
if (progress.periods[i]?.result !== "pending") return;
|
|
37519
|
+
const matches = fresh.some(
|
|
37520
|
+
(e) => p.eventLabel === void 0 ? periods.length === 1 : e.label === p.eventLabel || e.label === void 0 && periods.length === 1
|
|
37521
|
+
);
|
|
37522
|
+
if (matches) empty.add(i);
|
|
37523
|
+
});
|
|
37524
|
+
return empty;
|
|
37525
|
+
}
|
|
37526
|
+
async function readSyncEvents(client, fileboxId, signal, sinceMs) {
|
|
37527
|
+
const events = [];
|
|
37528
|
+
try {
|
|
37529
|
+
for (let page = 0; page < SYNC_EVENT_MAX_PAGES; page++) {
|
|
37530
|
+
const rows = await listDocumentEvents(
|
|
37531
|
+
client,
|
|
37532
|
+
fileboxId,
|
|
37533
|
+
SYNC_EVENT_ACTION,
|
|
37534
|
+
{ skip: page * SYNC_EVENT_LIMIT, limit: SYNC_EVENT_LIMIT },
|
|
37535
|
+
{ signal }
|
|
37536
|
+
);
|
|
37537
|
+
if (!Array.isArray(rows)) return null;
|
|
37538
|
+
events.push(...rows);
|
|
37539
|
+
if (sinceMs === void 0 || rows.length < SYNC_EVENT_LIMIT) break;
|
|
37540
|
+
const oldest = asRecord(rows[rows.length - 1])["datetime"];
|
|
37541
|
+
if (typeof oldest !== "number" || oldest < sinceMs) break;
|
|
37542
|
+
}
|
|
37543
|
+
} catch {
|
|
37544
|
+
return null;
|
|
37545
|
+
}
|
|
37546
|
+
return events;
|
|
37547
|
+
}
|
|
36897
37548
|
var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
36898
37549
|
if (signal?.aborted) {
|
|
36899
37550
|
reject(signal.reason ?? new Error("aborted"));
|
|
@@ -36923,15 +37574,42 @@ async function waitForDataQuerySync(cfg) {
|
|
|
36923
37574
|
`
|
|
36924
37575
|
);
|
|
36925
37576
|
}
|
|
37577
|
+
const sinceMs = cfg.triggerServerTimeMs === void 0 ? void 0 : cfg.triggerServerTimeMs - DATE_HEADER_RESOLUTION_MS;
|
|
37578
|
+
let warnedEvents = false;
|
|
37579
|
+
const warnEvents = (why) => {
|
|
37580
|
+
if (warnedEvents) return;
|
|
37581
|
+
warnedEvents = true;
|
|
37582
|
+
cfg.errOut.write(
|
|
37583
|
+
`warning: ${why} \u2014 a period whose sync returns no rows cannot be confirmed and will time out
|
|
37584
|
+
`
|
|
37585
|
+
);
|
|
37586
|
+
};
|
|
37587
|
+
if (sinceMs === void 0) warnEvents("the sync response carried no Date header");
|
|
37588
|
+
if (cfg.baseline.emptyEventIds === null) warnEvents("the filebox's sync events are unreadable");
|
|
37589
|
+
const settledEmpty = /* @__PURE__ */ new Set();
|
|
36926
37590
|
let progress;
|
|
36927
37591
|
let outcome;
|
|
36928
37592
|
for (; ; ) {
|
|
36929
37593
|
try {
|
|
36930
|
-
const [versions, dataQuery] = await Promise.all([
|
|
37594
|
+
const [versions, dataQuery, events] = await Promise.all([
|
|
36931
37595
|
listVersions(cfg.client, cfg.fileboxId, {}, { signal: cfg.signal }),
|
|
36932
|
-
getDataQueryRaw(cfg.client, cfg.fileboxId, { signal: cfg.signal })
|
|
37596
|
+
getDataQueryRaw(cfg.client, cfg.fileboxId, { signal: cfg.signal }),
|
|
37597
|
+
readSyncEvents(cfg.client, cfg.fileboxId, cfg.signal, sinceMs)
|
|
36933
37598
|
]);
|
|
36934
|
-
|
|
37599
|
+
if (events === null) warnEvents("the filebox's sync events are unreadable");
|
|
37600
|
+
else if (emptySyncEvents(events).some((e) => e.datetime === void 0)) {
|
|
37601
|
+
warnEvents("a sync event carries no numeric timestamp");
|
|
37602
|
+
}
|
|
37603
|
+
const polled = classifyDataQuerySyncProgress(cfg.periods, cfg.baseline, versions, dataQuery);
|
|
37604
|
+
for (const i of findEmptyPeriods(cfg.periods, cfg.baseline, polled, events, sinceMs)) {
|
|
37605
|
+
settledEmpty.add(i);
|
|
37606
|
+
}
|
|
37607
|
+
progress = {
|
|
37608
|
+
...polled,
|
|
37609
|
+
periods: polled.periods.map(
|
|
37610
|
+
(p, i) => p.result === "pending" && settledEmpty.has(i) ? { period: p.period, result: "empty" } : p
|
|
37611
|
+
)
|
|
37612
|
+
};
|
|
36935
37613
|
} catch (err) {
|
|
36936
37614
|
if (cfg.signal.aborted) {
|
|
36937
37615
|
outcome = "cancelled";
|
|
@@ -36992,6 +37670,7 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
|
|
|
36992
37670
|
const count = (r) => progress.periods.filter((p) => p.result === r).length;
|
|
36993
37671
|
const parts = [`${count("new_version")} new version${plural(count("new_version"))}`];
|
|
36994
37672
|
parts.push(`${count("no_change")} unchanged`);
|
|
37673
|
+
if (count("empty") > 0) parts.push(`${count("empty")} empty`);
|
|
36995
37674
|
if (count("pending") > 0) parts.push(`${count("pending")} unfinished`);
|
|
36996
37675
|
const headline = {
|
|
36997
37676
|
completed: "Sync finished",
|
|
@@ -37012,6 +37691,11 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
|
|
|
37012
37691
|
} else if (p.result === "no_change") {
|
|
37013
37692
|
cfg.out.write(
|
|
37014
37693
|
` ${p.period}: unchanged \u2014 identical to version #${p.version.id} (v${p.version.version}), so no new version was written
|
|
37694
|
+
`
|
|
37695
|
+
);
|
|
37696
|
+
} else if (p.result === "empty") {
|
|
37697
|
+
cfg.out.write(
|
|
37698
|
+
` ${p.period}: empty \u2014 the server logged an empty response for this period, so nothing was written
|
|
37015
37699
|
`
|
|
37016
37700
|
);
|
|
37017
37701
|
} else if (outcome === "timed_out") {
|
|
@@ -37991,7 +38675,7 @@ function buildIncludeBatchAction(docId, include, targets) {
|
|
|
37991
38675
|
const canonical = `${docId}|${include ? "on" : "off"}|${targets.map((v) => v.id).slice().sort((a, b) => a - b).join(",")}`;
|
|
37992
38676
|
return {
|
|
37993
38677
|
resource: "filebox-version-batch",
|
|
37994
|
-
id:
|
|
38678
|
+
id: createHash10("sha256").update(canonical).digest("hex").slice(0, 12),
|
|
37995
38679
|
verb: includeStateWords(include).verb
|
|
37996
38680
|
};
|
|
37997
38681
|
}
|
|
@@ -39338,23 +40022,30 @@ function resolveDateForSubType(subType, parsed2) {
|
|
|
39338
40022
|
const { year, month0, day } = parsed2;
|
|
39339
40023
|
switch ((subType ?? "").toLowerCase()) {
|
|
39340
40024
|
case "year":
|
|
39341
|
-
return {
|
|
40025
|
+
return {
|
|
40026
|
+
value: utcDateToNoonEpoch(year, 0, 1),
|
|
40027
|
+
label: String(year),
|
|
40028
|
+
eventLabel: String(year)
|
|
40029
|
+
};
|
|
39342
40030
|
case "quarter": {
|
|
39343
40031
|
const q = Math.floor(month0 / 3) + 1;
|
|
39344
40032
|
return {
|
|
39345
40033
|
value: utcDateToNoonEpoch(year, (q - 1) * 3, 1),
|
|
39346
|
-
label: `Q${q} ${year}
|
|
40034
|
+
label: `Q${q} ${year}`,
|
|
40035
|
+
eventLabel: `Q${q} ${year}`
|
|
39347
40036
|
};
|
|
39348
40037
|
}
|
|
39349
40038
|
case "month":
|
|
39350
40039
|
return {
|
|
39351
40040
|
value: utcDateToNoonEpoch(year, month0, 1),
|
|
39352
|
-
label: `${MONTH_NAMES[month0] ?? ""} ${year}
|
|
40041
|
+
label: `${MONTH_NAMES[month0] ?? ""} ${year}`,
|
|
40042
|
+
eventLabel: `${MONTH_NAMES[month0] ?? ""} ${year}`
|
|
39353
40043
|
};
|
|
39354
40044
|
case "day":
|
|
39355
40045
|
return {
|
|
39356
40046
|
value: utcDateToNoonEpoch(year, month0, day),
|
|
39357
|
-
label: `${MONTH_NAMES_SHORT[month0] ?? ""} ${day}, ${year}
|
|
40047
|
+
label: `${MONTH_NAMES_SHORT[month0] ?? ""} ${day}, ${year}`,
|
|
40048
|
+
eventLabel: `${String(month0 + 1).padStart(2, "0")}/${String(day).padStart(2, "0")}/${year}`
|
|
39358
40049
|
};
|
|
39359
40050
|
case "week": {
|
|
39360
40051
|
const date = new Date(Date.UTC(year, month0, day));
|
|
@@ -39365,7 +40056,8 @@ function resolveDateForSubType(subType, parsed2) {
|
|
|
39365
40056
|
return {
|
|
39366
40057
|
// Noon-UTC convention, same as the other branches.
|
|
39367
40058
|
value: Math.floor(monday.getTime() / 1e3) + 12 * 60 * 60,
|
|
39368
|
-
label: `Week ${week} ${isoYear}
|
|
40059
|
+
label: `Week ${week} ${isoYear}`,
|
|
40060
|
+
eventLabel: `W${String(week).padStart(2, "0")} ${isoYear}`
|
|
39369
40061
|
};
|
|
39370
40062
|
}
|
|
39371
40063
|
default:
|
|
@@ -39418,8 +40110,8 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39418
40110
|
].map((t) => dateTags.map((tag) => resolveDateForSubType(tagSubType(tag), t).value));
|
|
39419
40111
|
for (const parsed2 of parsedDates) {
|
|
39420
40112
|
const resolved = dateTags.map((tag) => {
|
|
39421
|
-
const { value, label: label2 } = resolveDateForSubType(tagSubType(tag), parsed2);
|
|
39422
|
-
return { id: tag.id, name: tag.name, value, label: label2 };
|
|
40113
|
+
const { value, label: label2, eventLabel } = resolveDateForSubType(tagSubType(tag), parsed2);
|
|
40114
|
+
return { id: tag.id, name: tag.name, value, label: label2, eventLabel };
|
|
39423
40115
|
});
|
|
39424
40116
|
dateTargets.push(
|
|
39425
40117
|
JSON.stringify(resolved.map(({ id, name, value }) => ({ id, name, value })))
|
|
@@ -39430,6 +40122,8 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39430
40122
|
periodLabels.push(label);
|
|
39431
40123
|
waitPeriods.push({
|
|
39432
40124
|
label,
|
|
40125
|
+
// The server joins a version's DATE labels in tag-id order.
|
|
40126
|
+
eventLabel: [...resolved].sort((a, b) => a.id - b.id).map((r) => r.eventLabel).join(", "),
|
|
39433
40127
|
tags: dateTags.map((tag, i) => ({
|
|
39434
40128
|
id: tag.id,
|
|
39435
40129
|
inPeriod: (stored) => samePeriodAs(tagSubType(tag), stored, resolved[i].value)
|
|
@@ -39470,9 +40164,17 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39470
40164
|
if (multipleTags.length > 0) {
|
|
39471
40165
|
body["multiple_tags"] = multipleTags;
|
|
39472
40166
|
}
|
|
39473
|
-
|
|
39474
|
-
|
|
39475
|
-
|
|
40167
|
+
let resp;
|
|
40168
|
+
let triggerServerTimeMs;
|
|
40169
|
+
if (cfg.wait === void 0) {
|
|
40170
|
+
resp = await syncDataQuery(cfg.client, cfg.id, body, { signal: cfg.signal });
|
|
40171
|
+
} else {
|
|
40172
|
+
const timed = await syncDataQueryTimed(cfg.client, cfg.id, body, {
|
|
40173
|
+
signal: cfg.signal
|
|
40174
|
+
});
|
|
40175
|
+
resp = timed.body;
|
|
40176
|
+
triggerServerTimeMs = timed.serverTimeMs;
|
|
40177
|
+
}
|
|
39476
40178
|
let detail = "OK";
|
|
39477
40179
|
if (typeof resp["details"] === "string" && resp["details"] !== "") {
|
|
39478
40180
|
detail = resp["details"];
|
|
@@ -39500,6 +40202,7 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39500
40202
|
await waitForDataQuerySync({
|
|
39501
40203
|
client: cfg.client,
|
|
39502
40204
|
fileboxId: cfg.id,
|
|
40205
|
+
triggerServerTimeMs,
|
|
39503
40206
|
periods: waitPeriods,
|
|
39504
40207
|
baseline,
|
|
39505
40208
|
timeoutSec: cfg.wait.timeoutSec,
|
|
@@ -40814,7 +41517,7 @@ function registerFileboxCommand(root) {
|
|
|
40814
41517
|
[]
|
|
40815
41518
|
).option(
|
|
40816
41519
|
"--wait",
|
|
40817
|
-
"After queueing, wait until every --date period has finished, and say whether each wrote a new version
|
|
41520
|
+
"After queueing, wait until every --date period has finished, and say whether each wrote a new version, was unchanged (identical output writes no version), or was empty (the server logged an empty response for that period). Exits non-zero on a failed sync or a timeout. Not combinable with --tag.",
|
|
40818
41521
|
false
|
|
40819
41522
|
).option(
|
|
40820
41523
|
"--timeout <seconds>",
|
|
@@ -40850,6 +41553,7 @@ function registerFileboxCommand(root) {
|
|
|
40850
41553
|
registerFileboxAdminCommands(fb, setupCall, addServerFlag, addAccountFlag);
|
|
40851
41554
|
registerFileboxKindCommands(fb);
|
|
40852
41555
|
registerFileboxRescanVersionCommand(fb);
|
|
41556
|
+
registerFileboxCustomReportsCommands(fb);
|
|
40853
41557
|
}
|
|
40854
41558
|
|
|
40855
41559
|
// src/commands/integrations.ts
|
|
@@ -41055,7 +41759,7 @@ function parseDataSourceIntegration(raw) {
|
|
|
41055
41759
|
return i;
|
|
41056
41760
|
}
|
|
41057
41761
|
async function runIntegrationsTypes(cfg) {
|
|
41058
|
-
const items = await
|
|
41762
|
+
const items = await listTypes2(cfg.client, cfg.search, {
|
|
41059
41763
|
signal: cfg.signal
|
|
41060
41764
|
});
|
|
41061
41765
|
if (cfg.asJson) {
|
|
@@ -41669,6 +42373,172 @@ Finish it with: dr integrations sftp provision ${id} (plus the same --date-*/--b
|
|
|
41669
42373
|
`);
|
|
41670
42374
|
writeSftpKit(cfg.out, kit);
|
|
41671
42375
|
}
|
|
42376
|
+
var NETSUITE_CALL_TIMEOUT_MS = 12e4;
|
|
42377
|
+
function netsuiteStampAction(id) {
|
|
42378
|
+
return { resource: "netsuite-bundle-version", id: String(id), verb: "stamp" };
|
|
42379
|
+
}
|
|
42380
|
+
function describeNetSuiteFailure(what, id, res) {
|
|
42381
|
+
return describeRawFailure(what, res, {
|
|
42382
|
+
404: `found no NetSuite connection ${id} visible to this account`
|
|
42383
|
+
});
|
|
42384
|
+
}
|
|
42385
|
+
var SAVED_SEARCH_ID_RE = /^(?:-?[0-9]+|customsearch[A-Za-z0-9_]*)$/;
|
|
42386
|
+
function parseSavedSearchId(raw) {
|
|
42387
|
+
const id = raw.trim();
|
|
42388
|
+
if (id === "") {
|
|
42389
|
+
throw new Error("--saved-search is required");
|
|
42390
|
+
}
|
|
42391
|
+
if (id.length > 64 || !SAVED_SEARCH_ID_RE.test(id)) {
|
|
42392
|
+
throw new Error(
|
|
42393
|
+
`invalid --saved-search "${id}" (expected a numeric internal id, optionally negative, or a customsearch* script id)`
|
|
42394
|
+
);
|
|
42395
|
+
}
|
|
42396
|
+
return id;
|
|
42397
|
+
}
|
|
42398
|
+
async function writeSqlFile(path12, sql) {
|
|
42399
|
+
const existing = await lstat(path12).catch((err) => {
|
|
42400
|
+
if (err.code === "ENOENT") return void 0;
|
|
42401
|
+
throw err;
|
|
42402
|
+
});
|
|
42403
|
+
if (existing !== void 0 && !existing.isFile()) {
|
|
42404
|
+
throw new Error(
|
|
42405
|
+
existing.isSymbolicLink() ? "it is a symlink" : "it exists and is not a regular file"
|
|
42406
|
+
);
|
|
42407
|
+
}
|
|
42408
|
+
const flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | (fsConstants.O_NOFOLLOW ?? 0);
|
|
42409
|
+
const handle = await open2(path12, flags, 384);
|
|
42410
|
+
try {
|
|
42411
|
+
const st = await handle.stat();
|
|
42412
|
+
if (!st.isFile() || st.nlink !== 1) throw new Error("it is hard-linked or not a regular file");
|
|
42413
|
+
if (existing !== void 0 && (st.ino !== existing.ino || st.dev !== existing.dev)) {
|
|
42414
|
+
throw new Error("it changed while being opened");
|
|
42415
|
+
}
|
|
42416
|
+
await handle.chmod(384);
|
|
42417
|
+
await handle.truncate(0);
|
|
42418
|
+
await handle.writeFile(sql);
|
|
42419
|
+
} finally {
|
|
42420
|
+
await handle.close();
|
|
42421
|
+
}
|
|
42422
|
+
}
|
|
42423
|
+
async function probeNetSuiteReadiness(cfg) {
|
|
42424
|
+
const res = await netsuiteBundleReadiness(cfg.client, cfg.id, cfg.dryRun, {
|
|
42425
|
+
signal: cfg.signal
|
|
42426
|
+
});
|
|
42427
|
+
if (!isSuccess(res.status)) {
|
|
42428
|
+
throw new Error(describeNetSuiteFailure("bundle readiness", cfg.id, res));
|
|
42429
|
+
}
|
|
42430
|
+
const body = decodeShape(res.body);
|
|
42431
|
+
if (body === null || typeof body !== "object" || typeof body.outcome !== "string") {
|
|
42432
|
+
throw new Error(
|
|
42433
|
+
`bundle readiness: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
42434
|
+
);
|
|
42435
|
+
}
|
|
42436
|
+
const detail = typeof body.detail === "string" ? body.detail : "";
|
|
42437
|
+
if (cfg.asJson) {
|
|
42438
|
+
writeJSONIndented(cfg.out, body);
|
|
42439
|
+
} else {
|
|
42440
|
+
const version = typeof body.bundle_version === "number" ? String(body.bundle_version) : "none";
|
|
42441
|
+
cfg.out.write(`NetSuite bundle readiness for integration ${cfg.id}: ${body.outcome}
|
|
42442
|
+
`);
|
|
42443
|
+
cfg.out.write(` Detail: ${orDash2(detail)}
|
|
42444
|
+
`);
|
|
42445
|
+
cfg.out.write(` Bundle version: ${version}
|
|
42446
|
+
`);
|
|
42447
|
+
if (body.dry_run === true) {
|
|
42448
|
+
cfg.out.write(" Dry run: nothing was stamped\n");
|
|
42449
|
+
}
|
|
42450
|
+
}
|
|
42451
|
+
if (!cfg.dryRun && body.outcome !== "CONFIRMED") {
|
|
42452
|
+
throw new Error(
|
|
42453
|
+
`readiness probe did not confirm (${body.outcome}): ${orDash2(detail)}; nothing was stamped`
|
|
42454
|
+
);
|
|
42455
|
+
}
|
|
42456
|
+
}
|
|
42457
|
+
async function runIntegrationsNetSuiteReadiness(cfg) {
|
|
42458
|
+
if (cfg.id <= 0) {
|
|
42459
|
+
throw new Error("integration id must be positive");
|
|
42460
|
+
}
|
|
42461
|
+
if (cfg.dryRun) {
|
|
42462
|
+
if (!isPlanPhase(cfg.token)) {
|
|
42463
|
+
throw new Error("--confirm-token does not apply to --dry-run, which writes nothing");
|
|
42464
|
+
}
|
|
42465
|
+
await probeNetSuiteReadiness(cfg);
|
|
42466
|
+
return;
|
|
42467
|
+
}
|
|
42468
|
+
const action = netsuiteStampAction(cfg.id);
|
|
42469
|
+
await preflightConfirmToken(action, cfg.token);
|
|
42470
|
+
await twoStepConfirm({
|
|
42471
|
+
out: cfg.out,
|
|
42472
|
+
action,
|
|
42473
|
+
token: cfg.token,
|
|
42474
|
+
server: cfg.serverFlag,
|
|
42475
|
+
confirmCmd: `dr integrations netsuite readiness ${cfg.id} --server ${cfg.serverFlag}`,
|
|
42476
|
+
describe: async () => {
|
|
42477
|
+
const i = decodeShape(
|
|
42478
|
+
await getById2(cfg.client, cfg.id, { signal: cfg.signal })
|
|
42479
|
+
);
|
|
42480
|
+
return [
|
|
42481
|
+
`name: "${i.name}"`,
|
|
42482
|
+
`type: ${integrationTypeName(i)}`,
|
|
42483
|
+
`org: ${orDash2(i.organization_name)} (id=${i.organization})`,
|
|
42484
|
+
"probe: one live SuiteQL call that checks the bundle role and permissions",
|
|
42485
|
+
"effect: on CONFIRMED, stamps bundle_version 3 (the 3.0 bundle) on the connection config",
|
|
42486
|
+
"preview without writing: re-run with --dry-run"
|
|
42487
|
+
];
|
|
42488
|
+
},
|
|
42489
|
+
execute: () => probeNetSuiteReadiness(cfg)
|
|
42490
|
+
});
|
|
42491
|
+
}
|
|
42492
|
+
async function runIntegrationsNetSuiteSavedSearchSql(cfg) {
|
|
42493
|
+
if (cfg.id <= 0) {
|
|
42494
|
+
throw new Error("integration id must be positive");
|
|
42495
|
+
}
|
|
42496
|
+
const savedSearchId = parseSavedSearchId(cfg.savedSearchId);
|
|
42497
|
+
const res = await netsuiteSavedSearchSql(cfg.client, cfg.id, savedSearchId, {
|
|
42498
|
+
signal: cfg.signal
|
|
42499
|
+
});
|
|
42500
|
+
if (!isSuccess(res.status)) {
|
|
42501
|
+
throw new Error(describeNetSuiteFailure("saved-search SQL", cfg.id, res));
|
|
42502
|
+
}
|
|
42503
|
+
const body = decodeShape(res.body);
|
|
42504
|
+
if (body === null || typeof body !== "object" || typeof body.sql !== "string") {
|
|
42505
|
+
throw new Error(
|
|
42506
|
+
`saved-search SQL: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
42507
|
+
);
|
|
42508
|
+
}
|
|
42509
|
+
const sql = body.sql;
|
|
42510
|
+
const outPath = cfg.outPath !== void 0 && cfg.outPath !== "" ? cfg.outPath : void 0;
|
|
42511
|
+
let writeError;
|
|
42512
|
+
if (outPath !== void 0) {
|
|
42513
|
+
try {
|
|
42514
|
+
await writeSqlFile(outPath, sql);
|
|
42515
|
+
} catch (err) {
|
|
42516
|
+
writeError = err instanceof Error ? err.message : String(err);
|
|
42517
|
+
}
|
|
42518
|
+
}
|
|
42519
|
+
const written = outPath !== void 0 && writeError === void 0;
|
|
42520
|
+
if (cfg.asJson) {
|
|
42521
|
+
writeJSONIndented(cfg.out, body);
|
|
42522
|
+
} else {
|
|
42523
|
+
const name = typeof body.saved_search_name === "string" && body.saved_search_name !== "" ? body.saved_search_name : "(not listed by the bundle)";
|
|
42524
|
+
const key = typeof body.source_query_key === "string" ? body.source_query_key : "";
|
|
42525
|
+
cfg.out.write(`Saved search ${savedSearchId}: ${name}
|
|
42526
|
+
`);
|
|
42527
|
+
cfg.out.write(` Library match: ${key || "none \u2014 custom report"}
|
|
42528
|
+
`);
|
|
42529
|
+
if (written) {
|
|
42530
|
+
cfg.out.write(` SQL: written to ${outPath} (${sql.length} chars)
|
|
42531
|
+
`);
|
|
42532
|
+
} else {
|
|
42533
|
+
cfg.out.write(" SQL:\n");
|
|
42534
|
+
cfg.out.write(sql.endsWith("\n") ? sql : `${sql}
|
|
42535
|
+
`);
|
|
42536
|
+
}
|
|
42537
|
+
}
|
|
42538
|
+
if (writeError !== void 0) {
|
|
42539
|
+
throw new Error(`could not write --out ${outPath}: ${writeError} (the SQL is printed above)`);
|
|
42540
|
+
}
|
|
42541
|
+
}
|
|
41672
42542
|
async function runIntegrationsITList(cfg) {
|
|
41673
42543
|
if (cfg.page < 1) {
|
|
41674
42544
|
throw new Error("--page must be >= 1");
|
|
@@ -42257,6 +43127,80 @@ function registerIntegrationsCommand(root) {
|
|
|
42257
43127
|
addAccountFlag(shareGroupsCmd);
|
|
42258
43128
|
registerITCommands(integrations);
|
|
42259
43129
|
registerSftpCommands(integrations);
|
|
43130
|
+
registerNetSuiteCommands(integrations);
|
|
43131
|
+
}
|
|
43132
|
+
function registerNetSuiteCommands(parent) {
|
|
43133
|
+
const netsuite = parent.command("netsuite").description("NetSuite 3.0 -> SuiteQL in-place migration helpers");
|
|
43134
|
+
const readinessCmd = netsuite.command("readiness").description(
|
|
43135
|
+
'Probe whether a NetSuite 3.0 connection is ready for SuiteQL (one live SuiteQL call). On CONFIRMED it stamps bundle_version 3 on the connection (two-step confirmation); --dry-run probes without writing. Any other outcome stamps nothing and, without --dry-run, exits non-zero. 409 (refused) causes, from the server: "Connection is SuiteQL-native or has no NetSuite 3.0 filebox", "connection account id is not a valid NetSuite account id"; missing credentials are the NOT_CONNECTED outcome, not a 409.'
|
|
43136
|
+
).argument("<integration_id>", "NetSuite connection (data integration) id").option("--dry-run", "Probe only: never stamp the connection, no confirmation needed", false).option(
|
|
43137
|
+
"--confirm-token <token>",
|
|
43138
|
+
"Token from a prior plan run - required to actually stamp",
|
|
43139
|
+
""
|
|
43140
|
+
).option("--json", "Output the raw response as JSON", false).action(
|
|
43141
|
+
async (idStr, opts) => {
|
|
43142
|
+
const id = parseIntegrationIdArg(idStr);
|
|
43143
|
+
const dryRun = opts.dryRun === true;
|
|
43144
|
+
if (dryRun && !isPlanPhase(opts.confirmToken)) {
|
|
43145
|
+
throw new Error("--confirm-token does not apply to --dry-run, which writes nothing");
|
|
43146
|
+
}
|
|
43147
|
+
if (!dryRun) {
|
|
43148
|
+
await preflightConfirmToken(netsuiteStampAction(id), opts.confirmToken);
|
|
43149
|
+
}
|
|
43150
|
+
const serverKey = resolveServer(opts.server);
|
|
43151
|
+
await withSetup(
|
|
43152
|
+
opts.server,
|
|
43153
|
+
opts.account,
|
|
43154
|
+
async ({ client, signal }) => {
|
|
43155
|
+
await runIntegrationsNetSuiteReadiness({
|
|
43156
|
+
client,
|
|
43157
|
+
id,
|
|
43158
|
+
dryRun,
|
|
43159
|
+
token: opts.confirmToken,
|
|
43160
|
+
serverFlag: serverKey,
|
|
43161
|
+
asJson: opts.json === true,
|
|
43162
|
+
signal,
|
|
43163
|
+
out: process.stdout
|
|
43164
|
+
});
|
|
43165
|
+
},
|
|
43166
|
+
NETSUITE_CALL_TIMEOUT_MS
|
|
43167
|
+
);
|
|
43168
|
+
}
|
|
43169
|
+
);
|
|
43170
|
+
addServerFlag(readinessCmd);
|
|
43171
|
+
addAccountFlag(readinessCmd);
|
|
43172
|
+
const savedSearchSqlCmd = netsuite.command("saved-search-sql").description(
|
|
43173
|
+
`Show a saved search's SQL as translated by the connection's 3.0 bundle, its name, and the SuiteQL library entry it matches (none: create a custom report instead). Read-only. 409 (refused) causes, from the server: "Connection is SuiteQL-native or has no NetSuite 3.0 filebox", "connection account id is not a valid NetSuite account id", "connection has no credentials: <fields>". 502: the bundle call failed, or the SQL is over 100,000 characters.`
|
|
43174
|
+
).argument("<integration_id>", "NetSuite connection (data integration) id").requiredOption(
|
|
43175
|
+
"--saved-search <id>",
|
|
43176
|
+
"NetSuite saved search id: a numeric internal id (negative for standard searches) or a customsearch* script id (required)"
|
|
43177
|
+
).option(
|
|
43178
|
+
"--out <path>",
|
|
43179
|
+
"Also write the decoded SQL to this file, owner-only (0600); a regular file is overwritten, a symlink or hard-linked file is refused. If the write fails the SQL is printed and the command exits non-zero (e.g. for filebox custom-reports create --query-file)"
|
|
43180
|
+
).option("--json", "Output the raw response as JSON", false).action(
|
|
43181
|
+
async (idStr, opts) => {
|
|
43182
|
+
const id = parseIntegrationIdArg(idStr);
|
|
43183
|
+
parseSavedSearchId(opts.savedSearch);
|
|
43184
|
+
await withSetup(
|
|
43185
|
+
opts.server,
|
|
43186
|
+
opts.account,
|
|
43187
|
+
async ({ client, signal }) => {
|
|
43188
|
+
await runIntegrationsNetSuiteSavedSearchSql({
|
|
43189
|
+
client,
|
|
43190
|
+
id,
|
|
43191
|
+
savedSearchId: opts.savedSearch,
|
|
43192
|
+
outPath: opts.out,
|
|
43193
|
+
asJson: opts.json === true,
|
|
43194
|
+
signal,
|
|
43195
|
+
out: process.stdout
|
|
43196
|
+
});
|
|
43197
|
+
},
|
|
43198
|
+
NETSUITE_CALL_TIMEOUT_MS
|
|
43199
|
+
);
|
|
43200
|
+
}
|
|
43201
|
+
);
|
|
43202
|
+
addServerFlag(savedSearchSqlCmd);
|
|
43203
|
+
addAccountFlag(savedSearchSqlCmd);
|
|
42260
43204
|
}
|
|
42261
43205
|
function registerSftpCommands(parent) {
|
|
42262
43206
|
const sftp = parent.command("sftp").description("Self-serve SFTP fileboxes (the embedded SFTP integration)");
|
|
@@ -42899,7 +43843,7 @@ function registerDevFetch(connectors) {
|
|
|
42899
43843
|
|
|
42900
43844
|
// src/commands/connectorsPoll.ts
|
|
42901
43845
|
import * as readline2 from "readline";
|
|
42902
|
-
import { createHash as
|
|
43846
|
+
import { createHash as createHash11 } from "crypto";
|
|
42903
43847
|
|
|
42904
43848
|
// src/types/connectors.ts
|
|
42905
43849
|
var POLL_ITEM_STATES = [
|
|
@@ -43195,6 +44139,9 @@ async function ask(cfg, message, choices, defaultChoice) {
|
|
|
43195
44139
|
}
|
|
43196
44140
|
var WIDEN_HELP = "seed = mark the newly exposed items as already landed, land = land them all";
|
|
43197
44141
|
var POLICY_HELP = "rekey = re-key the ledger from a fresh listing (nothing lands again), seed = mark everything in the window as landed, land = land everything in the window";
|
|
44142
|
+
function pollSetConfirmVerb(widen, onPolicyChange) {
|
|
44143
|
+
return widen === "land" || onPolicyChange === "land" ? "set-land" : "set-seed";
|
|
44144
|
+
}
|
|
43198
44145
|
async function runPollSet(cfg) {
|
|
43199
44146
|
if (Object.keys(cfg.changes).length === 0) {
|
|
43200
44147
|
throw new Error(
|
|
@@ -43251,7 +44198,12 @@ async function runPollSet(cfg) {
|
|
|
43251
44198
|
cfg.deadline?.pause();
|
|
43252
44199
|
await twoStepConfirm({
|
|
43253
44200
|
out: cfg.out,
|
|
43254
|
-
action: pollConfirmAction(
|
|
44201
|
+
action: pollConfirmAction(
|
|
44202
|
+
cfg.filebox,
|
|
44203
|
+
pollSetConfirmVerb(body.widen, body.on_policy_change),
|
|
44204
|
+
cfg.scope,
|
|
44205
|
+
{ ...body }
|
|
44206
|
+
),
|
|
43255
44207
|
token: cfg.token,
|
|
43256
44208
|
server: cfg.scope.serverKey,
|
|
43257
44209
|
confirmCmd: `dr connectors poll set ${cfg.filebox}${cfg.settingsArgs}${flags}${cfg.targetArgs}`,
|
|
@@ -43269,7 +44221,7 @@ function confirmDigest(scope, payload) {
|
|
|
43269
44221
|
controlPlane: scope.controlPlane,
|
|
43270
44222
|
payload: Object.fromEntries(Object.entries(payload).sort(([a], [b]) => a.localeCompare(b)))
|
|
43271
44223
|
});
|
|
43272
|
-
return
|
|
44224
|
+
return createHash11("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
43273
44225
|
}
|
|
43274
44226
|
function pollConfirmAction(filebox, verb, scope, payload = {}) {
|
|
43275
44227
|
return {
|
|
@@ -43602,8 +44554,21 @@ function ttyChoicePrompt(message, choices, defaultChoice, io = { input: process.
|
|
|
43602
44554
|
askOnce();
|
|
43603
44555
|
});
|
|
43604
44556
|
}
|
|
44557
|
+
var POLL_CONFIRM_HELP = [
|
|
44558
|
+
"Confirmation:",
|
|
44559
|
+
" seed, skip and set --on-policy-change seed print a plan and a --confirm-token;",
|
|
44560
|
+
" re-run the printed command, token included, to apply it.",
|
|
44561
|
+
" No dialog opens on production, so an agent or script can run a cut-over unattended:",
|
|
44562
|
+
" dr connectors poll seed <filebox> --until <cut-over> --server <prod>",
|
|
44563
|
+
" dr connectors poll seed <filebox> --until <cut-over> --server <prod> --confirm-token <token>",
|
|
44564
|
+
" (seed --dry-run counts the already-discovered items a seed would mark.)",
|
|
44565
|
+
" A write with any answer that lands files (set --widen land, set --on-policy-change land,",
|
|
44566
|
+
" resume --source land) also asks a person to type a word in a dialog on production,",
|
|
44567
|
+
" even when the same set also seeds (--on-policy-change seed --widen land)."
|
|
44568
|
+
].join("\n");
|
|
43605
44569
|
function registerConnectorsPoll(connectors) {
|
|
43606
|
-
const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version")
|
|
44570
|
+
const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version").addHelpText("after", `
|
|
44571
|
+
${POLL_CONFIRM_HELP}`);
|
|
43607
44572
|
const enableCmd = addSettingsFlags(
|
|
43608
44573
|
poll2.command("enable").description("Poll a filebox's binding (dated with the filebox's DATE tag)").argument("<filebox>", "filebox id or uuid")
|
|
43609
44574
|
).action(async (filebox, opts) => {
|
|
@@ -43629,14 +44594,15 @@ function registerConnectorsPoll(connectors) {
|
|
|
43629
44594
|
).option(
|
|
43630
44595
|
"--widen <seed|land>",
|
|
43631
44596
|
"a wider window: seed (mark exposed items landed) or land them"
|
|
43632
|
-
).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land")
|
|
44597
|
+
).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land").addHelpText("after", `
|
|
44598
|
+
${POLL_CONFIRM_HELP}`)
|
|
43633
44599
|
).action(
|
|
43634
44600
|
async (filebox, opts) => {
|
|
43635
44601
|
const changes = parseSettings(opts);
|
|
43636
44602
|
const widen = opts.widen === void 0 ? void 0 : parseChoice(opts.widen, "--widen", WIDEN_CHOICES);
|
|
43637
44603
|
const onPolicyChange = opts.onPolicyChange === void 0 ? void 0 : parseChoice(opts.onPolicyChange, "--on-policy-change", POLICY_CHOICES);
|
|
43638
44604
|
await preflightConfirmToken(
|
|
43639
|
-
pollConfirmAction(filebox,
|
|
44605
|
+
pollConfirmAction(filebox, pollSetConfirmVerb(widen, onPolicyChange), scopeOf(opts), {
|
|
43640
44606
|
...changes,
|
|
43641
44607
|
...widen !== void 0 ? { widen } : {},
|
|
43642
44608
|
...onPolicyChange !== void 0 ? { on_policy_change: onPolicyChange } : {}
|
|
@@ -43811,14 +44777,17 @@ function registerConnectorsPoll(connectors) {
|
|
|
43811
44777
|
});
|
|
43812
44778
|
addCommonFlags(skipCmd, "Output the item as JSON");
|
|
43813
44779
|
const seedCmd = addConfirmTokenFlag(
|
|
43814
|
-
poll2.command("seed").description(
|
|
44780
|
+
poll2.command("seed").description(
|
|
44781
|
+
"Mark items modified at or before --until as already landed (cut-over; token, no dialog)"
|
|
44782
|
+
).argument("<filebox>", "filebox id or uuid").requiredOption(
|
|
43815
44783
|
"--until <timestamp>",
|
|
43816
44784
|
"date or timestamp with a zone, e.g. 2026-10-08T17:30:00Z"
|
|
43817
44785
|
).option(
|
|
43818
44786
|
"--dry-run",
|
|
43819
44787
|
"count the already-discovered items it would mark, without changing anything",
|
|
43820
44788
|
false
|
|
43821
|
-
)
|
|
44789
|
+
).addHelpText("after", `
|
|
44790
|
+
${POLL_CONFIRM_HELP}`)
|
|
43822
44791
|
).action(
|
|
43823
44792
|
async (filebox, opts) => {
|
|
43824
44793
|
const until = parseTimestampFlag(opts.until, "--until");
|
|
@@ -46256,7 +47225,7 @@ function registerWidgetsCommand(root) {
|
|
|
46256
47225
|
}
|
|
46257
47226
|
|
|
46258
47227
|
// src/commands/reconciliation.ts
|
|
46259
|
-
import { createHash as
|
|
47228
|
+
import { createHash as createHash12 } from "crypto";
|
|
46260
47229
|
import { promises as fs18 } from "fs";
|
|
46261
47230
|
|
|
46262
47231
|
// src/api/reconciliation.ts
|
|
@@ -46785,7 +47754,7 @@ function wireReconciliationProcesses(parent) {
|
|
|
46785
47754
|
}
|
|
46786
47755
|
|
|
46787
47756
|
// src/commands/reconciliation_evidences.ts
|
|
46788
|
-
import { readFile as
|
|
47757
|
+
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
46789
47758
|
import { basename as basename6 } from "path";
|
|
46790
47759
|
|
|
46791
47760
|
// src/commands/_processResolver.ts
|
|
@@ -46912,7 +47881,7 @@ async function runReconciliationEvidencesCreate(cfg) {
|
|
|
46912
47881
|
}
|
|
46913
47882
|
let buf;
|
|
46914
47883
|
try {
|
|
46915
|
-
buf = await
|
|
47884
|
+
buf = await readFile6(cfg.filePath);
|
|
46916
47885
|
} catch (err) {
|
|
46917
47886
|
const m = err instanceof Error ? err.message : String(err);
|
|
46918
47887
|
throw new Error(`read --file: ${m}`);
|
|
@@ -47837,7 +48806,7 @@ function chunkByJsonBytes(rows, maxBytes) {
|
|
|
47837
48806
|
return chunks;
|
|
47838
48807
|
}
|
|
47839
48808
|
function settingsActionId(settings) {
|
|
47840
|
-
return
|
|
48809
|
+
return createHash12("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 12);
|
|
47841
48810
|
}
|
|
47842
48811
|
async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
47843
48812
|
const action = {
|
|
@@ -48215,7 +49184,7 @@ async function runReconciliationAccountsSetAssignee(cfg) {
|
|
|
48215
49184
|
var ASSIGNEE_BATCH_CHUNK_SIZE = 100;
|
|
48216
49185
|
function assigneeBatchActionId(date, rows) {
|
|
48217
49186
|
const canonical = rows.map((r) => [r.accountId, r.type, r.userId, r.userName, r.userEmail]);
|
|
48218
|
-
return
|
|
49187
|
+
return createHash12("sha256").update(JSON.stringify([date, canonical])).digest("hex").slice(0, 12);
|
|
48219
49188
|
}
|
|
48220
49189
|
async function runReconciliationAccountsSetAssigneeBatch(cfg) {
|
|
48221
49190
|
const action = {
|
|
@@ -48369,7 +49338,7 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
48369
49338
|
}
|
|
48370
49339
|
function clearBatchActionId(date, rows) {
|
|
48371
49340
|
const canonical = rows.map((r) => [r.accountId, r.type]);
|
|
48372
|
-
return
|
|
49341
|
+
return createHash12("sha256").update(JSON.stringify(["clear", date, canonical])).digest("hex").slice(0, 12);
|
|
48373
49342
|
}
|
|
48374
49343
|
async function runReconciliationAccountsClearAssigneeBatch(cfg) {
|
|
48375
49344
|
const action = {
|
|
@@ -49692,7 +50661,7 @@ function applyProse(data4, overlay, warn2 = () => {
|
|
|
49692
50661
|
}
|
|
49693
50662
|
|
|
49694
50663
|
// src/commands/_validateReportRender.ts
|
|
49695
|
-
import { readFile as
|
|
50664
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
49696
50665
|
import { join as join13 } from "path";
|
|
49697
50666
|
var MARKER = "<!--__DATA_BLOCK__-->";
|
|
49698
50667
|
function renderReportHtml(data4, templateHtml) {
|
|
@@ -49707,7 +50676,7 @@ function reportTemplatePath() {
|
|
|
49707
50676
|
return join13(findSkillDir("dr-validate"), "report-template.html");
|
|
49708
50677
|
}
|
|
49709
50678
|
async function readReportTemplate() {
|
|
49710
|
-
return
|
|
50679
|
+
return readFile7(reportTemplatePath(), "utf8");
|
|
49711
50680
|
}
|
|
49712
50681
|
|
|
49713
50682
|
// src/commands/validate.ts
|
|
@@ -50259,7 +51228,7 @@ function renderForError(v) {
|
|
|
50259
51228
|
}
|
|
50260
51229
|
|
|
50261
51230
|
// src/commands/mecWorkflow.ts
|
|
50262
|
-
import { createHash as
|
|
51231
|
+
import { createHash as createHash13 } from "crypto";
|
|
50263
51232
|
|
|
50264
51233
|
// src/api/workflow.ts
|
|
50265
51234
|
function buildQueryString(params) {
|
|
@@ -52351,13 +53320,13 @@ async function runMecAccountsTaskStatuses(cfg) {
|
|
|
52351
53320
|
}
|
|
52352
53321
|
renderMecAccountsTasksOrStatuses(cfg.out, resp, "statuses");
|
|
52353
53322
|
}
|
|
52354
|
-
function
|
|
52355
|
-
return
|
|
53323
|
+
function shortHash5(input) {
|
|
53324
|
+
return createHash13("sha256").update(input).digest("hex").slice(0, 12);
|
|
52356
53325
|
}
|
|
52357
53326
|
async function runMecWorkflowCreate(cfg) {
|
|
52358
53327
|
const action = {
|
|
52359
53328
|
resource: "mec-workflow-template",
|
|
52360
|
-
id:
|
|
53329
|
+
id: shortHash5(JSON.stringify(cfg.payload)),
|
|
52361
53330
|
verb: "create"
|
|
52362
53331
|
};
|
|
52363
53332
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53004,7 +53973,7 @@ async function runMecTaskDelete(cfg) {
|
|
|
53004
53973
|
const sortedIds = [...cfg.ids].map((r) => r.id).sort((a, b) => a - b);
|
|
53005
53974
|
const action = {
|
|
53006
53975
|
resource: "mec-workflow-task-batch",
|
|
53007
|
-
id: `${cfg.stepId}:${
|
|
53976
|
+
id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
|
|
53008
53977
|
verb: "bulk-delete"
|
|
53009
53978
|
};
|
|
53010
53979
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53167,7 +54136,7 @@ async function runMecTaskSignOff(cfg) {
|
|
|
53167
54136
|
const sortedIds = [...cfg.ids].sort((a, b) => a - b);
|
|
53168
54137
|
const action = {
|
|
53169
54138
|
resource: "mec-workflow-task",
|
|
53170
|
-
id: `${cfg.stepId}:${
|
|
54139
|
+
id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
|
|
53171
54140
|
verb: cfg.isSignOff ? "sign-off" : "reopen"
|
|
53172
54141
|
};
|
|
53173
54142
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53366,7 +54335,7 @@ async function runMecMemberGrantBulk(cfg) {
|
|
|
53366
54335
|
const hashInput = cfg.payload.emails.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
53367
54336
|
const action = {
|
|
53368
54337
|
resource: "mec-workflow-member-grant",
|
|
53369
|
-
id:
|
|
54338
|
+
id: shortHash5(hashInput),
|
|
53370
54339
|
verb: "bulk"
|
|
53371
54340
|
};
|
|
53372
54341
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53522,7 +54491,7 @@ async function runMecGroupGrantBulk(cfg) {
|
|
|
53522
54491
|
const hashInput = cfg.payload.permission_groups.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
53523
54492
|
const action = {
|
|
53524
54493
|
resource: "mec-workflow-group-grant",
|
|
53525
|
-
id:
|
|
54494
|
+
id: shortHash5(hashInput),
|
|
53526
54495
|
verb: "bulk"
|
|
53527
54496
|
};
|
|
53528
54497
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -54736,7 +55705,7 @@ function wireMec(program) {
|
|
|
54736
55705
|
}
|
|
54737
55706
|
|
|
54738
55707
|
// src/commands/workflow.ts
|
|
54739
|
-
import { createHash as
|
|
55708
|
+
import { createHash as createHash14 } from "crypto";
|
|
54740
55709
|
function dash6(s) {
|
|
54741
55710
|
if (s === null || s === void 0) return "-";
|
|
54742
55711
|
if (typeof s === "number") return String(s);
|
|
@@ -55204,13 +56173,13 @@ function renderGroupList(out, rows) {
|
|
|
55204
56173
|
(${rows.length} group${plural(rows.length)})
|
|
55205
56174
|
`);
|
|
55206
56175
|
}
|
|
55207
|
-
function
|
|
55208
|
-
return
|
|
56176
|
+
function shortHash6(input) {
|
|
56177
|
+
return createHash14("sha256").update(input).digest("hex").slice(0, 12);
|
|
55209
56178
|
}
|
|
55210
56179
|
async function runWorkflowCreate(cfg) {
|
|
55211
56180
|
const action = {
|
|
55212
56181
|
resource: "workflow-template",
|
|
55213
|
-
id:
|
|
56182
|
+
id: shortHash6(JSON.stringify(cfg.payload)),
|
|
55214
56183
|
verb: "create"
|
|
55215
56184
|
};
|
|
55216
56185
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -55852,7 +56821,7 @@ async function runTaskDelete(cfg) {
|
|
|
55852
56821
|
const sortedIds = [...cfg.ids].map((r) => r.id).sort((a, b) => a - b);
|
|
55853
56822
|
const action = {
|
|
55854
56823
|
resource: "workflow-task-batch",
|
|
55855
|
-
id: `${cfg.stepId}:${
|
|
56824
|
+
id: `${cfg.stepId}:${shortHash6(sortedIds.join(","))}`,
|
|
55856
56825
|
verb: "bulk-delete"
|
|
55857
56826
|
};
|
|
55858
56827
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -56015,7 +56984,7 @@ async function runTaskSignOff(cfg) {
|
|
|
56015
56984
|
const sortedIds = [...cfg.ids].sort((a, b) => a - b);
|
|
56016
56985
|
const action = {
|
|
56017
56986
|
resource: "workflow-task",
|
|
56018
|
-
id: `${cfg.stepId}:${
|
|
56987
|
+
id: `${cfg.stepId}:${shortHash6(sortedIds.join(","))}`,
|
|
56019
56988
|
verb: cfg.isSignOff ? "sign-off" : "reopen"
|
|
56020
56989
|
};
|
|
56021
56990
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -56212,7 +57181,7 @@ async function runMemberGrantBulk(cfg) {
|
|
|
56212
57181
|
const hashInput = cfg.payload.emails.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
56213
57182
|
const action = {
|
|
56214
57183
|
resource: "workflow-member-grant",
|
|
56215
|
-
id:
|
|
57184
|
+
id: shortHash6(hashInput),
|
|
56216
57185
|
verb: "bulk"
|
|
56217
57186
|
};
|
|
56218
57187
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -56368,7 +57337,7 @@ async function runGroupGrantBulk(cfg) {
|
|
|
56368
57337
|
const hashInput = cfg.payload.permission_groups.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
56369
57338
|
const action = {
|
|
56370
57339
|
resource: "workflow-group-grant",
|
|
56371
|
-
id:
|
|
57340
|
+
id: shortHash6(hashInput),
|
|
56372
57341
|
verb: "bulk"
|
|
56373
57342
|
};
|
|
56374
57343
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -57364,7 +58333,7 @@ function wireWorkflow(root) {
|
|
|
57364
58333
|
}
|
|
57365
58334
|
|
|
57366
58335
|
// src/commands/cash.ts
|
|
57367
|
-
import { createHash as
|
|
58336
|
+
import { createHash as createHash15 } from "crypto";
|
|
57368
58337
|
import { promises as fs25 } from "fs";
|
|
57369
58338
|
|
|
57370
58339
|
// src/api/cash.ts
|
|
@@ -58455,7 +59424,7 @@ async function runCashRulesDeleteAll(cfg) {
|
|
|
58455
59424
|
return;
|
|
58456
59425
|
}
|
|
58457
59426
|
const ids = targets.map((r) => r.id).sort((a, b) => a - b);
|
|
58458
|
-
const sha =
|
|
59427
|
+
const sha = createHash15("sha256").update(ids.join(",")).digest("hex").slice(0, 12);
|
|
58459
59428
|
const action = {
|
|
58460
59429
|
resource: "cash-rules-batch",
|
|
58461
59430
|
id: `${templateId}:${sha}`,
|
|
@@ -68173,7 +69142,7 @@ function parseDueDate(raw) {
|
|
|
68173
69142
|
}
|
|
68174
69143
|
return trimmed;
|
|
68175
69144
|
}
|
|
68176
|
-
async function readTicketDescription(inline, file,
|
|
69145
|
+
async function readTicketDescription(inline, file, readFile9 = (p) => fs31.readFile(p, "utf8")) {
|
|
68177
69146
|
if (inline !== void 0 && file !== void 0) {
|
|
68178
69147
|
throw new Error("pass --description or --description-file, not both.");
|
|
68179
69148
|
}
|
|
@@ -68181,7 +69150,7 @@ async function readTicketDescription(inline, file, readFile8 = (p) => fs31.readF
|
|
|
68181
69150
|
if (file.trim() === "") throw new Error("--description-file needs a path.");
|
|
68182
69151
|
let body;
|
|
68183
69152
|
try {
|
|
68184
|
-
body = await
|
|
69153
|
+
body = await readFile9(file);
|
|
68185
69154
|
} catch (err) {
|
|
68186
69155
|
const msg = err instanceof Error ? err.message : String(err);
|
|
68187
69156
|
throw new Error(`--description-file ${file}: ${msg}`);
|
|
@@ -69880,7 +70849,7 @@ function commandPath(actionCommand) {
|
|
|
69880
70849
|
// src/telemetry/config.ts
|
|
69881
70850
|
import { homedir as homedir6 } from "os";
|
|
69882
70851
|
import { join as join16 } from "path";
|
|
69883
|
-
import { readFile as
|
|
70852
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
69884
70853
|
var TELEMETRY_ENV = "DR_CLI_TELEMETRY";
|
|
69885
70854
|
var DISABLED_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
|
|
69886
70855
|
function isTelemetryEnabled(env = process.env) {
|
|
@@ -69918,7 +70887,7 @@ function remoteSettingsPath() {
|
|
|
69918
70887
|
async function readCollectorConfig(path12 = remoteSettingsPath()) {
|
|
69919
70888
|
let raw;
|
|
69920
70889
|
try {
|
|
69921
|
-
raw = await
|
|
70890
|
+
raw = await readFile8(path12, "utf8");
|
|
69922
70891
|
} catch {
|
|
69923
70892
|
return null;
|
|
69924
70893
|
}
|