@datarails-ai/dr-cli 1.6.130 → 1.6.131
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 +1007 -83
- 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.131",
|
|
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-14T12:32:00.887Z";
|
|
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",
|
|
@@ -5843,6 +5867,52 @@ function isBadGatewayError(err) {
|
|
|
5843
5867
|
if (/^\S+ \S+: HTTP 502(?::|\s|$)/.test(err.message)) return true;
|
|
5844
5868
|
return /\(HTTP 502\)$/.test(err.message);
|
|
5845
5869
|
}
|
|
5870
|
+
async function readRawJson(res) {
|
|
5871
|
+
const rawText = await res.text();
|
|
5872
|
+
let body = null;
|
|
5873
|
+
try {
|
|
5874
|
+
body = rawText ? JSON.parse(rawText) : null;
|
|
5875
|
+
} catch {
|
|
5876
|
+
body = null;
|
|
5877
|
+
}
|
|
5878
|
+
return { status: res.status, body, rawText };
|
|
5879
|
+
}
|
|
5880
|
+
var DETAIL_MAX_CHARS = 300;
|
|
5881
|
+
function isSuccess(status2) {
|
|
5882
|
+
return status2 >= 200 && status2 < 300;
|
|
5883
|
+
}
|
|
5884
|
+
function rawErrorDetail(res) {
|
|
5885
|
+
return truncate4(uncappedErrorDetail(res), DETAIL_MAX_CHARS);
|
|
5886
|
+
}
|
|
5887
|
+
function uncappedErrorDetail(res) {
|
|
5888
|
+
const { body } = res;
|
|
5889
|
+
if (typeof body === "string") return body;
|
|
5890
|
+
if (body !== null && typeof body === "object" && !Array.isArray(body)) {
|
|
5891
|
+
const detail = body.detail;
|
|
5892
|
+
if (typeof detail === "string") return detail;
|
|
5893
|
+
const fields = [];
|
|
5894
|
+
for (const [field, value] of Object.entries(body)) {
|
|
5895
|
+
const messages = Array.isArray(value) ? value : [value];
|
|
5896
|
+
if (messages.length > 0 && messages.every((m) => typeof m === "string")) {
|
|
5897
|
+
fields.push(`${field}: ${messages.join(" ")}`);
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
if (fields.length > 0) return fields.join("; ");
|
|
5901
|
+
}
|
|
5902
|
+
return res.rawText.trim();
|
|
5903
|
+
}
|
|
5904
|
+
var FAILURE_PHRASES = {
|
|
5905
|
+
400: "rejected",
|
|
5906
|
+
403: "refused",
|
|
5907
|
+
404: "not found, or not visible to this account",
|
|
5908
|
+
409: "refused",
|
|
5909
|
+
502: "failed upstream"
|
|
5910
|
+
};
|
|
5911
|
+
function describeRawFailure(what, res, overrides = {}) {
|
|
5912
|
+
const detail = decodeShape(rawErrorDetail(res));
|
|
5913
|
+
const phrase = overrides[res.status] ?? FAILURE_PHRASES[res.status];
|
|
5914
|
+
return phrase === void 0 ? `${what}: unexpected status ${res.status}: ${detail}` : `${what} ${phrase} (${res.status}): ${detail}`;
|
|
5915
|
+
}
|
|
5846
5916
|
|
|
5847
5917
|
// src/api/users.ts
|
|
5848
5918
|
async function list2(client, options) {
|
|
@@ -19114,12 +19184,52 @@ async function clearDataQuery(client, id, reqOptions) {
|
|
|
19114
19184
|
return client.do("DELETE", `/api/documents/${id}/data_query`, void 0, reqOptions);
|
|
19115
19185
|
}
|
|
19116
19186
|
async function syncDataQuery(client, id, body, reqOptions) {
|
|
19117
|
-
|
|
19187
|
+
return (await syncDataQueryTimed(client, id, body, reqOptions)).body;
|
|
19188
|
+
}
|
|
19189
|
+
async function syncDataQueryTimed(client, id, body, reqOptions) {
|
|
19190
|
+
const { data: data4, headers } = await client.postJSONWithHeaders(
|
|
19118
19191
|
`/api/documents/${id}/data_query_sync`,
|
|
19119
19192
|
body,
|
|
19120
19193
|
reqOptions
|
|
19121
19194
|
);
|
|
19122
|
-
|
|
19195
|
+
const serverTimeMs = Date.parse(headers.get("date") ?? "");
|
|
19196
|
+
return {
|
|
19197
|
+
body: data4 !== null && typeof data4 === "object" && !Array.isArray(data4) ? data4 : {},
|
|
19198
|
+
serverTimeMs: Number.isFinite(serverTimeMs) ? serverTimeMs : void 0
|
|
19199
|
+
};
|
|
19200
|
+
}
|
|
19201
|
+
async function listDocumentEvents(client, id, action, page, reqOptions) {
|
|
19202
|
+
return client.getJSON(
|
|
19203
|
+
`/api/documents/${id}/logs?action=${encodeURIComponent(action)}&skip=${page.skip}&limit=${page.limit}`,
|
|
19204
|
+
reqOptions
|
|
19205
|
+
);
|
|
19206
|
+
}
|
|
19207
|
+
async function listCustomReports(client, id, reqOptions) {
|
|
19208
|
+
const res = await client.do(
|
|
19209
|
+
"GET",
|
|
19210
|
+
`/api/documents/${id}/data_query_custom_reports`,
|
|
19211
|
+
void 0,
|
|
19212
|
+
reqOptions
|
|
19213
|
+
);
|
|
19214
|
+
return readRawJson(res);
|
|
19215
|
+
}
|
|
19216
|
+
async function createCustomReport(client, id, body, reqOptions) {
|
|
19217
|
+
const res = await client.do(
|
|
19218
|
+
"POST",
|
|
19219
|
+
`/api/documents/${id}/data_query_custom_reports`,
|
|
19220
|
+
JSON.stringify(body),
|
|
19221
|
+
reqOptions
|
|
19222
|
+
);
|
|
19223
|
+
return readRawJson(res);
|
|
19224
|
+
}
|
|
19225
|
+
async function applyCustomReport(client, id, reportId, reqOptions) {
|
|
19226
|
+
const res = await client.do(
|
|
19227
|
+
"POST",
|
|
19228
|
+
`/api/documents/${id}/data_query_custom_reports/${reportId}/apply`,
|
|
19229
|
+
JSON.stringify({}),
|
|
19230
|
+
reqOptions
|
|
19231
|
+
);
|
|
19232
|
+
return readRawJson(res);
|
|
19123
19233
|
}
|
|
19124
19234
|
async function getAutoCalculateSettings(client, docId, reqOptions) {
|
|
19125
19235
|
const path12 = `/api/documents/${docId}/auto_calculate_settings`;
|
|
@@ -28878,6 +28988,7 @@ import * as path5 from "path";
|
|
|
28878
28988
|
|
|
28879
28989
|
// src/api/errorEnrichments.ts
|
|
28880
28990
|
var BASE = "/api/v3/data_integrations/error-enrichments/";
|
|
28991
|
+
var TYPES_BASE = "/api/v3/data_integrations/error-enrichment-types/";
|
|
28881
28992
|
function filtersQuery(filters) {
|
|
28882
28993
|
if (filters === void 0) return "";
|
|
28883
28994
|
const params = new URLSearchParams();
|
|
@@ -28964,9 +29075,31 @@ async function testRegex(client, regex, samples, options) {
|
|
|
28964
29075
|
}
|
|
28965
29076
|
return out;
|
|
28966
29077
|
}
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
29078
|
+
async function listTypes(client, options) {
|
|
29079
|
+
const res = await client.do("GET", TYPES_BASE, void 0, options);
|
|
29080
|
+
const text2 = await res.text();
|
|
29081
|
+
if (res.status === 404) return null;
|
|
29082
|
+
if (res.status !== 200) {
|
|
29083
|
+
throw new Error(`GET ${TYPES_BASE}: HTTP ${res.status}: ${text2.trim().slice(0, 200)}`);
|
|
29084
|
+
}
|
|
29085
|
+
let parsed2;
|
|
29086
|
+
try {
|
|
29087
|
+
parsed2 = JSON.parse(text2);
|
|
29088
|
+
} catch {
|
|
29089
|
+
parsed2 = void 0;
|
|
29090
|
+
}
|
|
29091
|
+
if (!Array.isArray(parsed2)) {
|
|
29092
|
+
throw new Error(`GET ${TYPES_BASE}: unexpected response: ${text2.trim().slice(0, 200)}`);
|
|
29093
|
+
}
|
|
29094
|
+
return parsed2;
|
|
29095
|
+
}
|
|
29096
|
+
async function createType(client, type, options) {
|
|
29097
|
+
const out = await client.postJSON(TYPES_BASE, type, options);
|
|
29098
|
+
if (out === null) {
|
|
29099
|
+
throw new Error("error-enrichment-types create: empty response");
|
|
29100
|
+
}
|
|
29101
|
+
return out;
|
|
29102
|
+
}
|
|
28970
29103
|
|
|
28971
29104
|
// src/commands/errorEnrichments.ts
|
|
28972
29105
|
function writeTable22(out, rows) {
|
|
@@ -29176,7 +29309,10 @@ async function runEnrichmentsList(cfg) {
|
|
|
29176
29309
|
const results = await sweep(
|
|
29177
29310
|
cfg.servers,
|
|
29178
29311
|
cfg.makeSetup,
|
|
29179
|
-
(client, signal) =>
|
|
29312
|
+
async (client, signal) => {
|
|
29313
|
+
await requireKnownClassifications(client, cfg.filters, signal);
|
|
29314
|
+
return list19(client, cfg.filters, { signal });
|
|
29315
|
+
},
|
|
29180
29316
|
cfg.errOut
|
|
29181
29317
|
);
|
|
29182
29318
|
const single = cfg.servers.length === 1;
|
|
@@ -29204,7 +29340,10 @@ async function runEnrichmentsStats(cfg) {
|
|
|
29204
29340
|
const results = await sweep(
|
|
29205
29341
|
cfg.servers,
|
|
29206
29342
|
cfg.makeSetup,
|
|
29207
|
-
(client, signal) =>
|
|
29343
|
+
async (client, signal) => {
|
|
29344
|
+
await requireKnownClassifications(client, cfg.filters, signal);
|
|
29345
|
+
return stats(client, cfg.filters, { signal });
|
|
29346
|
+
},
|
|
29208
29347
|
cfg.errOut
|
|
29209
29348
|
);
|
|
29210
29349
|
const single = cfg.servers.length === 1;
|
|
@@ -29232,6 +29371,47 @@ async function runEnrichmentsStats(cfg) {
|
|
|
29232
29371
|
}
|
|
29233
29372
|
requireSweepSuccess(results);
|
|
29234
29373
|
}
|
|
29374
|
+
function renderTypesTable(out, types) {
|
|
29375
|
+
if (types.length === 0) {
|
|
29376
|
+
out.write("(no types)\n");
|
|
29377
|
+
return;
|
|
29378
|
+
}
|
|
29379
|
+
const rows = [{ cells: ["NAME", "DESCRIPTION"] }];
|
|
29380
|
+
for (const t of types) rows.push({ cells: [t.name, oneLine2(t.description, 80)] });
|
|
29381
|
+
writeTable22(out, rows);
|
|
29382
|
+
}
|
|
29383
|
+
async function runEnrichmentTypesList(cfg) {
|
|
29384
|
+
const results = await sweep(
|
|
29385
|
+
cfg.servers,
|
|
29386
|
+
cfg.makeSetup,
|
|
29387
|
+
async (client, signal) => {
|
|
29388
|
+
const types = await listTypes(client, { signal });
|
|
29389
|
+
if (types === null) throw new Error("this server does not serve error-enrichment types yet");
|
|
29390
|
+
return types;
|
|
29391
|
+
},
|
|
29392
|
+
cfg.errOut
|
|
29393
|
+
);
|
|
29394
|
+
const single = cfg.servers.length === 1;
|
|
29395
|
+
const only = results[0];
|
|
29396
|
+
if (cfg.asJson) {
|
|
29397
|
+
if (single) {
|
|
29398
|
+
if (only?.status === "ok") writeJSONIndented(cfg.out, only.value ?? []);
|
|
29399
|
+
} else {
|
|
29400
|
+
writeJSONIndented(cfg.out, sweepJson(results, "types"));
|
|
29401
|
+
}
|
|
29402
|
+
} else {
|
|
29403
|
+
for (const r of results) {
|
|
29404
|
+
if (!single) {
|
|
29405
|
+
const suffix = r.status === "ok" ? `${(r.value ?? []).length} type${plural((r.value ?? []).length)}` : `${r.status}: ${r.message ?? ""}`;
|
|
29406
|
+
cfg.out.write(`== ${r.server} \u2014 ${suffix} ==
|
|
29407
|
+
`);
|
|
29408
|
+
}
|
|
29409
|
+
if (r.status === "ok") renderTypesTable(cfg.out, r.value ?? []);
|
|
29410
|
+
if (!single) cfg.out.write("\n");
|
|
29411
|
+
}
|
|
29412
|
+
}
|
|
29413
|
+
requireSweepSuccess(results);
|
|
29414
|
+
}
|
|
29235
29415
|
function oneLine2(s, max) {
|
|
29236
29416
|
return truncate3(s.replace(/\s+/g, " "), max);
|
|
29237
29417
|
}
|
|
@@ -29726,6 +29906,45 @@ async function runEnrichmentsImport(cfg) {
|
|
|
29726
29906
|
}
|
|
29727
29907
|
});
|
|
29728
29908
|
}
|
|
29909
|
+
function requireTypeName(name) {
|
|
29910
|
+
if (name.trim() === "") {
|
|
29911
|
+
throw new Error("--name must not be empty");
|
|
29912
|
+
}
|
|
29913
|
+
}
|
|
29914
|
+
async function runEnrichmentTypesCreate(cfg) {
|
|
29915
|
+
requireTypeName(cfg.name);
|
|
29916
|
+
const action = {
|
|
29917
|
+
resource: "error-enrichment-type",
|
|
29918
|
+
id: cfg.name,
|
|
29919
|
+
verb: "create"
|
|
29920
|
+
};
|
|
29921
|
+
await preflightConfirmToken(action, cfg.token);
|
|
29922
|
+
const description = cfg.description === void 0 ? "" : ` --description ${shellQuote(cfg.description)}`;
|
|
29923
|
+
await twoStepConfirm({
|
|
29924
|
+
out: cfg.out,
|
|
29925
|
+
action,
|
|
29926
|
+
token: cfg.token,
|
|
29927
|
+
server: cfg.serverFlag,
|
|
29928
|
+
confirmCmd: `dr error-enrichments types create --name ${shellQuote(cfg.name)}${description} ${cmdTail(cfg.serverFlag, cfg.accountFlag, void 0)}`,
|
|
29929
|
+
describe: async () => [
|
|
29930
|
+
`type: ${cfg.name}`,
|
|
29931
|
+
...cfg.description === void 0 ? [] : [`description: ${truncate3(cfg.description, 120)}`],
|
|
29932
|
+
`adds a classification type on ${cfg.serverFlag}; this CLI has no verb to rename or remove it`
|
|
29933
|
+
],
|
|
29934
|
+
execute: async () => {
|
|
29935
|
+
const created = await createType(
|
|
29936
|
+
cfg.client,
|
|
29937
|
+
{
|
|
29938
|
+
name: cfg.name,
|
|
29939
|
+
...cfg.description === void 0 ? {} : { description: cfg.description }
|
|
29940
|
+
},
|
|
29941
|
+
{ signal: cfg.signal }
|
|
29942
|
+
);
|
|
29943
|
+
cfg.out.write(`Created error-enrichment type ${created.name} on ${cfg.serverFlag}.
|
|
29944
|
+
`);
|
|
29945
|
+
}
|
|
29946
|
+
});
|
|
29947
|
+
}
|
|
29729
29948
|
function collectArray6(value, previous) {
|
|
29730
29949
|
return [...previous, value];
|
|
29731
29950
|
}
|
|
@@ -29908,18 +30127,20 @@ function resolveUpdateTargets(idArgs, entries, rule) {
|
|
|
29908
30127
|
}
|
|
29909
30128
|
return { patches: ids.map((id) => ({ id, patch: rule })), mode: "shared" };
|
|
29910
30129
|
}
|
|
29911
|
-
function requireKnownClassifications(
|
|
29912
|
-
const
|
|
29913
|
-
|
|
29914
|
-
);
|
|
30130
|
+
async function requireKnownClassifications(client, filters, signal) {
|
|
30131
|
+
const wanted = filters.classifications ?? [];
|
|
30132
|
+
if (wanted.length === 0) return;
|
|
30133
|
+
const types = await listTypes(client, { signal });
|
|
30134
|
+
if (types === null) return;
|
|
30135
|
+
const known = new Set(types.map((t) => t.name));
|
|
30136
|
+
const unknown = wanted.filter((v) => !known.has(v));
|
|
29915
30137
|
if (unknown.length > 0) {
|
|
29916
30138
|
throw new Error(
|
|
29917
|
-
`unknown --classification ${unknown.map((v) => JSON.stringify(v)).join(", ")}. Valid: ${
|
|
30139
|
+
`unknown --classification ${unknown.map((v) => JSON.stringify(v)).join(", ")}. Valid: ${types.map((t) => t.name).join(", ")} (see dr error-enrichments types list)`
|
|
29918
30140
|
);
|
|
29919
30141
|
}
|
|
29920
30142
|
}
|
|
29921
30143
|
function filtersFromOpts(opts) {
|
|
29922
|
-
requireKnownClassifications(opts.classification);
|
|
29923
30144
|
return {
|
|
29924
30145
|
integrationTypes: opts.integrationType,
|
|
29925
30146
|
classifications: opts.classification,
|
|
@@ -29938,7 +30159,10 @@ function addRuleFieldFlags(cmd, payloadHelp) {
|
|
|
29938
30159
|
'Raw error example (repeatable). To clear all examples, use --payload-file with {"examples": []}',
|
|
29939
30160
|
collectArray6,
|
|
29940
30161
|
[]
|
|
29941
|
-
).option(
|
|
30162
|
+
).option(
|
|
30163
|
+
"--classification <c>",
|
|
30164
|
+
"Classification type name (see `dr error-enrichments types list`)"
|
|
30165
|
+
).option("--approved <bool>", "true | false");
|
|
29942
30166
|
}
|
|
29943
30167
|
function addSweepFlags(cmd) {
|
|
29944
30168
|
return cmd.option(
|
|
@@ -29953,7 +30177,12 @@ function addFilterFlags(cmd) {
|
|
|
29953
30177
|
"Filter by integration type (repeatable)",
|
|
29954
30178
|
collectArray6,
|
|
29955
30179
|
[]
|
|
29956
|
-
).option(
|
|
30180
|
+
).option(
|
|
30181
|
+
"--classification <c>",
|
|
30182
|
+
"Filter by classification type name (repeatable; see `dr error-enrichments types list`)",
|
|
30183
|
+
collectArray6,
|
|
30184
|
+
[]
|
|
30185
|
+
).option("--approved <bool>", "Filter by approved state (true | false)").option("--search <q>", "Search pattern / description / type name");
|
|
29957
30186
|
}
|
|
29958
30187
|
function defaultSweepSetup(accountFlag) {
|
|
29959
30188
|
return (serverKey) => setupCall(serverKey, accountFlag);
|
|
@@ -30204,6 +30433,49 @@ function registerErrorEnrichmentsCommand(root) {
|
|
|
30204
30433
|
});
|
|
30205
30434
|
addServerFlag(importCmd);
|
|
30206
30435
|
addAccountFlag(importCmd);
|
|
30436
|
+
const types = group.command("types").description("Manage the classification types rules can be filed under (per-server)");
|
|
30437
|
+
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(
|
|
30438
|
+
async (opts) => {
|
|
30439
|
+
const servers = resolveTargetServers({
|
|
30440
|
+
server: opts.server,
|
|
30441
|
+
allServers: opts.allServers === true,
|
|
30442
|
+
serversCsv: opts.servers,
|
|
30443
|
+
includeTest: opts.includeTest === true
|
|
30444
|
+
});
|
|
30445
|
+
await runEnrichmentTypesList({
|
|
30446
|
+
servers,
|
|
30447
|
+
makeSetup: defaultSweepSetup(normaliseAccount(opts.account)),
|
|
30448
|
+
asJson: opts.json === true,
|
|
30449
|
+
out: process.stdout,
|
|
30450
|
+
errOut: process.stderr
|
|
30451
|
+
});
|
|
30452
|
+
}
|
|
30453
|
+
);
|
|
30454
|
+
addServerFlag(typesListCmd);
|
|
30455
|
+
addAccountFlag(typesListCmd);
|
|
30456
|
+
addSweepFlags(typesListCmd);
|
|
30457
|
+
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(
|
|
30458
|
+
async (opts) => {
|
|
30459
|
+
requireTypeName(opts.name);
|
|
30460
|
+
const setup = await setupCall(opts.server, normaliseAccount(opts.account));
|
|
30461
|
+
try {
|
|
30462
|
+
await runEnrichmentTypesCreate({
|
|
30463
|
+
client: setup.client,
|
|
30464
|
+
name: opts.name,
|
|
30465
|
+
description: opts.description,
|
|
30466
|
+
token: opts.confirmToken,
|
|
30467
|
+
serverFlag: resolveServer(opts.server),
|
|
30468
|
+
accountFlag: normaliseAccount(opts.account),
|
|
30469
|
+
signal: setup.signal,
|
|
30470
|
+
out: process.stdout
|
|
30471
|
+
});
|
|
30472
|
+
} finally {
|
|
30473
|
+
setup.cleanup();
|
|
30474
|
+
}
|
|
30475
|
+
}
|
|
30476
|
+
);
|
|
30477
|
+
addServerFlag(typesCreateCmd);
|
|
30478
|
+
addAccountFlag(typesCreateCmd);
|
|
30207
30479
|
}
|
|
30208
30480
|
|
|
30209
30481
|
// src/commands/skill.ts
|
|
@@ -34143,6 +34415,8 @@ function registerDocumentsCommand(root) {
|
|
|
34143
34415
|
|
|
34144
34416
|
// src/commands/integrations.ts
|
|
34145
34417
|
import { Option as Option2 } from "commander";
|
|
34418
|
+
import { constants as fsConstants } from "fs";
|
|
34419
|
+
import { lstat, open as open2 } from "fs/promises";
|
|
34146
34420
|
|
|
34147
34421
|
// src/api/integrations.ts
|
|
34148
34422
|
async function list23(client, opts, reqOptions) {
|
|
@@ -34167,7 +34441,7 @@ async function getDataSourcesPage(client, page, reqOptions) {
|
|
|
34167
34441
|
const out = await client.getJSON(path12, reqOptions);
|
|
34168
34442
|
return out ?? { next: null, results: [] };
|
|
34169
34443
|
}
|
|
34170
|
-
async function
|
|
34444
|
+
async function listTypes2(client, search, reqOptions) {
|
|
34171
34445
|
let path12 = "/api/v3/data_integrations/types/";
|
|
34172
34446
|
const s = (search ?? "").trim();
|
|
34173
34447
|
if (s !== "") {
|
|
@@ -34272,14 +34546,25 @@ async function sftpProvision(client, fileboxId, body, reqOptions) {
|
|
|
34272
34546
|
JSON.stringify(body),
|
|
34273
34547
|
reqOptions
|
|
34274
34548
|
);
|
|
34275
|
-
|
|
34276
|
-
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34549
|
+
return readRawJson(res);
|
|
34550
|
+
}
|
|
34551
|
+
async function netsuiteBundleReadiness(client, id, dryRun, reqOptions) {
|
|
34552
|
+
const res = await client.do(
|
|
34553
|
+
"POST",
|
|
34554
|
+
`/api/v3/data_integrations/data-sources/${id}/netsuite/bundle-readiness`,
|
|
34555
|
+
JSON.stringify({ dry_run: dryRun }),
|
|
34556
|
+
reqOptions
|
|
34557
|
+
);
|
|
34558
|
+
return readRawJson(res);
|
|
34559
|
+
}
|
|
34560
|
+
async function netsuiteSavedSearchSql(client, id, savedSearchId, reqOptions) {
|
|
34561
|
+
const res = await client.do(
|
|
34562
|
+
"GET",
|
|
34563
|
+
`/api/v3/data_integrations/data-sources/${id}/netsuite/saved-search-sql?saved_search_id=${encodeURIComponent(savedSearchId)}`,
|
|
34564
|
+
void 0,
|
|
34565
|
+
reqOptions
|
|
34566
|
+
);
|
|
34567
|
+
return readRawJson(res);
|
|
34283
34568
|
}
|
|
34284
34569
|
async function shareUsers2(client, id, emails, reqOptions) {
|
|
34285
34570
|
const out = await client.postJSON(
|
|
@@ -34356,7 +34641,7 @@ async function itResync(client, id, reqOptions) {
|
|
|
34356
34641
|
// src/commands/filebox.ts
|
|
34357
34642
|
import { promises as fsPromises, existsSync as existsSync2 } from "fs";
|
|
34358
34643
|
import * as nodePath from "path";
|
|
34359
|
-
import { createHash as
|
|
34644
|
+
import { createHash as createHash10 } from "crypto";
|
|
34360
34645
|
|
|
34361
34646
|
// src/commands/filebox_admin.ts
|
|
34362
34647
|
import { promises as fs15 } from "fs";
|
|
@@ -36420,6 +36705,290 @@ function registerFileboxRescanVersionCommand(fb) {
|
|
|
36420
36705
|
addAccountFlag(cmd);
|
|
36421
36706
|
}
|
|
36422
36707
|
|
|
36708
|
+
// src/commands/filebox_customReports.ts
|
|
36709
|
+
import { createHash as createHash9 } from "crypto";
|
|
36710
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
36711
|
+
function shortHash4(input) {
|
|
36712
|
+
return createHash9("sha256").update(input).digest("hex").slice(0, 12);
|
|
36713
|
+
}
|
|
36714
|
+
function isCustomReport(v) {
|
|
36715
|
+
if (v === null || typeof v !== "object") return false;
|
|
36716
|
+
const r = v;
|
|
36717
|
+
return typeof r.id === "number" && typeof r.name === "string";
|
|
36718
|
+
}
|
|
36719
|
+
async function fetchCustomReports(client, fileboxId, signal) {
|
|
36720
|
+
const res = await listCustomReports(client, fileboxId, { signal });
|
|
36721
|
+
if (!isSuccess(res.status)) {
|
|
36722
|
+
throw new Error(describeRawFailure("list custom reports", res));
|
|
36723
|
+
}
|
|
36724
|
+
const body = decodeShape(res.body);
|
|
36725
|
+
const reports = body?.custom_reports;
|
|
36726
|
+
if (!Array.isArray(reports) || !reports.every(isCustomReport)) {
|
|
36727
|
+
throw new Error(
|
|
36728
|
+
`list custom reports: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
36729
|
+
);
|
|
36730
|
+
}
|
|
36731
|
+
return { body, reports };
|
|
36732
|
+
}
|
|
36733
|
+
function usedBy(n) {
|
|
36734
|
+
const count = typeof n === "number" ? n : 0;
|
|
36735
|
+
return `${count} filebox${count === 1 ? "" : "es"}`;
|
|
36736
|
+
}
|
|
36737
|
+
function writeLinkResult(cfg, what, res, headline) {
|
|
36738
|
+
if (!isSuccess(res.status)) {
|
|
36739
|
+
throw new Error(describeRawFailure(what, res));
|
|
36740
|
+
}
|
|
36741
|
+
const body = decodeShape(res.body);
|
|
36742
|
+
if (body === null || typeof body !== "object" || !isCustomReport(body.custom_report)) {
|
|
36743
|
+
throw new Error(
|
|
36744
|
+
`${what}: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
36745
|
+
);
|
|
36746
|
+
}
|
|
36747
|
+
if (cfg.asJson) {
|
|
36748
|
+
writeJSONIndented(cfg.out, body);
|
|
36749
|
+
return;
|
|
36750
|
+
}
|
|
36751
|
+
const report = body.custom_report;
|
|
36752
|
+
cfg.out.write(`${headline(report)} (used by ${usedBy(report.usage_count)}).
|
|
36753
|
+
`);
|
|
36754
|
+
}
|
|
36755
|
+
function describeCurrentQuery(raw) {
|
|
36756
|
+
let config = raw?.config;
|
|
36757
|
+
if (typeof config === "string") {
|
|
36758
|
+
try {
|
|
36759
|
+
config = JSON.parse(config);
|
|
36760
|
+
} catch {
|
|
36761
|
+
config = null;
|
|
36762
|
+
}
|
|
36763
|
+
}
|
|
36764
|
+
if (config === null || typeof config !== "object") return "no data query";
|
|
36765
|
+
const c = config;
|
|
36766
|
+
const type = typeof c["objectType"] === "string" && c["objectType"] !== "" ? c["objectType"] : "-";
|
|
36767
|
+
const key = c["source_query_key"];
|
|
36768
|
+
const reportId = c["custom_report_id"];
|
|
36769
|
+
const source = typeof key === "string" && key !== "" ? `library ${key}` : typeof reportId === "number" ? `custom report #${reportId}` : "custom";
|
|
36770
|
+
const query = c["query"];
|
|
36771
|
+
const length = typeof query === "string" ? `${query.length} chars` : "none";
|
|
36772
|
+
return `objectType ${type}, source ${source}, query ${length}`;
|
|
36773
|
+
}
|
|
36774
|
+
async function describeFileboxTarget(client, fileboxId, signal) {
|
|
36775
|
+
const details = decodeShape(await getDetails(client, fileboxId, { signal }));
|
|
36776
|
+
const dataQuery = decodeShape(await getDataQueryRaw(client, fileboxId, { signal }));
|
|
36777
|
+
return [`filebox: ${fileboxId} "${details.name}"`, `current: ${describeCurrentQuery(dataQuery)}`];
|
|
36778
|
+
}
|
|
36779
|
+
async function runFileboxCustomReportsList(cfg) {
|
|
36780
|
+
const { body, reports } = await fetchCustomReports(cfg.client, cfg.fileboxId, cfg.signal);
|
|
36781
|
+
if (cfg.asJson) {
|
|
36782
|
+
writeJSONIndented(cfg.out, body);
|
|
36783
|
+
return;
|
|
36784
|
+
}
|
|
36785
|
+
if (reports.length === 0) {
|
|
36786
|
+
cfg.out.write(`No custom reports on the connection of filebox ${cfg.fileboxId}.
|
|
36787
|
+
`);
|
|
36788
|
+
return;
|
|
36789
|
+
}
|
|
36790
|
+
const rows = [
|
|
36791
|
+
["ID", "NAME", "USED BY"],
|
|
36792
|
+
...reports.map((r) => [String(r.id), r.name, usedBy(r.usage_count)])
|
|
36793
|
+
];
|
|
36794
|
+
const widths = [0, 1].map((i) => Math.max(...rows.map((row) => (row[i] ?? "").length)));
|
|
36795
|
+
for (const row of rows) {
|
|
36796
|
+
const [id = "", name = "", used = ""] = row;
|
|
36797
|
+
cfg.out.write(
|
|
36798
|
+
`${id.padEnd((widths[0] ?? 0) + 2)}${name.padEnd((widths[1] ?? 0) + 2)}${used}
|
|
36799
|
+
`
|
|
36800
|
+
);
|
|
36801
|
+
}
|
|
36802
|
+
cfg.out.write(`${reports.length} custom report${plural(reports.length)}
|
|
36803
|
+
`);
|
|
36804
|
+
}
|
|
36805
|
+
function customReportCreateAction(fileboxId, name, query) {
|
|
36806
|
+
const hash = shortHash4(JSON.stringify([name.trim(), query]));
|
|
36807
|
+
return { resource: "filebox-custom-report", id: `${fileboxId}:${hash}`, verb: "create" };
|
|
36808
|
+
}
|
|
36809
|
+
async function readCustomReportQueryFile(path12) {
|
|
36810
|
+
if (path12.trim() === "") {
|
|
36811
|
+
throw new Error("--query-file is required");
|
|
36812
|
+
}
|
|
36813
|
+
const query = await readFile5(path12, "utf8");
|
|
36814
|
+
if (query.trim() === "") {
|
|
36815
|
+
throw new Error(`--query-file ${path12} is empty`);
|
|
36816
|
+
}
|
|
36817
|
+
return query;
|
|
36818
|
+
}
|
|
36819
|
+
async function runFileboxCustomReportsCreate(cfg) {
|
|
36820
|
+
const name = cfg.name.trim();
|
|
36821
|
+
if (name === "") {
|
|
36822
|
+
throw new Error("--name is required");
|
|
36823
|
+
}
|
|
36824
|
+
const action = customReportCreateAction(cfg.fileboxId, name, cfg.query);
|
|
36825
|
+
await preflightConfirmToken(action, cfg.token);
|
|
36826
|
+
const firstLine = cfg.query.trim().split("\n")[0] ?? "";
|
|
36827
|
+
await twoStepConfirm({
|
|
36828
|
+
out: cfg.out,
|
|
36829
|
+
action,
|
|
36830
|
+
token: cfg.token,
|
|
36831
|
+
server: cfg.serverFlag,
|
|
36832
|
+
confirmCmd: `dr filebox custom-reports create ${cfg.fileboxId} --name ${shellQuote(name)} --query-file ${shellQuote(cfg.queryFile)} --server ${cfg.serverFlag}`,
|
|
36833
|
+
describe: async () => [
|
|
36834
|
+
...await describeFileboxTarget(cfg.client, cfg.fileboxId, cfg.signal),
|
|
36835
|
+
`name: "${name}" (unique on the filebox's NetSuite connection)`,
|
|
36836
|
+
`query: ${cfg.query.length} chars, starting "${truncate3(firstLine, 60)}"`,
|
|
36837
|
+
"effect: creates the custom report and links this filebox to it, replacing the filebox's query"
|
|
36838
|
+
],
|
|
36839
|
+
execute: async () => {
|
|
36840
|
+
const res = await createCustomReport(
|
|
36841
|
+
cfg.client,
|
|
36842
|
+
cfg.fileboxId,
|
|
36843
|
+
{ name, query: cfg.query },
|
|
36844
|
+
{ signal: cfg.signal }
|
|
36845
|
+
);
|
|
36846
|
+
writeLinkResult(
|
|
36847
|
+
cfg,
|
|
36848
|
+
"create custom report",
|
|
36849
|
+
res,
|
|
36850
|
+
(r) => `Created custom report #${r.id} "${r.name}" and linked filebox ${cfg.fileboxId} to it`
|
|
36851
|
+
);
|
|
36852
|
+
}
|
|
36853
|
+
});
|
|
36854
|
+
}
|
|
36855
|
+
function customReportApplyAction(fileboxId, reportId, query) {
|
|
36856
|
+
return {
|
|
36857
|
+
resource: "filebox-custom-report",
|
|
36858
|
+
id: `${fileboxId}:${reportId}:${shortHash4(query)}`,
|
|
36859
|
+
verb: "apply"
|
|
36860
|
+
};
|
|
36861
|
+
}
|
|
36862
|
+
async function runFileboxCustomReportsApply(cfg) {
|
|
36863
|
+
const { reports } = await fetchCustomReports(cfg.client, cfg.fileboxId, cfg.signal);
|
|
36864
|
+
const report = reports.find((r) => r.id === cfg.reportId);
|
|
36865
|
+
if (report === void 0) {
|
|
36866
|
+
throw new Error(
|
|
36867
|
+
`custom report ${cfg.reportId} is not on the connection of filebox ${cfg.fileboxId}; see dr filebox custom-reports list ${cfg.fileboxId}`
|
|
36868
|
+
);
|
|
36869
|
+
}
|
|
36870
|
+
const query = typeof report.query === "string" ? report.query : "";
|
|
36871
|
+
const action = customReportApplyAction(cfg.fileboxId, cfg.reportId, query);
|
|
36872
|
+
await preflightConfirmToken(action, cfg.token);
|
|
36873
|
+
await twoStepConfirm({
|
|
36874
|
+
out: cfg.out,
|
|
36875
|
+
action,
|
|
36876
|
+
token: cfg.token,
|
|
36877
|
+
server: cfg.serverFlag,
|
|
36878
|
+
confirmCmd: `dr filebox custom-reports apply ${cfg.fileboxId} ${cfg.reportId} --server ${cfg.serverFlag}`,
|
|
36879
|
+
describe: async () => [
|
|
36880
|
+
...await describeFileboxTarget(cfg.client, cfg.fileboxId, cfg.signal),
|
|
36881
|
+
`report: #${report.id} "${report.name}" (used by ${usedBy(report.usage_count)})`,
|
|
36882
|
+
`sql: ${query.length} chars, sha256 ${shortHash4(query)}`,
|
|
36883
|
+
"effect: links this filebox to the report, replacing the filebox's query with the report's"
|
|
36884
|
+
],
|
|
36885
|
+
execute: async () => {
|
|
36886
|
+
const res = await applyCustomReport(cfg.client, cfg.fileboxId, cfg.reportId, {
|
|
36887
|
+
signal: cfg.signal
|
|
36888
|
+
});
|
|
36889
|
+
writeLinkResult(
|
|
36890
|
+
cfg,
|
|
36891
|
+
"apply custom report",
|
|
36892
|
+
res,
|
|
36893
|
+
(r) => `Linked filebox ${cfg.fileboxId} to custom report #${r.id} "${r.name}"`
|
|
36894
|
+
);
|
|
36895
|
+
}
|
|
36896
|
+
});
|
|
36897
|
+
}
|
|
36898
|
+
function registerFileboxCustomReportsCommands(fb) {
|
|
36899
|
+
const reports = fb.command("custom-reports").description(
|
|
36900
|
+
"SuiteQL custom reports of a filebox's NetSuite connection: named queries its fileboxes share"
|
|
36901
|
+
);
|
|
36902
|
+
const listCmd = reports.command("list").description(
|
|
36903
|
+
"List the custom reports of the filebox's NetSuite connection, with how many fileboxes use each"
|
|
36904
|
+
).argument("<filebox_id>", "filebox id (positive integer)").option("--json", "Output the raw response as JSON (decoded)", false).action(async (idStr, opts) => {
|
|
36905
|
+
const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
|
|
36906
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36907
|
+
try {
|
|
36908
|
+
await runFileboxCustomReportsList({
|
|
36909
|
+
client: setup.client,
|
|
36910
|
+
fileboxId,
|
|
36911
|
+
asJson: opts.json === true,
|
|
36912
|
+
signal: setup.signal,
|
|
36913
|
+
out: process.stdout
|
|
36914
|
+
});
|
|
36915
|
+
} finally {
|
|
36916
|
+
setup.cleanup();
|
|
36917
|
+
}
|
|
36918
|
+
});
|
|
36919
|
+
addServerFlag(listCmd);
|
|
36920
|
+
addAccountFlag(listCmd);
|
|
36921
|
+
const createCmd = reports.command("create").description(
|
|
36922
|
+
"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."
|
|
36923
|
+
).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(
|
|
36924
|
+
"--confirm-token <token>",
|
|
36925
|
+
"Token from a prior plan run - required to actually create",
|
|
36926
|
+
""
|
|
36927
|
+
).option("--json", "Output the raw response as JSON (decoded)", false).action(
|
|
36928
|
+
async (idStr, opts) => {
|
|
36929
|
+
const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
|
|
36930
|
+
if (opts.name.trim() === "") {
|
|
36931
|
+
throw new Error("--name is required");
|
|
36932
|
+
}
|
|
36933
|
+
const query = await readCustomReportQueryFile(opts.queryFile);
|
|
36934
|
+
await preflightConfirmToken(
|
|
36935
|
+
customReportCreateAction(fileboxId, opts.name, query),
|
|
36936
|
+
opts.confirmToken
|
|
36937
|
+
);
|
|
36938
|
+
const serverKey = resolveServer(opts.server);
|
|
36939
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36940
|
+
try {
|
|
36941
|
+
await runFileboxCustomReportsCreate({
|
|
36942
|
+
client: setup.client,
|
|
36943
|
+
fileboxId,
|
|
36944
|
+
name: opts.name,
|
|
36945
|
+
query,
|
|
36946
|
+
token: opts.confirmToken,
|
|
36947
|
+
serverFlag: serverKey,
|
|
36948
|
+
queryFile: opts.queryFile,
|
|
36949
|
+
asJson: opts.json === true,
|
|
36950
|
+
signal: setup.signal,
|
|
36951
|
+
out: process.stdout
|
|
36952
|
+
});
|
|
36953
|
+
} finally {
|
|
36954
|
+
setup.cleanup();
|
|
36955
|
+
}
|
|
36956
|
+
}
|
|
36957
|
+
);
|
|
36958
|
+
addServerFlag(createCmd);
|
|
36959
|
+
addAccountFlag(createCmd);
|
|
36960
|
+
const applyCmd = reports.command("apply").description(
|
|
36961
|
+
"Link the filebox to an existing custom report of its connection, replacing its query with the report's (two-step confirmation)"
|
|
36962
|
+
).argument("<filebox_id>", "filebox id (positive integer)").argument("<report_id>", "custom report id (from custom-reports list)").option(
|
|
36963
|
+
"--confirm-token <token>",
|
|
36964
|
+
"Token from a prior plan run - required to actually apply",
|
|
36965
|
+
""
|
|
36966
|
+
).option("--json", "Output the raw response as JSON (decoded)", false).action(
|
|
36967
|
+
async (idStr, reportIdStr, opts) => {
|
|
36968
|
+
const fileboxId = parsePositiveIntArg(idStr, "<filebox_id>");
|
|
36969
|
+
const reportId = parsePositiveIntArg(reportIdStr, "<report_id>");
|
|
36970
|
+
const serverKey = resolveServer(opts.server);
|
|
36971
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
36972
|
+
try {
|
|
36973
|
+
await runFileboxCustomReportsApply({
|
|
36974
|
+
client: setup.client,
|
|
36975
|
+
fileboxId,
|
|
36976
|
+
reportId,
|
|
36977
|
+
token: opts.confirmToken,
|
|
36978
|
+
serverFlag: serverKey,
|
|
36979
|
+
asJson: opts.json === true,
|
|
36980
|
+
signal: setup.signal,
|
|
36981
|
+
out: process.stdout
|
|
36982
|
+
});
|
|
36983
|
+
} finally {
|
|
36984
|
+
setup.cleanup();
|
|
36985
|
+
}
|
|
36986
|
+
}
|
|
36987
|
+
);
|
|
36988
|
+
addServerFlag(applyCmd);
|
|
36989
|
+
addAccountFlag(applyCmd);
|
|
36990
|
+
}
|
|
36991
|
+
|
|
36423
36992
|
// src/api/forecast.ts
|
|
36424
36993
|
var SCENARIO_PARAM = "Forecast";
|
|
36425
36994
|
async function listScenarios(client, filter = {}, options) {
|
|
@@ -36804,6 +37373,11 @@ var DQ_WAIT_POLL_INTERVAL_MS = 5e3;
|
|
|
36804
37373
|
var DQ_WAIT_POLL_MAX_INTERVAL_MS = 3e4;
|
|
36805
37374
|
var DQ_WAIT_DEFAULT_TIMEOUT_SEC = 1800;
|
|
36806
37375
|
var DQ_WAIT_TIMEOUT_MAX_SEC = 86400;
|
|
37376
|
+
var SYNC_EVENT_ACTION = "integration_query_status_changed";
|
|
37377
|
+
var EMPTY_SYNC_EVENT_PREFIX = "Sync completed successfully, but no new data was retrieved due to an empty response";
|
|
37378
|
+
var SYNC_EVENT_LIMIT = 50;
|
|
37379
|
+
var SYNC_EVENT_MAX_PAGES = 10;
|
|
37380
|
+
var DATE_HEADER_RESOLUTION_MS = 1e3;
|
|
36807
37381
|
var DQ_WAIT_SETUP_OVERHEAD_MS = 6e4;
|
|
36808
37382
|
function dataQuerySyncCallTimeoutMs(wait, timeoutSec) {
|
|
36809
37383
|
if (!wait) return void 0;
|
|
@@ -36838,7 +37412,7 @@ function maxId(rows) {
|
|
|
36838
37412
|
function ref(v) {
|
|
36839
37413
|
return { id: v.id, version: v.version };
|
|
36840
37414
|
}
|
|
36841
|
-
function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw) {
|
|
37415
|
+
function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw, eventsRaw = null) {
|
|
36842
37416
|
const dataQuery = asRecord(dataQueryRaw);
|
|
36843
37417
|
return {
|
|
36844
37418
|
periods: periods.map((p) => {
|
|
@@ -36849,7 +37423,8 @@ function buildDataQuerySyncBaseline(periods, versions, dataQueryRaw) {
|
|
|
36849
37423
|
};
|
|
36850
37424
|
}),
|
|
36851
37425
|
maxVersionId: maxId(versions),
|
|
36852
|
-
updateTime: stamp(dataQuery["update_time"])
|
|
37426
|
+
updateTime: stamp(dataQuery["update_time"]),
|
|
37427
|
+
emptyEventIds: Array.isArray(eventsRaw) ? new Set(emptySyncEvents(eventsRaw).map((e) => e.id)) : null
|
|
36853
37428
|
};
|
|
36854
37429
|
}
|
|
36855
37430
|
function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw) {
|
|
@@ -36882,11 +37457,12 @@ function classifyDataQuerySyncProgress(periods, baseline, versions, dataQueryRaw
|
|
|
36882
37457
|
}
|
|
36883
37458
|
async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal, preRead) {
|
|
36884
37459
|
try {
|
|
36885
|
-
const [versions, dataQuery] = await Promise.all([
|
|
37460
|
+
const [versions, dataQuery, events] = await Promise.all([
|
|
36886
37461
|
listVersions(client, fileboxId, {}, { signal }),
|
|
36887
|
-
preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal })
|
|
37462
|
+
preRead !== void 0 ? Promise.resolve(preRead.raw) : getDataQueryRaw(client, fileboxId, { signal }),
|
|
37463
|
+
readSyncEvents(client, fileboxId, signal)
|
|
36888
37464
|
]);
|
|
36889
|
-
return buildDataQuerySyncBaseline(periods, versions, dataQuery);
|
|
37465
|
+
return buildDataQuerySyncBaseline(periods, versions, dataQuery, events);
|
|
36890
37466
|
} catch (err) {
|
|
36891
37467
|
const msg = err instanceof Error ? err.message : String(err);
|
|
36892
37468
|
throw new Error(
|
|
@@ -36894,6 +37470,61 @@ async function captureDataQuerySyncBaseline(client, fileboxId, periods, signal,
|
|
|
36894
37470
|
);
|
|
36895
37471
|
}
|
|
36896
37472
|
}
|
|
37473
|
+
function emptySyncEvents(eventsRaw) {
|
|
37474
|
+
const out = [];
|
|
37475
|
+
for (const raw of eventsRaw) {
|
|
37476
|
+
const e = asRecord(raw);
|
|
37477
|
+
const id = e["_id"];
|
|
37478
|
+
const text2 = e["text"];
|
|
37479
|
+
if (typeof id !== "string" || id === "" || typeof text2 !== "string") continue;
|
|
37480
|
+
const decoded = decodeShape(text2);
|
|
37481
|
+
if (!decoded.startsWith(EMPTY_SYNC_EVENT_PREFIX)) continue;
|
|
37482
|
+
const at = decoded.lastIndexOf(" for ");
|
|
37483
|
+
const label = at >= 0 && decoded.endsWith(".") ? decoded.slice(at + " for ".length, -1) : void 0;
|
|
37484
|
+
const datetime = typeof e["datetime"] === "number" ? e["datetime"] : void 0;
|
|
37485
|
+
out.push({ id, datetime, label });
|
|
37486
|
+
}
|
|
37487
|
+
return out;
|
|
37488
|
+
}
|
|
37489
|
+
function findEmptyPeriods(periods, baseline, progress, eventsRaw, sinceMs) {
|
|
37490
|
+
const empty = /* @__PURE__ */ new Set();
|
|
37491
|
+
const known = baseline.emptyEventIds;
|
|
37492
|
+
if (known === null || sinceMs === void 0 || !Array.isArray(eventsRaw)) return empty;
|
|
37493
|
+
if (progress.error !== void 0 || progress.unmatchedNewVersions.length > 0) return empty;
|
|
37494
|
+
const fresh = emptySyncEvents(eventsRaw).filter(
|
|
37495
|
+
(e) => !known.has(e.id) && e.datetime !== void 0 && e.datetime >= sinceMs
|
|
37496
|
+
);
|
|
37497
|
+
periods.forEach((p, i) => {
|
|
37498
|
+
if (progress.periods[i]?.result !== "pending") return;
|
|
37499
|
+
const matches = fresh.some(
|
|
37500
|
+
(e) => p.eventLabel === void 0 ? periods.length === 1 : e.label === p.eventLabel || e.label === void 0 && periods.length === 1
|
|
37501
|
+
);
|
|
37502
|
+
if (matches) empty.add(i);
|
|
37503
|
+
});
|
|
37504
|
+
return empty;
|
|
37505
|
+
}
|
|
37506
|
+
async function readSyncEvents(client, fileboxId, signal, sinceMs) {
|
|
37507
|
+
const events = [];
|
|
37508
|
+
try {
|
|
37509
|
+
for (let page = 0; page < SYNC_EVENT_MAX_PAGES; page++) {
|
|
37510
|
+
const rows = await listDocumentEvents(
|
|
37511
|
+
client,
|
|
37512
|
+
fileboxId,
|
|
37513
|
+
SYNC_EVENT_ACTION,
|
|
37514
|
+
{ skip: page * SYNC_EVENT_LIMIT, limit: SYNC_EVENT_LIMIT },
|
|
37515
|
+
{ signal }
|
|
37516
|
+
);
|
|
37517
|
+
if (!Array.isArray(rows)) return null;
|
|
37518
|
+
events.push(...rows);
|
|
37519
|
+
if (sinceMs === void 0 || rows.length < SYNC_EVENT_LIMIT) break;
|
|
37520
|
+
const oldest = asRecord(rows[rows.length - 1])["datetime"];
|
|
37521
|
+
if (typeof oldest !== "number" || oldest < sinceMs) break;
|
|
37522
|
+
}
|
|
37523
|
+
} catch {
|
|
37524
|
+
return null;
|
|
37525
|
+
}
|
|
37526
|
+
return events;
|
|
37527
|
+
}
|
|
36897
37528
|
var defaultSleep6 = (ms, signal) => new Promise((resolve3, reject) => {
|
|
36898
37529
|
if (signal?.aborted) {
|
|
36899
37530
|
reject(signal.reason ?? new Error("aborted"));
|
|
@@ -36923,15 +37554,42 @@ async function waitForDataQuerySync(cfg) {
|
|
|
36923
37554
|
`
|
|
36924
37555
|
);
|
|
36925
37556
|
}
|
|
37557
|
+
const sinceMs = cfg.triggerServerTimeMs === void 0 ? void 0 : cfg.triggerServerTimeMs - DATE_HEADER_RESOLUTION_MS;
|
|
37558
|
+
let warnedEvents = false;
|
|
37559
|
+
const warnEvents = (why) => {
|
|
37560
|
+
if (warnedEvents) return;
|
|
37561
|
+
warnedEvents = true;
|
|
37562
|
+
cfg.errOut.write(
|
|
37563
|
+
`warning: ${why} \u2014 a period whose sync returns no rows cannot be confirmed and will time out
|
|
37564
|
+
`
|
|
37565
|
+
);
|
|
37566
|
+
};
|
|
37567
|
+
if (sinceMs === void 0) warnEvents("the sync response carried no Date header");
|
|
37568
|
+
if (cfg.baseline.emptyEventIds === null) warnEvents("the filebox's sync events are unreadable");
|
|
37569
|
+
const settledEmpty = /* @__PURE__ */ new Set();
|
|
36926
37570
|
let progress;
|
|
36927
37571
|
let outcome;
|
|
36928
37572
|
for (; ; ) {
|
|
36929
37573
|
try {
|
|
36930
|
-
const [versions, dataQuery] = await Promise.all([
|
|
37574
|
+
const [versions, dataQuery, events] = await Promise.all([
|
|
36931
37575
|
listVersions(cfg.client, cfg.fileboxId, {}, { signal: cfg.signal }),
|
|
36932
|
-
getDataQueryRaw(cfg.client, cfg.fileboxId, { signal: cfg.signal })
|
|
37576
|
+
getDataQueryRaw(cfg.client, cfg.fileboxId, { signal: cfg.signal }),
|
|
37577
|
+
readSyncEvents(cfg.client, cfg.fileboxId, cfg.signal, sinceMs)
|
|
36933
37578
|
]);
|
|
36934
|
-
|
|
37579
|
+
if (events === null) warnEvents("the filebox's sync events are unreadable");
|
|
37580
|
+
else if (emptySyncEvents(events).some((e) => e.datetime === void 0)) {
|
|
37581
|
+
warnEvents("a sync event carries no numeric timestamp");
|
|
37582
|
+
}
|
|
37583
|
+
const polled = classifyDataQuerySyncProgress(cfg.periods, cfg.baseline, versions, dataQuery);
|
|
37584
|
+
for (const i of findEmptyPeriods(cfg.periods, cfg.baseline, polled, events, sinceMs)) {
|
|
37585
|
+
settledEmpty.add(i);
|
|
37586
|
+
}
|
|
37587
|
+
progress = {
|
|
37588
|
+
...polled,
|
|
37589
|
+
periods: polled.periods.map(
|
|
37590
|
+
(p, i) => p.result === "pending" && settledEmpty.has(i) ? { period: p.period, result: "empty" } : p
|
|
37591
|
+
)
|
|
37592
|
+
};
|
|
36935
37593
|
} catch (err) {
|
|
36936
37594
|
if (cfg.signal.aborted) {
|
|
36937
37595
|
outcome = "cancelled";
|
|
@@ -36992,6 +37650,7 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
|
|
|
36992
37650
|
const count = (r) => progress.periods.filter((p) => p.result === r).length;
|
|
36993
37651
|
const parts = [`${count("new_version")} new version${plural(count("new_version"))}`];
|
|
36994
37652
|
parts.push(`${count("no_change")} unchanged`);
|
|
37653
|
+
if (count("empty") > 0) parts.push(`${count("empty")} empty`);
|
|
36995
37654
|
if (count("pending") > 0) parts.push(`${count("pending")} unfinished`);
|
|
36996
37655
|
const headline = {
|
|
36997
37656
|
completed: "Sync finished",
|
|
@@ -37012,6 +37671,11 @@ function renderSummary2(cfg, progress, outcome, waitedSec) {
|
|
|
37012
37671
|
} else if (p.result === "no_change") {
|
|
37013
37672
|
cfg.out.write(
|
|
37014
37673
|
` ${p.period}: unchanged \u2014 identical to version #${p.version.id} (v${p.version.version}), so no new version was written
|
|
37674
|
+
`
|
|
37675
|
+
);
|
|
37676
|
+
} else if (p.result === "empty") {
|
|
37677
|
+
cfg.out.write(
|
|
37678
|
+
` ${p.period}: empty \u2014 the server logged an empty response for this period, so nothing was written
|
|
37015
37679
|
`
|
|
37016
37680
|
);
|
|
37017
37681
|
} else if (outcome === "timed_out") {
|
|
@@ -37991,7 +38655,7 @@ function buildIncludeBatchAction(docId, include, targets) {
|
|
|
37991
38655
|
const canonical = `${docId}|${include ? "on" : "off"}|${targets.map((v) => v.id).slice().sort((a, b) => a - b).join(",")}`;
|
|
37992
38656
|
return {
|
|
37993
38657
|
resource: "filebox-version-batch",
|
|
37994
|
-
id:
|
|
38658
|
+
id: createHash10("sha256").update(canonical).digest("hex").slice(0, 12),
|
|
37995
38659
|
verb: includeStateWords(include).verb
|
|
37996
38660
|
};
|
|
37997
38661
|
}
|
|
@@ -39338,23 +40002,30 @@ function resolveDateForSubType(subType, parsed2) {
|
|
|
39338
40002
|
const { year, month0, day } = parsed2;
|
|
39339
40003
|
switch ((subType ?? "").toLowerCase()) {
|
|
39340
40004
|
case "year":
|
|
39341
|
-
return {
|
|
40005
|
+
return {
|
|
40006
|
+
value: utcDateToNoonEpoch(year, 0, 1),
|
|
40007
|
+
label: String(year),
|
|
40008
|
+
eventLabel: String(year)
|
|
40009
|
+
};
|
|
39342
40010
|
case "quarter": {
|
|
39343
40011
|
const q = Math.floor(month0 / 3) + 1;
|
|
39344
40012
|
return {
|
|
39345
40013
|
value: utcDateToNoonEpoch(year, (q - 1) * 3, 1),
|
|
39346
|
-
label: `Q${q} ${year}
|
|
40014
|
+
label: `Q${q} ${year}`,
|
|
40015
|
+
eventLabel: `Q${q} ${year}`
|
|
39347
40016
|
};
|
|
39348
40017
|
}
|
|
39349
40018
|
case "month":
|
|
39350
40019
|
return {
|
|
39351
40020
|
value: utcDateToNoonEpoch(year, month0, 1),
|
|
39352
|
-
label: `${MONTH_NAMES[month0] ?? ""} ${year}
|
|
40021
|
+
label: `${MONTH_NAMES[month0] ?? ""} ${year}`,
|
|
40022
|
+
eventLabel: `${MONTH_NAMES[month0] ?? ""} ${year}`
|
|
39353
40023
|
};
|
|
39354
40024
|
case "day":
|
|
39355
40025
|
return {
|
|
39356
40026
|
value: utcDateToNoonEpoch(year, month0, day),
|
|
39357
|
-
label: `${MONTH_NAMES_SHORT[month0] ?? ""} ${day}, ${year}
|
|
40027
|
+
label: `${MONTH_NAMES_SHORT[month0] ?? ""} ${day}, ${year}`,
|
|
40028
|
+
eventLabel: `${String(month0 + 1).padStart(2, "0")}/${String(day).padStart(2, "0")}/${year}`
|
|
39358
40029
|
};
|
|
39359
40030
|
case "week": {
|
|
39360
40031
|
const date = new Date(Date.UTC(year, month0, day));
|
|
@@ -39365,7 +40036,8 @@ function resolveDateForSubType(subType, parsed2) {
|
|
|
39365
40036
|
return {
|
|
39366
40037
|
// Noon-UTC convention, same as the other branches.
|
|
39367
40038
|
value: Math.floor(monday.getTime() / 1e3) + 12 * 60 * 60,
|
|
39368
|
-
label: `Week ${week} ${isoYear}
|
|
40039
|
+
label: `Week ${week} ${isoYear}`,
|
|
40040
|
+
eventLabel: `W${String(week).padStart(2, "0")} ${isoYear}`
|
|
39369
40041
|
};
|
|
39370
40042
|
}
|
|
39371
40043
|
default:
|
|
@@ -39418,8 +40090,8 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39418
40090
|
].map((t) => dateTags.map((tag) => resolveDateForSubType(tagSubType(tag), t).value));
|
|
39419
40091
|
for (const parsed2 of parsedDates) {
|
|
39420
40092
|
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 };
|
|
40093
|
+
const { value, label: label2, eventLabel } = resolveDateForSubType(tagSubType(tag), parsed2);
|
|
40094
|
+
return { id: tag.id, name: tag.name, value, label: label2, eventLabel };
|
|
39423
40095
|
});
|
|
39424
40096
|
dateTargets.push(
|
|
39425
40097
|
JSON.stringify(resolved.map(({ id, name, value }) => ({ id, name, value })))
|
|
@@ -39430,6 +40102,8 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39430
40102
|
periodLabels.push(label);
|
|
39431
40103
|
waitPeriods.push({
|
|
39432
40104
|
label,
|
|
40105
|
+
// The server joins a version's DATE labels in tag-id order.
|
|
40106
|
+
eventLabel: [...resolved].sort((a, b) => a.id - b.id).map((r) => r.eventLabel).join(", "),
|
|
39433
40107
|
tags: dateTags.map((tag, i) => ({
|
|
39434
40108
|
id: tag.id,
|
|
39435
40109
|
inPeriod: (stored) => samePeriodAs(tagSubType(tag), stored, resolved[i].value)
|
|
@@ -39470,9 +40144,17 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39470
40144
|
if (multipleTags.length > 0) {
|
|
39471
40145
|
body["multiple_tags"] = multipleTags;
|
|
39472
40146
|
}
|
|
39473
|
-
|
|
39474
|
-
|
|
39475
|
-
|
|
40147
|
+
let resp;
|
|
40148
|
+
let triggerServerTimeMs;
|
|
40149
|
+
if (cfg.wait === void 0) {
|
|
40150
|
+
resp = await syncDataQuery(cfg.client, cfg.id, body, { signal: cfg.signal });
|
|
40151
|
+
} else {
|
|
40152
|
+
const timed = await syncDataQueryTimed(cfg.client, cfg.id, body, {
|
|
40153
|
+
signal: cfg.signal
|
|
40154
|
+
});
|
|
40155
|
+
resp = timed.body;
|
|
40156
|
+
triggerServerTimeMs = timed.serverTimeMs;
|
|
40157
|
+
}
|
|
39476
40158
|
let detail = "OK";
|
|
39477
40159
|
if (typeof resp["details"] === "string" && resp["details"] !== "") {
|
|
39478
40160
|
detail = resp["details"];
|
|
@@ -39500,6 +40182,7 @@ async function runFileboxDataQuerySync(cfg) {
|
|
|
39500
40182
|
await waitForDataQuerySync({
|
|
39501
40183
|
client: cfg.client,
|
|
39502
40184
|
fileboxId: cfg.id,
|
|
40185
|
+
triggerServerTimeMs,
|
|
39503
40186
|
periods: waitPeriods,
|
|
39504
40187
|
baseline,
|
|
39505
40188
|
timeoutSec: cfg.wait.timeoutSec,
|
|
@@ -40814,7 +41497,7 @@ function registerFileboxCommand(root) {
|
|
|
40814
41497
|
[]
|
|
40815
41498
|
).option(
|
|
40816
41499
|
"--wait",
|
|
40817
|
-
"After queueing, wait until every --date period has finished, and say whether each wrote a new version
|
|
41500
|
+
"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
41501
|
false
|
|
40819
41502
|
).option(
|
|
40820
41503
|
"--timeout <seconds>",
|
|
@@ -40850,6 +41533,7 @@ function registerFileboxCommand(root) {
|
|
|
40850
41533
|
registerFileboxAdminCommands(fb, setupCall, addServerFlag, addAccountFlag);
|
|
40851
41534
|
registerFileboxKindCommands(fb);
|
|
40852
41535
|
registerFileboxRescanVersionCommand(fb);
|
|
41536
|
+
registerFileboxCustomReportsCommands(fb);
|
|
40853
41537
|
}
|
|
40854
41538
|
|
|
40855
41539
|
// src/commands/integrations.ts
|
|
@@ -41055,7 +41739,7 @@ function parseDataSourceIntegration(raw) {
|
|
|
41055
41739
|
return i;
|
|
41056
41740
|
}
|
|
41057
41741
|
async function runIntegrationsTypes(cfg) {
|
|
41058
|
-
const items = await
|
|
41742
|
+
const items = await listTypes2(cfg.client, cfg.search, {
|
|
41059
41743
|
signal: cfg.signal
|
|
41060
41744
|
});
|
|
41061
41745
|
if (cfg.asJson) {
|
|
@@ -41669,6 +42353,172 @@ Finish it with: dr integrations sftp provision ${id} (plus the same --date-*/--b
|
|
|
41669
42353
|
`);
|
|
41670
42354
|
writeSftpKit(cfg.out, kit);
|
|
41671
42355
|
}
|
|
42356
|
+
var NETSUITE_CALL_TIMEOUT_MS = 12e4;
|
|
42357
|
+
function netsuiteStampAction(id) {
|
|
42358
|
+
return { resource: "netsuite-bundle-version", id: String(id), verb: "stamp" };
|
|
42359
|
+
}
|
|
42360
|
+
function describeNetSuiteFailure(what, id, res) {
|
|
42361
|
+
return describeRawFailure(what, res, {
|
|
42362
|
+
404: `found no NetSuite connection ${id} visible to this account`
|
|
42363
|
+
});
|
|
42364
|
+
}
|
|
42365
|
+
var SAVED_SEARCH_ID_RE = /^(?:-?[0-9]+|customsearch[A-Za-z0-9_]*)$/;
|
|
42366
|
+
function parseSavedSearchId(raw) {
|
|
42367
|
+
const id = raw.trim();
|
|
42368
|
+
if (id === "") {
|
|
42369
|
+
throw new Error("--saved-search is required");
|
|
42370
|
+
}
|
|
42371
|
+
if (id.length > 64 || !SAVED_SEARCH_ID_RE.test(id)) {
|
|
42372
|
+
throw new Error(
|
|
42373
|
+
`invalid --saved-search "${id}" (expected a numeric internal id, optionally negative, or a customsearch* script id)`
|
|
42374
|
+
);
|
|
42375
|
+
}
|
|
42376
|
+
return id;
|
|
42377
|
+
}
|
|
42378
|
+
async function writeSqlFile(path12, sql) {
|
|
42379
|
+
const existing = await lstat(path12).catch((err) => {
|
|
42380
|
+
if (err.code === "ENOENT") return void 0;
|
|
42381
|
+
throw err;
|
|
42382
|
+
});
|
|
42383
|
+
if (existing !== void 0 && !existing.isFile()) {
|
|
42384
|
+
throw new Error(
|
|
42385
|
+
existing.isSymbolicLink() ? "it is a symlink" : "it exists and is not a regular file"
|
|
42386
|
+
);
|
|
42387
|
+
}
|
|
42388
|
+
const flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | (fsConstants.O_NOFOLLOW ?? 0);
|
|
42389
|
+
const handle = await open2(path12, flags, 384);
|
|
42390
|
+
try {
|
|
42391
|
+
const st = await handle.stat();
|
|
42392
|
+
if (!st.isFile() || st.nlink !== 1) throw new Error("it is hard-linked or not a regular file");
|
|
42393
|
+
if (existing !== void 0 && (st.ino !== existing.ino || st.dev !== existing.dev)) {
|
|
42394
|
+
throw new Error("it changed while being opened");
|
|
42395
|
+
}
|
|
42396
|
+
await handle.chmod(384);
|
|
42397
|
+
await handle.truncate(0);
|
|
42398
|
+
await handle.writeFile(sql);
|
|
42399
|
+
} finally {
|
|
42400
|
+
await handle.close();
|
|
42401
|
+
}
|
|
42402
|
+
}
|
|
42403
|
+
async function probeNetSuiteReadiness(cfg) {
|
|
42404
|
+
const res = await netsuiteBundleReadiness(cfg.client, cfg.id, cfg.dryRun, {
|
|
42405
|
+
signal: cfg.signal
|
|
42406
|
+
});
|
|
42407
|
+
if (!isSuccess(res.status)) {
|
|
42408
|
+
throw new Error(describeNetSuiteFailure("bundle readiness", cfg.id, res));
|
|
42409
|
+
}
|
|
42410
|
+
const body = decodeShape(res.body);
|
|
42411
|
+
if (body === null || typeof body !== "object" || typeof body.outcome !== "string") {
|
|
42412
|
+
throw new Error(
|
|
42413
|
+
`bundle readiness: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
42414
|
+
);
|
|
42415
|
+
}
|
|
42416
|
+
const detail = typeof body.detail === "string" ? body.detail : "";
|
|
42417
|
+
if (cfg.asJson) {
|
|
42418
|
+
writeJSONIndented(cfg.out, body);
|
|
42419
|
+
} else {
|
|
42420
|
+
const version = typeof body.bundle_version === "number" ? String(body.bundle_version) : "none";
|
|
42421
|
+
cfg.out.write(`NetSuite bundle readiness for integration ${cfg.id}: ${body.outcome}
|
|
42422
|
+
`);
|
|
42423
|
+
cfg.out.write(` Detail: ${orDash2(detail)}
|
|
42424
|
+
`);
|
|
42425
|
+
cfg.out.write(` Bundle version: ${version}
|
|
42426
|
+
`);
|
|
42427
|
+
if (body.dry_run === true) {
|
|
42428
|
+
cfg.out.write(" Dry run: nothing was stamped\n");
|
|
42429
|
+
}
|
|
42430
|
+
}
|
|
42431
|
+
if (!cfg.dryRun && body.outcome !== "CONFIRMED") {
|
|
42432
|
+
throw new Error(
|
|
42433
|
+
`readiness probe did not confirm (${body.outcome}): ${orDash2(detail)}; nothing was stamped`
|
|
42434
|
+
);
|
|
42435
|
+
}
|
|
42436
|
+
}
|
|
42437
|
+
async function runIntegrationsNetSuiteReadiness(cfg) {
|
|
42438
|
+
if (cfg.id <= 0) {
|
|
42439
|
+
throw new Error("integration id must be positive");
|
|
42440
|
+
}
|
|
42441
|
+
if (cfg.dryRun) {
|
|
42442
|
+
if (!isPlanPhase(cfg.token)) {
|
|
42443
|
+
throw new Error("--confirm-token does not apply to --dry-run, which writes nothing");
|
|
42444
|
+
}
|
|
42445
|
+
await probeNetSuiteReadiness(cfg);
|
|
42446
|
+
return;
|
|
42447
|
+
}
|
|
42448
|
+
const action = netsuiteStampAction(cfg.id);
|
|
42449
|
+
await preflightConfirmToken(action, cfg.token);
|
|
42450
|
+
await twoStepConfirm({
|
|
42451
|
+
out: cfg.out,
|
|
42452
|
+
action,
|
|
42453
|
+
token: cfg.token,
|
|
42454
|
+
server: cfg.serverFlag,
|
|
42455
|
+
confirmCmd: `dr integrations netsuite readiness ${cfg.id} --server ${cfg.serverFlag}`,
|
|
42456
|
+
describe: async () => {
|
|
42457
|
+
const i = decodeShape(
|
|
42458
|
+
await getById2(cfg.client, cfg.id, { signal: cfg.signal })
|
|
42459
|
+
);
|
|
42460
|
+
return [
|
|
42461
|
+
`name: "${i.name}"`,
|
|
42462
|
+
`type: ${integrationTypeName(i)}`,
|
|
42463
|
+
`org: ${orDash2(i.organization_name)} (id=${i.organization})`,
|
|
42464
|
+
"probe: one live SuiteQL call that checks the bundle role and permissions",
|
|
42465
|
+
"effect: on CONFIRMED, stamps bundle_version 3 (the 3.0 bundle) on the connection config",
|
|
42466
|
+
"preview without writing: re-run with --dry-run"
|
|
42467
|
+
];
|
|
42468
|
+
},
|
|
42469
|
+
execute: () => probeNetSuiteReadiness(cfg)
|
|
42470
|
+
});
|
|
42471
|
+
}
|
|
42472
|
+
async function runIntegrationsNetSuiteSavedSearchSql(cfg) {
|
|
42473
|
+
if (cfg.id <= 0) {
|
|
42474
|
+
throw new Error("integration id must be positive");
|
|
42475
|
+
}
|
|
42476
|
+
const savedSearchId = parseSavedSearchId(cfg.savedSearchId);
|
|
42477
|
+
const res = await netsuiteSavedSearchSql(cfg.client, cfg.id, savedSearchId, {
|
|
42478
|
+
signal: cfg.signal
|
|
42479
|
+
});
|
|
42480
|
+
if (!isSuccess(res.status)) {
|
|
42481
|
+
throw new Error(describeNetSuiteFailure("saved-search SQL", cfg.id, res));
|
|
42482
|
+
}
|
|
42483
|
+
const body = decodeShape(res.body);
|
|
42484
|
+
if (body === null || typeof body !== "object" || typeof body.sql !== "string") {
|
|
42485
|
+
throw new Error(
|
|
42486
|
+
`saved-search SQL: unexpected ${res.status} response: ${truncate3(res.rawText.trim(), 300)}`
|
|
42487
|
+
);
|
|
42488
|
+
}
|
|
42489
|
+
const sql = body.sql;
|
|
42490
|
+
const outPath = cfg.outPath !== void 0 && cfg.outPath !== "" ? cfg.outPath : void 0;
|
|
42491
|
+
let writeError;
|
|
42492
|
+
if (outPath !== void 0) {
|
|
42493
|
+
try {
|
|
42494
|
+
await writeSqlFile(outPath, sql);
|
|
42495
|
+
} catch (err) {
|
|
42496
|
+
writeError = err instanceof Error ? err.message : String(err);
|
|
42497
|
+
}
|
|
42498
|
+
}
|
|
42499
|
+
const written = outPath !== void 0 && writeError === void 0;
|
|
42500
|
+
if (cfg.asJson) {
|
|
42501
|
+
writeJSONIndented(cfg.out, body);
|
|
42502
|
+
} else {
|
|
42503
|
+
const name = typeof body.saved_search_name === "string" && body.saved_search_name !== "" ? body.saved_search_name : "(not listed by the bundle)";
|
|
42504
|
+
const key = typeof body.source_query_key === "string" ? body.source_query_key : "";
|
|
42505
|
+
cfg.out.write(`Saved search ${savedSearchId}: ${name}
|
|
42506
|
+
`);
|
|
42507
|
+
cfg.out.write(` Library match: ${key || "none \u2014 custom report"}
|
|
42508
|
+
`);
|
|
42509
|
+
if (written) {
|
|
42510
|
+
cfg.out.write(` SQL: written to ${outPath} (${sql.length} chars)
|
|
42511
|
+
`);
|
|
42512
|
+
} else {
|
|
42513
|
+
cfg.out.write(" SQL:\n");
|
|
42514
|
+
cfg.out.write(sql.endsWith("\n") ? sql : `${sql}
|
|
42515
|
+
`);
|
|
42516
|
+
}
|
|
42517
|
+
}
|
|
42518
|
+
if (writeError !== void 0) {
|
|
42519
|
+
throw new Error(`could not write --out ${outPath}: ${writeError} (the SQL is printed above)`);
|
|
42520
|
+
}
|
|
42521
|
+
}
|
|
41672
42522
|
async function runIntegrationsITList(cfg) {
|
|
41673
42523
|
if (cfg.page < 1) {
|
|
41674
42524
|
throw new Error("--page must be >= 1");
|
|
@@ -42257,6 +43107,80 @@ function registerIntegrationsCommand(root) {
|
|
|
42257
43107
|
addAccountFlag(shareGroupsCmd);
|
|
42258
43108
|
registerITCommands(integrations);
|
|
42259
43109
|
registerSftpCommands(integrations);
|
|
43110
|
+
registerNetSuiteCommands(integrations);
|
|
43111
|
+
}
|
|
43112
|
+
function registerNetSuiteCommands(parent) {
|
|
43113
|
+
const netsuite = parent.command("netsuite").description("NetSuite 3.0 -> SuiteQL in-place migration helpers");
|
|
43114
|
+
const readinessCmd = netsuite.command("readiness").description(
|
|
43115
|
+
'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.'
|
|
43116
|
+
).argument("<integration_id>", "NetSuite connection (data integration) id").option("--dry-run", "Probe only: never stamp the connection, no confirmation needed", false).option(
|
|
43117
|
+
"--confirm-token <token>",
|
|
43118
|
+
"Token from a prior plan run - required to actually stamp",
|
|
43119
|
+
""
|
|
43120
|
+
).option("--json", "Output the raw response as JSON", false).action(
|
|
43121
|
+
async (idStr, opts) => {
|
|
43122
|
+
const id = parseIntegrationIdArg(idStr);
|
|
43123
|
+
const dryRun = opts.dryRun === true;
|
|
43124
|
+
if (dryRun && !isPlanPhase(opts.confirmToken)) {
|
|
43125
|
+
throw new Error("--confirm-token does not apply to --dry-run, which writes nothing");
|
|
43126
|
+
}
|
|
43127
|
+
if (!dryRun) {
|
|
43128
|
+
await preflightConfirmToken(netsuiteStampAction(id), opts.confirmToken);
|
|
43129
|
+
}
|
|
43130
|
+
const serverKey = resolveServer(opts.server);
|
|
43131
|
+
await withSetup(
|
|
43132
|
+
opts.server,
|
|
43133
|
+
opts.account,
|
|
43134
|
+
async ({ client, signal }) => {
|
|
43135
|
+
await runIntegrationsNetSuiteReadiness({
|
|
43136
|
+
client,
|
|
43137
|
+
id,
|
|
43138
|
+
dryRun,
|
|
43139
|
+
token: opts.confirmToken,
|
|
43140
|
+
serverFlag: serverKey,
|
|
43141
|
+
asJson: opts.json === true,
|
|
43142
|
+
signal,
|
|
43143
|
+
out: process.stdout
|
|
43144
|
+
});
|
|
43145
|
+
},
|
|
43146
|
+
NETSUITE_CALL_TIMEOUT_MS
|
|
43147
|
+
);
|
|
43148
|
+
}
|
|
43149
|
+
);
|
|
43150
|
+
addServerFlag(readinessCmd);
|
|
43151
|
+
addAccountFlag(readinessCmd);
|
|
43152
|
+
const savedSearchSqlCmd = netsuite.command("saved-search-sql").description(
|
|
43153
|
+
`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.`
|
|
43154
|
+
).argument("<integration_id>", "NetSuite connection (data integration) id").requiredOption(
|
|
43155
|
+
"--saved-search <id>",
|
|
43156
|
+
"NetSuite saved search id: a numeric internal id (negative for standard searches) or a customsearch* script id (required)"
|
|
43157
|
+
).option(
|
|
43158
|
+
"--out <path>",
|
|
43159
|
+
"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)"
|
|
43160
|
+
).option("--json", "Output the raw response as JSON", false).action(
|
|
43161
|
+
async (idStr, opts) => {
|
|
43162
|
+
const id = parseIntegrationIdArg(idStr);
|
|
43163
|
+
parseSavedSearchId(opts.savedSearch);
|
|
43164
|
+
await withSetup(
|
|
43165
|
+
opts.server,
|
|
43166
|
+
opts.account,
|
|
43167
|
+
async ({ client, signal }) => {
|
|
43168
|
+
await runIntegrationsNetSuiteSavedSearchSql({
|
|
43169
|
+
client,
|
|
43170
|
+
id,
|
|
43171
|
+
savedSearchId: opts.savedSearch,
|
|
43172
|
+
outPath: opts.out,
|
|
43173
|
+
asJson: opts.json === true,
|
|
43174
|
+
signal,
|
|
43175
|
+
out: process.stdout
|
|
43176
|
+
});
|
|
43177
|
+
},
|
|
43178
|
+
NETSUITE_CALL_TIMEOUT_MS
|
|
43179
|
+
);
|
|
43180
|
+
}
|
|
43181
|
+
);
|
|
43182
|
+
addServerFlag(savedSearchSqlCmd);
|
|
43183
|
+
addAccountFlag(savedSearchSqlCmd);
|
|
42260
43184
|
}
|
|
42261
43185
|
function registerSftpCommands(parent) {
|
|
42262
43186
|
const sftp = parent.command("sftp").description("Self-serve SFTP fileboxes (the embedded SFTP integration)");
|
|
@@ -42899,7 +43823,7 @@ function registerDevFetch(connectors) {
|
|
|
42899
43823
|
|
|
42900
43824
|
// src/commands/connectorsPoll.ts
|
|
42901
43825
|
import * as readline2 from "readline";
|
|
42902
|
-
import { createHash as
|
|
43826
|
+
import { createHash as createHash11 } from "crypto";
|
|
42903
43827
|
|
|
42904
43828
|
// src/types/connectors.ts
|
|
42905
43829
|
var POLL_ITEM_STATES = [
|
|
@@ -43269,7 +44193,7 @@ function confirmDigest(scope, payload) {
|
|
|
43269
44193
|
controlPlane: scope.controlPlane,
|
|
43270
44194
|
payload: Object.fromEntries(Object.entries(payload).sort(([a], [b]) => a.localeCompare(b)))
|
|
43271
44195
|
});
|
|
43272
|
-
return
|
|
44196
|
+
return createHash11("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
43273
44197
|
}
|
|
43274
44198
|
function pollConfirmAction(filebox, verb, scope, payload = {}) {
|
|
43275
44199
|
return {
|
|
@@ -46256,7 +47180,7 @@ function registerWidgetsCommand(root) {
|
|
|
46256
47180
|
}
|
|
46257
47181
|
|
|
46258
47182
|
// src/commands/reconciliation.ts
|
|
46259
|
-
import { createHash as
|
|
47183
|
+
import { createHash as createHash12 } from "crypto";
|
|
46260
47184
|
import { promises as fs18 } from "fs";
|
|
46261
47185
|
|
|
46262
47186
|
// src/api/reconciliation.ts
|
|
@@ -46785,7 +47709,7 @@ function wireReconciliationProcesses(parent) {
|
|
|
46785
47709
|
}
|
|
46786
47710
|
|
|
46787
47711
|
// src/commands/reconciliation_evidences.ts
|
|
46788
|
-
import { readFile as
|
|
47712
|
+
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
46789
47713
|
import { basename as basename6 } from "path";
|
|
46790
47714
|
|
|
46791
47715
|
// src/commands/_processResolver.ts
|
|
@@ -46912,7 +47836,7 @@ async function runReconciliationEvidencesCreate(cfg) {
|
|
|
46912
47836
|
}
|
|
46913
47837
|
let buf;
|
|
46914
47838
|
try {
|
|
46915
|
-
buf = await
|
|
47839
|
+
buf = await readFile6(cfg.filePath);
|
|
46916
47840
|
} catch (err) {
|
|
46917
47841
|
const m = err instanceof Error ? err.message : String(err);
|
|
46918
47842
|
throw new Error(`read --file: ${m}`);
|
|
@@ -47837,7 +48761,7 @@ function chunkByJsonBytes(rows, maxBytes) {
|
|
|
47837
48761
|
return chunks;
|
|
47838
48762
|
}
|
|
47839
48763
|
function settingsActionId(settings) {
|
|
47840
|
-
return
|
|
48764
|
+
return createHash12("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 12);
|
|
47841
48765
|
}
|
|
47842
48766
|
async function runReconciliationAccountsUpdateSettings(cfg) {
|
|
47843
48767
|
const action = {
|
|
@@ -48215,7 +49139,7 @@ async function runReconciliationAccountsSetAssignee(cfg) {
|
|
|
48215
49139
|
var ASSIGNEE_BATCH_CHUNK_SIZE = 100;
|
|
48216
49140
|
function assigneeBatchActionId(date, rows) {
|
|
48217
49141
|
const canonical = rows.map((r) => [r.accountId, r.type, r.userId, r.userName, r.userEmail]);
|
|
48218
|
-
return
|
|
49142
|
+
return createHash12("sha256").update(JSON.stringify([date, canonical])).digest("hex").slice(0, 12);
|
|
48219
49143
|
}
|
|
48220
49144
|
async function runReconciliationAccountsSetAssigneeBatch(cfg) {
|
|
48221
49145
|
const action = {
|
|
@@ -48369,7 +49293,7 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
48369
49293
|
}
|
|
48370
49294
|
function clearBatchActionId(date, rows) {
|
|
48371
49295
|
const canonical = rows.map((r) => [r.accountId, r.type]);
|
|
48372
|
-
return
|
|
49296
|
+
return createHash12("sha256").update(JSON.stringify(["clear", date, canonical])).digest("hex").slice(0, 12);
|
|
48373
49297
|
}
|
|
48374
49298
|
async function runReconciliationAccountsClearAssigneeBatch(cfg) {
|
|
48375
49299
|
const action = {
|
|
@@ -49692,7 +50616,7 @@ function applyProse(data4, overlay, warn2 = () => {
|
|
|
49692
50616
|
}
|
|
49693
50617
|
|
|
49694
50618
|
// src/commands/_validateReportRender.ts
|
|
49695
|
-
import { readFile as
|
|
50619
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
49696
50620
|
import { join as join13 } from "path";
|
|
49697
50621
|
var MARKER = "<!--__DATA_BLOCK__-->";
|
|
49698
50622
|
function renderReportHtml(data4, templateHtml) {
|
|
@@ -49707,7 +50631,7 @@ function reportTemplatePath() {
|
|
|
49707
50631
|
return join13(findSkillDir("dr-validate"), "report-template.html");
|
|
49708
50632
|
}
|
|
49709
50633
|
async function readReportTemplate() {
|
|
49710
|
-
return
|
|
50634
|
+
return readFile7(reportTemplatePath(), "utf8");
|
|
49711
50635
|
}
|
|
49712
50636
|
|
|
49713
50637
|
// src/commands/validate.ts
|
|
@@ -50259,7 +51183,7 @@ function renderForError(v) {
|
|
|
50259
51183
|
}
|
|
50260
51184
|
|
|
50261
51185
|
// src/commands/mecWorkflow.ts
|
|
50262
|
-
import { createHash as
|
|
51186
|
+
import { createHash as createHash13 } from "crypto";
|
|
50263
51187
|
|
|
50264
51188
|
// src/api/workflow.ts
|
|
50265
51189
|
function buildQueryString(params) {
|
|
@@ -52351,13 +53275,13 @@ async function runMecAccountsTaskStatuses(cfg) {
|
|
|
52351
53275
|
}
|
|
52352
53276
|
renderMecAccountsTasksOrStatuses(cfg.out, resp, "statuses");
|
|
52353
53277
|
}
|
|
52354
|
-
function
|
|
52355
|
-
return
|
|
53278
|
+
function shortHash5(input) {
|
|
53279
|
+
return createHash13("sha256").update(input).digest("hex").slice(0, 12);
|
|
52356
53280
|
}
|
|
52357
53281
|
async function runMecWorkflowCreate(cfg) {
|
|
52358
53282
|
const action = {
|
|
52359
53283
|
resource: "mec-workflow-template",
|
|
52360
|
-
id:
|
|
53284
|
+
id: shortHash5(JSON.stringify(cfg.payload)),
|
|
52361
53285
|
verb: "create"
|
|
52362
53286
|
};
|
|
52363
53287
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53004,7 +53928,7 @@ async function runMecTaskDelete(cfg) {
|
|
|
53004
53928
|
const sortedIds = [...cfg.ids].map((r) => r.id).sort((a, b) => a - b);
|
|
53005
53929
|
const action = {
|
|
53006
53930
|
resource: "mec-workflow-task-batch",
|
|
53007
|
-
id: `${cfg.stepId}:${
|
|
53931
|
+
id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
|
|
53008
53932
|
verb: "bulk-delete"
|
|
53009
53933
|
};
|
|
53010
53934
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53167,7 +54091,7 @@ async function runMecTaskSignOff(cfg) {
|
|
|
53167
54091
|
const sortedIds = [...cfg.ids].sort((a, b) => a - b);
|
|
53168
54092
|
const action = {
|
|
53169
54093
|
resource: "mec-workflow-task",
|
|
53170
|
-
id: `${cfg.stepId}:${
|
|
54094
|
+
id: `${cfg.stepId}:${shortHash5(sortedIds.join(","))}`,
|
|
53171
54095
|
verb: cfg.isSignOff ? "sign-off" : "reopen"
|
|
53172
54096
|
};
|
|
53173
54097
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53366,7 +54290,7 @@ async function runMecMemberGrantBulk(cfg) {
|
|
|
53366
54290
|
const hashInput = cfg.payload.emails.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
53367
54291
|
const action = {
|
|
53368
54292
|
resource: "mec-workflow-member-grant",
|
|
53369
|
-
id:
|
|
54293
|
+
id: shortHash5(hashInput),
|
|
53370
54294
|
verb: "bulk"
|
|
53371
54295
|
};
|
|
53372
54296
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -53522,7 +54446,7 @@ async function runMecGroupGrantBulk(cfg) {
|
|
|
53522
54446
|
const hashInput = cfg.payload.permission_groups.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
53523
54447
|
const action = {
|
|
53524
54448
|
resource: "mec-workflow-group-grant",
|
|
53525
|
-
id:
|
|
54449
|
+
id: shortHash5(hashInput),
|
|
53526
54450
|
verb: "bulk"
|
|
53527
54451
|
};
|
|
53528
54452
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -54736,7 +55660,7 @@ function wireMec(program) {
|
|
|
54736
55660
|
}
|
|
54737
55661
|
|
|
54738
55662
|
// src/commands/workflow.ts
|
|
54739
|
-
import { createHash as
|
|
55663
|
+
import { createHash as createHash14 } from "crypto";
|
|
54740
55664
|
function dash6(s) {
|
|
54741
55665
|
if (s === null || s === void 0) return "-";
|
|
54742
55666
|
if (typeof s === "number") return String(s);
|
|
@@ -55204,13 +56128,13 @@ function renderGroupList(out, rows) {
|
|
|
55204
56128
|
(${rows.length} group${plural(rows.length)})
|
|
55205
56129
|
`);
|
|
55206
56130
|
}
|
|
55207
|
-
function
|
|
55208
|
-
return
|
|
56131
|
+
function shortHash6(input) {
|
|
56132
|
+
return createHash14("sha256").update(input).digest("hex").slice(0, 12);
|
|
55209
56133
|
}
|
|
55210
56134
|
async function runWorkflowCreate(cfg) {
|
|
55211
56135
|
const action = {
|
|
55212
56136
|
resource: "workflow-template",
|
|
55213
|
-
id:
|
|
56137
|
+
id: shortHash6(JSON.stringify(cfg.payload)),
|
|
55214
56138
|
verb: "create"
|
|
55215
56139
|
};
|
|
55216
56140
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -55852,7 +56776,7 @@ async function runTaskDelete(cfg) {
|
|
|
55852
56776
|
const sortedIds = [...cfg.ids].map((r) => r.id).sort((a, b) => a - b);
|
|
55853
56777
|
const action = {
|
|
55854
56778
|
resource: "workflow-task-batch",
|
|
55855
|
-
id: `${cfg.stepId}:${
|
|
56779
|
+
id: `${cfg.stepId}:${shortHash6(sortedIds.join(","))}`,
|
|
55856
56780
|
verb: "bulk-delete"
|
|
55857
56781
|
};
|
|
55858
56782
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -56015,7 +56939,7 @@ async function runTaskSignOff(cfg) {
|
|
|
56015
56939
|
const sortedIds = [...cfg.ids].sort((a, b) => a - b);
|
|
56016
56940
|
const action = {
|
|
56017
56941
|
resource: "workflow-task",
|
|
56018
|
-
id: `${cfg.stepId}:${
|
|
56942
|
+
id: `${cfg.stepId}:${shortHash6(sortedIds.join(","))}`,
|
|
56019
56943
|
verb: cfg.isSignOff ? "sign-off" : "reopen"
|
|
56020
56944
|
};
|
|
56021
56945
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -56212,7 +57136,7 @@ async function runMemberGrantBulk(cfg) {
|
|
|
56212
57136
|
const hashInput = cfg.payload.emails.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
56213
57137
|
const action = {
|
|
56214
57138
|
resource: "workflow-member-grant",
|
|
56215
|
-
id:
|
|
57139
|
+
id: shortHash6(hashInput),
|
|
56216
57140
|
verb: "bulk"
|
|
56217
57141
|
};
|
|
56218
57142
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -56368,7 +57292,7 @@ async function runGroupGrantBulk(cfg) {
|
|
|
56368
57292
|
const hashInput = cfg.payload.permission_groups.join(",") + "|" + cfg.payload.entities_ids.join(",") + "|" + cfg.payload.role;
|
|
56369
57293
|
const action = {
|
|
56370
57294
|
resource: "workflow-group-grant",
|
|
56371
|
-
id:
|
|
57295
|
+
id: shortHash6(hashInput),
|
|
56372
57296
|
verb: "bulk"
|
|
56373
57297
|
};
|
|
56374
57298
|
await preflightConfirmToken(action, cfg.token);
|
|
@@ -57364,7 +58288,7 @@ function wireWorkflow(root) {
|
|
|
57364
58288
|
}
|
|
57365
58289
|
|
|
57366
58290
|
// src/commands/cash.ts
|
|
57367
|
-
import { createHash as
|
|
58291
|
+
import { createHash as createHash15 } from "crypto";
|
|
57368
58292
|
import { promises as fs25 } from "fs";
|
|
57369
58293
|
|
|
57370
58294
|
// src/api/cash.ts
|
|
@@ -58455,7 +59379,7 @@ async function runCashRulesDeleteAll(cfg) {
|
|
|
58455
59379
|
return;
|
|
58456
59380
|
}
|
|
58457
59381
|
const ids = targets.map((r) => r.id).sort((a, b) => a - b);
|
|
58458
|
-
const sha =
|
|
59382
|
+
const sha = createHash15("sha256").update(ids.join(",")).digest("hex").slice(0, 12);
|
|
58459
59383
|
const action = {
|
|
58460
59384
|
resource: "cash-rules-batch",
|
|
58461
59385
|
id: `${templateId}:${sha}`,
|
|
@@ -68173,7 +69097,7 @@ function parseDueDate(raw) {
|
|
|
68173
69097
|
}
|
|
68174
69098
|
return trimmed;
|
|
68175
69099
|
}
|
|
68176
|
-
async function readTicketDescription(inline, file,
|
|
69100
|
+
async function readTicketDescription(inline, file, readFile9 = (p) => fs31.readFile(p, "utf8")) {
|
|
68177
69101
|
if (inline !== void 0 && file !== void 0) {
|
|
68178
69102
|
throw new Error("pass --description or --description-file, not both.");
|
|
68179
69103
|
}
|
|
@@ -68181,7 +69105,7 @@ async function readTicketDescription(inline, file, readFile8 = (p) => fs31.readF
|
|
|
68181
69105
|
if (file.trim() === "") throw new Error("--description-file needs a path.");
|
|
68182
69106
|
let body;
|
|
68183
69107
|
try {
|
|
68184
|
-
body = await
|
|
69108
|
+
body = await readFile9(file);
|
|
68185
69109
|
} catch (err) {
|
|
68186
69110
|
const msg = err instanceof Error ? err.message : String(err);
|
|
68187
69111
|
throw new Error(`--description-file ${file}: ${msg}`);
|
|
@@ -69880,7 +70804,7 @@ function commandPath(actionCommand) {
|
|
|
69880
70804
|
// src/telemetry/config.ts
|
|
69881
70805
|
import { homedir as homedir6 } from "os";
|
|
69882
70806
|
import { join as join16 } from "path";
|
|
69883
|
-
import { readFile as
|
|
70807
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
69884
70808
|
var TELEMETRY_ENV = "DR_CLI_TELEMETRY";
|
|
69885
70809
|
var DISABLED_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
|
|
69886
70810
|
function isTelemetryEnabled(env = process.env) {
|
|
@@ -69918,7 +70842,7 @@ function remoteSettingsPath() {
|
|
|
69918
70842
|
async function readCollectorConfig(path12 = remoteSettingsPath()) {
|
|
69919
70843
|
let raw;
|
|
69920
70844
|
try {
|
|
69921
|
-
raw = await
|
|
70845
|
+
raw = await readFile8(path12, "utf8");
|
|
69922
70846
|
} catch {
|
|
69923
70847
|
return null;
|
|
69924
70848
|
}
|