@datarails-ai/dr-cli 1.6.150 → 1.6.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +1630 -1222
- package/dist/scopes-map.json +39 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@datarails-ai/dr-cli",
|
|
11
|
-
version: "1.6.
|
|
11
|
+
version: "1.6.151",
|
|
12
12
|
description: "Datarails CLI",
|
|
13
13
|
type: "module",
|
|
14
14
|
engines: {
|
|
@@ -69,7 +69,7 @@ var package_default = {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
// src/buildInfo.ts
|
|
72
|
-
var BUILD_TIME = "2026-10-
|
|
72
|
+
var BUILD_TIME = "2026-10-22T09:31:33.112Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -512,6 +512,12 @@ function resolve(input) {
|
|
|
512
512
|
return lookup(trimmed);
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
+
// src/auth/userAgent.ts
|
|
516
|
+
var USER_AGENT = `dr-cli/${package_default.version} (${process.platform}; ${process.arch}; node/${process.versions.node})`;
|
|
517
|
+
function withUserAgent(headers) {
|
|
518
|
+
return { "User-Agent": USER_AGENT, ...headers };
|
|
519
|
+
}
|
|
520
|
+
|
|
515
521
|
// src/auth/errors.ts
|
|
516
522
|
var NotLoggedInError = class extends Error {
|
|
517
523
|
code = "ERR_NOT_LOGGED_IN";
|
|
@@ -760,12 +766,12 @@ async function mintJwt(host, sessionId, csrfToken, options) {
|
|
|
760
766
|
const base = trimTrailingSlash(host);
|
|
761
767
|
const res = await fetch(`${base}/jwt/api/token/`, {
|
|
762
768
|
method: "POST",
|
|
763
|
-
headers: {
|
|
769
|
+
headers: withUserAgent({
|
|
764
770
|
"Content-Type": "application/json",
|
|
765
771
|
Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`,
|
|
766
772
|
"X-CSRFToken": csrfToken,
|
|
767
773
|
Referer: `${base}/`
|
|
768
|
-
},
|
|
774
|
+
}),
|
|
769
775
|
body: "{}",
|
|
770
776
|
signal: options?.signal
|
|
771
777
|
});
|
|
@@ -789,10 +795,10 @@ async function refreshJwt(host, refreshToken, options) {
|
|
|
789
795
|
const base = trimTrailingSlash(host);
|
|
790
796
|
const res = await fetch(`${base}/jwt/api/token/refresh/`, {
|
|
791
797
|
method: "POST",
|
|
792
|
-
headers: {
|
|
798
|
+
headers: withUserAgent({
|
|
793
799
|
"Content-Type": "application/json",
|
|
794
800
|
Referer: `${base}/`
|
|
795
|
-
},
|
|
801
|
+
}),
|
|
796
802
|
body: JSON.stringify({ refresh: refreshToken }),
|
|
797
803
|
signal: options?.signal
|
|
798
804
|
});
|
|
@@ -816,7 +822,7 @@ async function refreshExternalJwt(host, refreshToken, options) {
|
|
|
816
822
|
const base = trimTrailingSlash(host);
|
|
817
823
|
const res = await fetch(`${base}/jwt/api/external/token/refresh/`, {
|
|
818
824
|
method: "POST",
|
|
819
|
-
headers: { "Content-Type": "application/json" },
|
|
825
|
+
headers: withUserAgent({ "Content-Type": "application/json" }),
|
|
820
826
|
body: JSON.stringify({ refresh: refreshToken }),
|
|
821
827
|
signal: options?.signal
|
|
822
828
|
});
|
|
@@ -855,7 +861,7 @@ async function exchangeApiKey(host, apiKey, options) {
|
|
|
855
861
|
const base = trimTrailingSlash(host);
|
|
856
862
|
const res = await fetch(`${base}/jwt/api/apikey/token/`, {
|
|
857
863
|
method: "POST",
|
|
858
|
-
headers: { Authorization: `Bearer ${key}`, Accept: "application/json" },
|
|
864
|
+
headers: withUserAgent({ Authorization: `Bearer ${key}`, Accept: "application/json" }),
|
|
859
865
|
signal: options?.signal
|
|
860
866
|
});
|
|
861
867
|
const text2 = await res.text();
|
|
@@ -912,10 +918,10 @@ async function loginAs(host, operatorAccessToken, userId, options) {
|
|
|
912
918
|
}
|
|
913
919
|
const res = await fetch(`${base}/jwt/api/loginas/`, {
|
|
914
920
|
method: "POST",
|
|
915
|
-
headers: {
|
|
921
|
+
headers: withUserAgent({
|
|
916
922
|
"Content-Type": "application/json",
|
|
917
923
|
Authorization: `Bearer ${operatorAccessToken}`
|
|
918
|
-
},
|
|
924
|
+
}),
|
|
919
925
|
body: JSON.stringify(body),
|
|
920
926
|
signal: options?.signal
|
|
921
927
|
});
|
|
@@ -1075,7 +1081,7 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
|
|
|
1075
1081
|
const deadline = AbortSignal.timeout(API_KEY_EXCHANGE_DEADLINE_MS);
|
|
1076
1082
|
deadline.addEventListener("abort", () => controller.abort(deadline.reason), { once: true });
|
|
1077
1083
|
const signal = controller.signal;
|
|
1078
|
-
const
|
|
1084
|
+
const run2 = (async () => {
|
|
1079
1085
|
if (host === "") {
|
|
1080
1086
|
throw new SessionExpiredError(`${identity2} (no host to exchange the API key against)`);
|
|
1081
1087
|
}
|
|
@@ -1148,20 +1154,20 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
|
|
|
1148
1154
|
}
|
|
1149
1155
|
})();
|
|
1150
1156
|
const evict = () => {
|
|
1151
|
-
if (inFlightExchanges.get(key)?.run ===
|
|
1157
|
+
if (inFlightExchanges.get(key)?.run === run2) inFlightExchanges.delete(key);
|
|
1152
1158
|
};
|
|
1153
1159
|
const entry = {
|
|
1154
|
-
run,
|
|
1160
|
+
run: run2,
|
|
1155
1161
|
waiters: 0,
|
|
1156
1162
|
abandon: () => {
|
|
1157
|
-
if (inFlightExchanges.get(key)?.run !==
|
|
1163
|
+
if (inFlightExchanges.get(key)?.run !== run2) return;
|
|
1158
1164
|
evict();
|
|
1159
1165
|
const reason = new Error(`${identity2} (api-key exchange abandoned by its last caller)`);
|
|
1160
1166
|
reason.name = "AbortError";
|
|
1161
1167
|
controller.abort(reason);
|
|
1162
1168
|
}
|
|
1163
1169
|
};
|
|
1164
|
-
void
|
|
1170
|
+
void run2.then(evict, evict);
|
|
1165
1171
|
inFlightExchanges.set(key, entry);
|
|
1166
1172
|
return joinExchange(entry, opts.signal);
|
|
1167
1173
|
}
|
|
@@ -2150,6 +2156,7 @@ var AuthorizedClient = class _AuthorizedClient {
|
|
|
2150
2156
|
* @internal
|
|
2151
2157
|
*/
|
|
2152
2158
|
applyAuthHeaders(headers, hasBody, includeBearer) {
|
|
2159
|
+
headers.set("User-Agent", USER_AGENT);
|
|
2153
2160
|
if (!this.isCookieless()) {
|
|
2154
2161
|
headers.set(
|
|
2155
2162
|
"Cookie",
|
|
@@ -5607,11 +5614,11 @@ async function endCookieSession(base, session) {
|
|
|
5607
5614
|
return res.status >= 200 && res.status < 400 ? void 0 : `HTTP ${res.status}`;
|
|
5608
5615
|
});
|
|
5609
5616
|
}
|
|
5610
|
-
async function withTimeout(
|
|
5617
|
+
async function withTimeout(run2) {
|
|
5611
5618
|
const controller = new AbortController();
|
|
5612
5619
|
const timeout = setTimeout(() => controller.abort(), REVOKE_TIMEOUT_MS);
|
|
5613
5620
|
try {
|
|
5614
|
-
return await
|
|
5621
|
+
return await run2(controller.signal);
|
|
5615
5622
|
} catch {
|
|
5616
5623
|
return controller.signal.aborted ? "timed out" : "network error";
|
|
5617
5624
|
} finally {
|
|
@@ -5652,7 +5659,7 @@ async function requestDeviceCode(host, options) {
|
|
|
5652
5659
|
const body = new URLSearchParams({ client_id: CLIENT_ID, scope: paths.scope });
|
|
5653
5660
|
const res = await fetch(`${base}${paths.code}`, {
|
|
5654
5661
|
method: "POST",
|
|
5655
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
5662
|
+
headers: withUserAgent({ "Content-Type": "application/x-www-form-urlencoded" }),
|
|
5656
5663
|
body: body.toString(),
|
|
5657
5664
|
signal: options?.signal
|
|
5658
5665
|
});
|
|
@@ -5780,7 +5787,7 @@ async function poll(host, path13, deviceCode, options, terminalHints, onSuccess)
|
|
|
5780
5787
|
});
|
|
5781
5788
|
const res = await fetch(`${base}${path13}`, {
|
|
5782
5789
|
method: "POST",
|
|
5783
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
5790
|
+
headers: withUserAgent({ "Content-Type": "application/x-www-form-urlencoded" }),
|
|
5784
5791
|
body: body.toString(),
|
|
5785
5792
|
signal
|
|
5786
5793
|
});
|
|
@@ -25922,14 +25929,14 @@ function wireDimensionSuggestions(dimensions) {
|
|
|
25922
25929
|
refresh: runDimensionSuggestionsRefresh
|
|
25923
25930
|
});
|
|
25924
25931
|
}
|
|
25925
|
-
function wireSuggestionGroup(parent, r,
|
|
25932
|
+
function wireSuggestionGroup(parent, r, run2) {
|
|
25926
25933
|
const group = parent.command("suggestions").description(
|
|
25927
25934
|
`AI-suggested ${r.noun}s \u2014 agent-generated; requires the ${r.flagName} Unleash flag and an org-admin user. \`profile\` is the exception: read-only table/field profiling, no flag needed`
|
|
25928
25935
|
);
|
|
25929
25936
|
const listCmd = group.command("list").description(`List AI ${r.noun} suggestions and the latest generation-run status (last_run)`).option("--json", "Output raw JSON instead of a table", false).action(async (opts) => {
|
|
25930
25937
|
const setup = await setupCall(opts.server, opts.account);
|
|
25931
25938
|
try {
|
|
25932
|
-
await
|
|
25939
|
+
await run2.list({
|
|
25933
25940
|
client: setup.client,
|
|
25934
25941
|
asJson: opts.json === true,
|
|
25935
25942
|
signal: setup.signal,
|
|
@@ -25959,7 +25966,7 @@ function wireSuggestionGroup(parent, r, run) {
|
|
|
25959
25966
|
timeoutMs: suggestionsCallTimeoutMs(wait, timeoutSec)
|
|
25960
25967
|
});
|
|
25961
25968
|
try {
|
|
25962
|
-
await
|
|
25969
|
+
await run2.refresh({
|
|
25963
25970
|
client: setup.client,
|
|
25964
25971
|
asJson: opts.json === true,
|
|
25965
25972
|
wait,
|
|
@@ -31281,16 +31288,16 @@ async function runEnrichmentTypesList(cfg) {
|
|
|
31281
31288
|
function oneLine2(s, max) {
|
|
31282
31289
|
return truncate3(s.replace(/\s+/g, " "), max);
|
|
31283
31290
|
}
|
|
31284
|
-
async function runBatch(items, target,
|
|
31291
|
+
async function runBatch(items, target, run2, signal) {
|
|
31285
31292
|
if (items.length === 1) {
|
|
31286
31293
|
const only = items[0];
|
|
31287
|
-
return [{ target: target(only, 0), ok: true, value: await
|
|
31294
|
+
return [{ target: target(only, 0), ok: true, value: await run2(only) }];
|
|
31288
31295
|
}
|
|
31289
31296
|
const outcomes = [];
|
|
31290
31297
|
for (let i = 0; i < items.length; i++) {
|
|
31291
31298
|
const item = items[i];
|
|
31292
31299
|
try {
|
|
31293
|
-
outcomes.push({ target: target(item, i), ok: true, value: await
|
|
31300
|
+
outcomes.push({ target: target(item, i), ok: true, value: await run2(item) });
|
|
31294
31301
|
} catch (err) {
|
|
31295
31302
|
outcomes.push({
|
|
31296
31303
|
target: target(item, i),
|
|
@@ -33227,16 +33234,16 @@ function findArityViolations(entries) {
|
|
|
33227
33234
|
for (const entry of entries) {
|
|
33228
33235
|
if (typeof entry.formula !== "string") continue;
|
|
33229
33236
|
const calls = findFunctionCalls(entry.formula);
|
|
33230
|
-
for (const
|
|
33231
|
-
const upper =
|
|
33237
|
+
for (const call3 of calls) {
|
|
33238
|
+
const upper = call3.name.toUpperCase();
|
|
33232
33239
|
const arity = KNOWN_FUNCTION_ARITIES[upper];
|
|
33233
33240
|
if (arity === void 0) continue;
|
|
33234
|
-
if (
|
|
33241
|
+
if (call3.argCount >= arity.min && call3.argCount <= arity.max) continue;
|
|
33235
33242
|
const expected = arity.min === arity.max ? `exactly ${arity.min}` : `${arity.min}-${arity.max}`;
|
|
33236
33243
|
violations.push({
|
|
33237
33244
|
fieldName: entry.name === "" ? "(unnamed)" : entry.name,
|
|
33238
|
-
functionName:
|
|
33239
|
-
got:
|
|
33245
|
+
functionName: call3.name,
|
|
33246
|
+
got: call3.argCount,
|
|
33240
33247
|
expected,
|
|
33241
33248
|
note: arity.note
|
|
33242
33249
|
});
|
|
@@ -46214,503 +46221,1194 @@ function toControlPlaneError(method, path13, err) {
|
|
|
46214
46221
|
);
|
|
46215
46222
|
}
|
|
46216
46223
|
|
|
46217
|
-
// src/
|
|
46218
|
-
|
|
46219
|
-
|
|
46220
|
-
|
|
46221
|
-
|
|
46222
|
-
|
|
46223
|
-
|
|
46224
|
-
...i.queryConfig,
|
|
46225
|
-
// Coerce absent values to null (not undefined) so JSON.stringify keeps the
|
|
46226
|
-
// key — matches build_hook_job's dq.get(...) -> None; a dropped objectType
|
|
46227
|
-
// becomes a cryptic KeyError in the runner's dispatch.
|
|
46228
|
-
objectType: i.dataQuery.objectType ?? null,
|
|
46229
|
-
type: i.dataQuery.type ?? null,
|
|
46230
|
-
query: i.dataQuery.query
|
|
46231
|
-
};
|
|
46232
|
-
if (i.period) config.period = i.period;
|
|
46233
|
-
return {
|
|
46234
|
-
correlation_id: i.correlationId,
|
|
46235
|
-
config,
|
|
46236
|
-
secret_ref: i.secretRef,
|
|
46237
|
-
cred_token: "dev-local",
|
|
46238
|
-
output_sas: "file:///out/output.csv",
|
|
46239
|
-
input_sas: null,
|
|
46240
|
-
cursor: null,
|
|
46241
|
-
contract_version: i.contractVersion,
|
|
46242
|
-
metadata: {}
|
|
46243
|
-
};
|
|
46244
|
-
}
|
|
46245
|
-
function asObject(v) {
|
|
46246
|
-
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
46247
|
-
return v;
|
|
46224
|
+
// src/api/awsConnections.ts
|
|
46225
|
+
async function call(method, path13, send, what) {
|
|
46226
|
+
let out;
|
|
46227
|
+
try {
|
|
46228
|
+
out = await send();
|
|
46229
|
+
} catch (err) {
|
|
46230
|
+
throw toControlPlaneError(method, path13, err);
|
|
46248
46231
|
}
|
|
46249
|
-
|
|
46232
|
+
if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
|
|
46233
|
+
return out;
|
|
46250
46234
|
}
|
|
46251
|
-
function
|
|
46252
|
-
const
|
|
46253
|
-
|
|
46254
|
-
|
|
46255
|
-
|
|
46256
|
-
)
|
|
46257
|
-
|
|
46258
|
-
|
|
46259
|
-
const src = cfg ?? root;
|
|
46260
|
-
const query = src.query;
|
|
46261
|
-
let queryObj;
|
|
46262
|
-
if (query === void 0 || query === null) {
|
|
46263
|
-
queryObj = {};
|
|
46264
|
-
} else {
|
|
46265
|
-
const asObj = asObject(query);
|
|
46266
|
-
if (asObj === null) {
|
|
46267
|
-
throw new Error(
|
|
46268
|
-
`data-query \`query\` is not an object (got ${typeof query}) \u2014 expected the Sync-query field`
|
|
46269
|
-
);
|
|
46270
|
-
}
|
|
46271
|
-
queryObj = asObj;
|
|
46272
|
-
}
|
|
46273
|
-
return { objectType: queryObj.objectType, type: src.type, query: queryObj };
|
|
46235
|
+
async function createAwsConnection(client, fields, reqOptions) {
|
|
46236
|
+
const path13 = "/aws-connections";
|
|
46237
|
+
return call(
|
|
46238
|
+
"POST",
|
|
46239
|
+
path13,
|
|
46240
|
+
() => client.postJSON(path13, fields, reqOptions),
|
|
46241
|
+
"aws connection create"
|
|
46242
|
+
);
|
|
46274
46243
|
}
|
|
46275
|
-
function
|
|
46276
|
-
const
|
|
46277
|
-
|
|
46278
|
-
|
|
46279
|
-
|
|
46280
|
-
)
|
|
46281
|
-
|
|
46282
|
-
|
|
46244
|
+
async function listAwsConnections(client, secretRef, reqOptions) {
|
|
46245
|
+
const path13 = secretRef === void 0 ? "/aws-connections" : `/aws-connections?secret_ref=${encodeURIComponent(secretRef)}`;
|
|
46246
|
+
return call(
|
|
46247
|
+
"GET",
|
|
46248
|
+
path13,
|
|
46249
|
+
() => client.getJSON(path13, reqOptions),
|
|
46250
|
+
"aws connection list"
|
|
46251
|
+
);
|
|
46283
46252
|
}
|
|
46284
|
-
|
|
46285
|
-
|
|
46286
|
-
|
|
46287
|
-
|
|
46288
|
-
|
|
46289
|
-
|
|
46290
|
-
|
|
46291
|
-
|
|
46292
|
-
|
|
46293
|
-
|
|
46294
|
-
}
|
|
46295
|
-
|
|
46253
|
+
async function getAwsConnection(client, id, reqOptions) {
|
|
46254
|
+
const path13 = `/aws-connections/${id}`;
|
|
46255
|
+
return call(
|
|
46256
|
+
"GET",
|
|
46257
|
+
path13,
|
|
46258
|
+
() => client.getJSON(path13, reqOptions),
|
|
46259
|
+
"aws connection get"
|
|
46260
|
+
);
|
|
46261
|
+
}
|
|
46262
|
+
async function updateAwsConnection(client, id, fields, reqOptions) {
|
|
46263
|
+
const path13 = `/aws-connections/${id}`;
|
|
46264
|
+
return call(
|
|
46265
|
+
"PATCH",
|
|
46266
|
+
path13,
|
|
46267
|
+
() => client.patchJSON(path13, fields, reqOptions),
|
|
46268
|
+
"aws connection update"
|
|
46269
|
+
);
|
|
46270
|
+
}
|
|
46271
|
+
|
|
46272
|
+
// src/commands/connectorsTasks.ts
|
|
46273
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
46274
|
+
|
|
46275
|
+
// src/api/scheduledTasks.ts
|
|
46276
|
+
var BASE2 = "/scheduled-tasks";
|
|
46277
|
+
async function call2(method, path13, send, what) {
|
|
46278
|
+
let out;
|
|
46296
46279
|
try {
|
|
46297
|
-
|
|
46298
|
-
} catch {
|
|
46299
|
-
throw
|
|
46280
|
+
out = await send();
|
|
46281
|
+
} catch (err) {
|
|
46282
|
+
throw toControlPlaneError(method, path13, err);
|
|
46300
46283
|
}
|
|
46301
|
-
|
|
46302
|
-
|
|
46303
|
-
|
|
46304
|
-
|
|
46305
|
-
|
|
46284
|
+
if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
|
|
46285
|
+
return out;
|
|
46286
|
+
}
|
|
46287
|
+
async function createTask(client, fields, reqOptions) {
|
|
46288
|
+
return call2(
|
|
46289
|
+
"POST",
|
|
46290
|
+
BASE2,
|
|
46291
|
+
() => client.postJSON(BASE2, fields, reqOptions),
|
|
46292
|
+
"task create"
|
|
46306
46293
|
);
|
|
46307
|
-
|
|
46308
|
-
|
|
46309
|
-
|
|
46310
|
-
|
|
46311
|
-
|
|
46294
|
+
}
|
|
46295
|
+
async function listTasks(client, orgId, reqOptions) {
|
|
46296
|
+
const path13 = orgId === void 0 ? BASE2 : `${BASE2}?org_id=${orgId}`;
|
|
46297
|
+
return call2("GET", path13, () => client.getJSON(path13, reqOptions), "task list");
|
|
46298
|
+
}
|
|
46299
|
+
async function getTask(client, taskId, reqOptions) {
|
|
46300
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
|
|
46301
|
+
return call2("GET", path13, () => client.getJSON(path13, reqOptions), "task get");
|
|
46302
|
+
}
|
|
46303
|
+
async function updateTask(client, taskId, fields, reqOptions) {
|
|
46304
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
|
|
46305
|
+
return call2(
|
|
46306
|
+
"PATCH",
|
|
46307
|
+
path13,
|
|
46308
|
+
() => client.patchJSON(path13, fields, reqOptions),
|
|
46309
|
+
"task update"
|
|
46312
46310
|
);
|
|
46313
|
-
if (process.platform !== "win32") {
|
|
46314
|
-
await fs19.chmod(secretsPath, 384);
|
|
46315
|
-
}
|
|
46316
|
-
await fs19.writeFile(path9.join(input.outDir, CONNECTION_FILE), connectionSource, "utf-8");
|
|
46317
|
-
await fs19.writeFile(path9.join(input.outDir, QUERY_FILE), querySource, "utf-8");
|
|
46318
46311
|
}
|
|
46319
|
-
async function
|
|
46320
|
-
const
|
|
46321
|
-
let current = "";
|
|
46312
|
+
async function deleteTask(client, taskId, reqOptions) {
|
|
46313
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
|
|
46322
46314
|
try {
|
|
46323
|
-
|
|
46324
|
-
} catch {
|
|
46315
|
+
await client.deleteJSON(path13, reqOptions);
|
|
46316
|
+
} catch (err) {
|
|
46317
|
+
throw toControlPlaneError("DELETE", path13, err);
|
|
46325
46318
|
}
|
|
46326
|
-
const normalized = entry.split("\\").join("/").replace(/\/+$/, "") + "/";
|
|
46327
|
-
const lines = current.split(/\r?\n/).map((l) => l.trim());
|
|
46328
|
-
if (lines.includes(normalized) || lines.includes(entry)) return;
|
|
46329
|
-
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
46330
|
-
await fs19.writeFile(gitignorePath, `${current}${sep}${normalized}
|
|
46331
|
-
`, "utf-8");
|
|
46332
46319
|
}
|
|
46333
|
-
function
|
|
46334
|
-
const
|
|
46335
|
-
|
|
46336
|
-
|
|
46337
|
-
|
|
46338
|
-
|
|
46320
|
+
async function runTask(client, taskId, reqOptions) {
|
|
46321
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/run`;
|
|
46322
|
+
return call2(
|
|
46323
|
+
"POST",
|
|
46324
|
+
path13,
|
|
46325
|
+
() => client.postJSON(path13, {}, reqOptions),
|
|
46326
|
+
"task run"
|
|
46327
|
+
);
|
|
46339
46328
|
}
|
|
46340
|
-
function
|
|
46341
|
-
const
|
|
46342
|
-
|
|
46343
|
-
|
|
46344
|
-
|
|
46345
|
-
|
|
46346
|
-
|
|
46347
|
-
|
|
46348
|
-
|
|
46349
|
-
|
|
46350
|
-
|
|
46351
|
-
|
|
46352
|
-
|
|
46353
|
-
|
|
46354
|
-
}
|
|
46355
|
-
|
|
46356
|
-
|
|
46357
|
-
|
|
46358
|
-
|
|
46359
|
-
|
|
46360
|
-
|
|
46361
|
-
|
|
46362
|
-
|
|
46363
|
-
|
|
46364
|
-
|
|
46365
|
-
});
|
|
46366
|
-
const monoSetup = await setupCall(opts.server, opts.account, {
|
|
46367
|
-
host: monolithHost,
|
|
46368
|
-
authHost: monolithHost
|
|
46369
|
-
});
|
|
46370
|
-
try {
|
|
46371
|
-
const rawDataQuery = await getDataQueryRaw(monoSetup.client, documentId, {
|
|
46372
|
-
signal: monoSetup.signal
|
|
46373
|
-
});
|
|
46374
|
-
const dataQuery = extractDataQueryFields(rawDataQuery);
|
|
46375
|
-
if (dataQuery.objectType === void 0 || dataQuery.objectType === null || dataQuery.objectType === "") {
|
|
46376
|
-
process.stderr.write(
|
|
46377
|
-
'WARNING: objectType is not set inside the Sync-query (config.query.objectType). get_query_class will receive None \u2014 fine only if the connector has a single default dataset. Add it to the data-query, e.g. config.query.objectType = "<dataset>".\n'
|
|
46378
|
-
);
|
|
46379
|
-
}
|
|
46380
|
-
const integration = await itGet(monoSetup.client, datasourceId, {
|
|
46381
|
-
signal: monoSetup.signal
|
|
46382
|
-
});
|
|
46383
|
-
const credentials = extractCredentials(integration);
|
|
46384
|
-
let period;
|
|
46385
|
-
if (opts.from !== void 0 && opts.to !== void 0) {
|
|
46386
|
-
period = { from: opts.from, to: opts.to };
|
|
46387
|
-
}
|
|
46388
|
-
const env = buildDevEnvelope({
|
|
46389
|
-
correlationId: randomUUID2(),
|
|
46390
|
-
secretRef: binding.secret_ref,
|
|
46391
|
-
publicConfig: binding.public_config,
|
|
46392
|
-
queryConfig: binding.query_config,
|
|
46393
|
-
dataQuery,
|
|
46394
|
-
...period !== void 0 ? { period } : {},
|
|
46395
|
-
// Deliberate sandbox stub: the fetch/dispatch path doesn't branch on
|
|
46396
|
-
// contract_version, so a fixed "1" is sufficient for a local run.
|
|
46397
|
-
contractVersion: "1"
|
|
46398
|
-
});
|
|
46399
|
-
await writeRunDir({
|
|
46400
|
-
outDir,
|
|
46401
|
-
env,
|
|
46402
|
-
secretRef: binding.secret_ref,
|
|
46403
|
-
credentials,
|
|
46404
|
-
connectionPath: opts.connection,
|
|
46405
|
-
queryPath: opts.query
|
|
46406
|
-
});
|
|
46407
|
-
await ensureGitignored(gitignoreEntry);
|
|
46408
|
-
process.stdout.write(`Wrote run dir: ${outDir}
|
|
46409
|
-
`);
|
|
46410
|
-
process.stdout.write(
|
|
46411
|
-
` env.json (secret_ref=${binding.secret_ref}, flavor=${binding.runtime_flavor})
|
|
46412
|
-
`
|
|
46413
|
-
);
|
|
46414
|
-
process.stdout.write(" secrets.json (credentials NOT shown)\n");
|
|
46415
|
-
process.stdout.write(" connection.py, query.py\n");
|
|
46416
|
-
} finally {
|
|
46417
|
-
monoSetup.cleanup();
|
|
46418
|
-
}
|
|
46419
|
-
} catch (err) {
|
|
46420
|
-
throw augmentTlsCertError(err);
|
|
46421
|
-
} finally {
|
|
46422
|
-
cpSetup.cleanup();
|
|
46423
|
-
}
|
|
46424
|
-
});
|
|
46425
|
-
addServerFlag(cmd);
|
|
46426
|
-
addAccountFlag(cmd);
|
|
46427
|
-
cmd.option(
|
|
46428
|
-
"--connectors-url <url>",
|
|
46429
|
-
"Override the control-plane base URL (e.g. a kubectl port-forward http://localhost:8000)"
|
|
46329
|
+
async function pauseTask(client, taskId, reqOptions) {
|
|
46330
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/pause`;
|
|
46331
|
+
return call2(
|
|
46332
|
+
"POST",
|
|
46333
|
+
path13,
|
|
46334
|
+
() => client.postJSON(path13, {}, reqOptions),
|
|
46335
|
+
"task pause"
|
|
46336
|
+
);
|
|
46337
|
+
}
|
|
46338
|
+
async function resumeTask(client, taskId, reqOptions) {
|
|
46339
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/resume`;
|
|
46340
|
+
return call2(
|
|
46341
|
+
"POST",
|
|
46342
|
+
path13,
|
|
46343
|
+
() => client.postJSON(path13, {}, reqOptions),
|
|
46344
|
+
"task resume"
|
|
46345
|
+
);
|
|
46346
|
+
}
|
|
46347
|
+
async function listRuns(client, taskId, limit, reqOptions) {
|
|
46348
|
+
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/runs?limit=${limit}`;
|
|
46349
|
+
return call2(
|
|
46350
|
+
"GET",
|
|
46351
|
+
path13,
|
|
46352
|
+
() => client.getJSON(path13, reqOptions),
|
|
46353
|
+
"task runs"
|
|
46430
46354
|
);
|
|
46431
46355
|
}
|
|
46432
46356
|
|
|
46433
|
-
// src/commands/
|
|
46434
|
-
import * as readline2 from "readline";
|
|
46435
|
-
import { createHash as createHash11 } from "crypto";
|
|
46436
|
-
|
|
46437
|
-
// src/types/connectors.ts
|
|
46438
|
-
var POLL_ITEM_STATES = [
|
|
46439
|
-
"seen",
|
|
46440
|
-
"new",
|
|
46441
|
-
"dispatched",
|
|
46442
|
-
"running",
|
|
46443
|
-
"retry_wait",
|
|
46444
|
-
"landed",
|
|
46445
|
-
"failed",
|
|
46446
|
-
"skipped",
|
|
46447
|
-
"seeded",
|
|
46448
|
-
"superseded"
|
|
46449
|
-
];
|
|
46450
|
-
|
|
46451
|
-
// src/commands/connectorsPoll.ts
|
|
46357
|
+
// src/commands/connectorsTasks.ts
|
|
46452
46358
|
var DURATION_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
46453
|
-
var
|
|
46454
|
-
|
|
46455
|
-
|
|
46456
|
-
};
|
|
46457
|
-
var LOOKBACK_MAX_DAYS = 3650;
|
|
46458
|
-
function parseDurationSeconds(raw2, flag) {
|
|
46359
|
+
var MIN_EVERY_SECONDS = 60;
|
|
46360
|
+
var MAX_EVERY_SECONDS = 2 ** 31 - 1;
|
|
46361
|
+
function parseEvery(raw2) {
|
|
46459
46362
|
const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
|
|
46460
46363
|
const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS[m[2] ?? "s"] ?? 1);
|
|
46461
|
-
|
|
46462
|
-
|
|
46463
|
-
|
|
46464
|
-
|
|
46465
|
-
if (!Number.isSafeInteger(seconds) || seconds < min || seconds > max) {
|
|
46466
|
-
throw new Error(
|
|
46467
|
-
`invalid ${flag} "${raw2}": must be between ${formatSeconds(min)} and ${formatSeconds(max)}`
|
|
46468
|
-
);
|
|
46364
|
+
if (m === null)
|
|
46365
|
+
throw new Error(`invalid --every "${raw2}": expected a duration like 5m, 1h or 1d`);
|
|
46366
|
+
if (!Number.isSafeInteger(seconds) || seconds < MIN_EVERY_SECONDS || seconds > MAX_EVERY_SECONDS) {
|
|
46367
|
+
throw new Error(`invalid --every "${raw2}": must be at least 1m`);
|
|
46469
46368
|
}
|
|
46470
46369
|
return seconds;
|
|
46471
46370
|
}
|
|
46472
|
-
function
|
|
46473
|
-
const
|
|
46474
|
-
|
|
46475
|
-
|
|
46476
|
-
throw new Error(
|
|
46477
|
-
`invalid --lookback "${raw2}": must be between 1 and ${LOOKBACK_MAX_DAYS} days, e.g. 180d`
|
|
46478
|
-
);
|
|
46371
|
+
function parseCron(raw2) {
|
|
46372
|
+
const fields = raw2.trim().split(/\s+/);
|
|
46373
|
+
if (fields.length !== 5 || fields.some((f) => f === "")) {
|
|
46374
|
+
throw new Error(`invalid --cron "${raw2}": expected five fields, e.g. "15 * * * *"`);
|
|
46479
46375
|
}
|
|
46480
|
-
return
|
|
46481
|
-
}
|
|
46482
|
-
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
46483
|
-
var DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))$/;
|
|
46484
|
-
function inRange(value, max, min = 0) {
|
|
46485
|
-
const n = Number(value ?? "0");
|
|
46486
|
-
return n >= min && n <= max;
|
|
46376
|
+
return fields.join(" ");
|
|
46487
46377
|
}
|
|
46488
|
-
|
|
46489
|
-
|
|
46490
|
-
|
|
46491
|
-
|
|
46378
|
+
var ROLE_RE = /^[a-z][a-z_]*$/;
|
|
46379
|
+
var DATASOURCE_ID_RE = /^[0-9]{1,18}$/;
|
|
46380
|
+
function parseDatasources(values) {
|
|
46381
|
+
const out = {};
|
|
46382
|
+
for (const raw2 of values) {
|
|
46383
|
+
const eq = raw2.indexOf("=");
|
|
46384
|
+
const role = eq < 0 ? "" : raw2.slice(0, eq).trim();
|
|
46385
|
+
const id = eq < 0 ? "" : raw2.slice(eq + 1).trim();
|
|
46386
|
+
if (!ROLE_RE.test(role) || !DATASOURCE_ID_RE.test(id)) {
|
|
46387
|
+
throw new Error(
|
|
46388
|
+
`invalid --datasource "${raw2}": expected <role>=<datasource id>, e.g. target=4242 (role: lowercase letters and _; id: digits)`
|
|
46389
|
+
);
|
|
46390
|
+
}
|
|
46391
|
+
if (Object.hasOwn(out, role)) throw new Error(`--datasource role "${role}" given twice`);
|
|
46392
|
+
out[role] = id;
|
|
46393
|
+
}
|
|
46394
|
+
const count = Object.keys(out).length;
|
|
46395
|
+
if (count < 1 || count > 4) throw new Error("give between 1 and 4 --datasource roles");
|
|
46396
|
+
return out;
|
|
46492
46397
|
}
|
|
46493
|
-
function
|
|
46494
|
-
const
|
|
46495
|
-
|
|
46496
|
-
|
|
46497
|
-
|
|
46498
|
-
|
|
46499
|
-
|
|
46500
|
-
|
|
46501
|
-
return (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString();
|
|
46398
|
+
function parseConfig(raw2, readFile9 = readUtf8) {
|
|
46399
|
+
const text2 = raw2.startsWith("@") ? readFile9(raw2.slice(1)) : raw2;
|
|
46400
|
+
let parsed2;
|
|
46401
|
+
try {
|
|
46402
|
+
parsed2 = JSON.parse(text2);
|
|
46403
|
+
} catch (err) {
|
|
46404
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
46405
|
+
throw new Error(`invalid --config JSON: ${msg}`);
|
|
46502
46406
|
}
|
|
46503
|
-
|
|
46504
|
-
|
|
46505
|
-
|
|
46506
|
-
|
|
46507
|
-
return new Date(ms).toISOString();
|
|
46407
|
+
if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2)) {
|
|
46408
|
+
throw new Error("invalid --config: expected a JSON object");
|
|
46409
|
+
}
|
|
46410
|
+
return parsed2;
|
|
46508
46411
|
}
|
|
46509
|
-
function
|
|
46510
|
-
|
|
46511
|
-
if (raw2 === "ignore") return "ignore";
|
|
46512
|
-
throw new Error(`--same-name must be new-version or ignore (got "${raw2}")`);
|
|
46412
|
+
function readUtf8(p) {
|
|
46413
|
+
return readFileSync3(p, "utf-8");
|
|
46513
46414
|
}
|
|
46514
|
-
function
|
|
46515
|
-
|
|
46516
|
-
if (match === void 0) {
|
|
46517
|
-
throw new Error(`${flag} must be ${choices.join(" or ")} (got "${raw2}")`);
|
|
46518
|
-
}
|
|
46519
|
-
return match;
|
|
46415
|
+
function collect(value, previous = []) {
|
|
46416
|
+
return [...previous, value];
|
|
46520
46417
|
}
|
|
46521
|
-
|
|
46522
|
-
|
|
46523
|
-
|
|
46524
|
-
const fields = {};
|
|
46525
|
-
if (opts.interval !== void 0) {
|
|
46526
|
-
fields.interval_seconds = parseDurationSeconds(opts.interval, "--interval");
|
|
46418
|
+
function parseScheduleFlags(opts) {
|
|
46419
|
+
if (opts.every !== void 0 && opts.cron !== void 0) {
|
|
46420
|
+
throw new Error("give --every or --cron, not both");
|
|
46527
46421
|
}
|
|
46528
|
-
|
|
46529
|
-
if (opts.
|
|
46530
|
-
if (opts.
|
|
46531
|
-
|
|
46422
|
+
const fields = {};
|
|
46423
|
+
if (opts.artifact !== void 0) fields.artifact_name = opts.artifact;
|
|
46424
|
+
if (opts.flavor !== void 0) fields.runtime_flavor = opts.flavor;
|
|
46425
|
+
if (opts.datasource !== void 0) fields.datasources = parseDatasources(opts.datasource);
|
|
46426
|
+
if (opts.primaryRole !== void 0) fields.primary_role = opts.primaryRole;
|
|
46427
|
+
if (opts.config !== void 0) fields.config = parseConfig(opts.config);
|
|
46428
|
+
if (opts.every !== void 0) fields.every_seconds = parseEvery(opts.every);
|
|
46429
|
+
if (opts.cron !== void 0) fields.cron = parseCron(opts.cron);
|
|
46430
|
+
if (opts.tz !== void 0) fields.timezone = opts.tz;
|
|
46431
|
+
if (fields.primary_role !== void 0 && fields.datasources !== void 0) {
|
|
46432
|
+
if (!Object.hasOwn(fields.datasources, fields.primary_role)) {
|
|
46433
|
+
throw new Error(
|
|
46434
|
+
`--primary-role "${fields.primary_role}" is not one of the --datasource roles`
|
|
46435
|
+
);
|
|
46436
|
+
}
|
|
46532
46437
|
}
|
|
46533
|
-
if (opts.sameName !== void 0) fields.change_policy = parseSameName(opts.sameName);
|
|
46534
46438
|
return fields;
|
|
46535
46439
|
}
|
|
46536
|
-
function
|
|
46537
|
-
const
|
|
46538
|
-
["--
|
|
46539
|
-
["--
|
|
46540
|
-
["--
|
|
46541
|
-
["--
|
|
46542
|
-
|
|
46543
|
-
|
|
46544
|
-
|
|
46440
|
+
function buildCreateFields(name, orgId, opts) {
|
|
46441
|
+
const missing = [
|
|
46442
|
+
["--artifact", opts.artifact],
|
|
46443
|
+
["--flavor", opts.flavor],
|
|
46444
|
+
["--datasource", opts.datasource],
|
|
46445
|
+
["--config", opts.config]
|
|
46446
|
+
].filter(([, v]) => v === void 0).map(([flag]) => flag);
|
|
46447
|
+
if (opts.every === void 0 && opts.cron === void 0) missing.push("--every or --cron");
|
|
46448
|
+
if (missing.length > 0) throw new Error(`missing ${missing.join(", ")}`);
|
|
46449
|
+
const fields = parseScheduleFlags(opts);
|
|
46450
|
+
return {
|
|
46451
|
+
org_id: orgId,
|
|
46452
|
+
name,
|
|
46453
|
+
artifact_name: fields.artifact_name,
|
|
46454
|
+
runtime_flavor: fields.runtime_flavor,
|
|
46455
|
+
datasources: fields.datasources,
|
|
46456
|
+
config: fields.config,
|
|
46457
|
+
...fields.primary_role !== void 0 ? { primary_role: fields.primary_role } : {},
|
|
46458
|
+
...fields.every_seconds !== void 0 ? { every_seconds: fields.every_seconds } : {},
|
|
46459
|
+
...fields.cron !== void 0 ? { cron: fields.cron } : {},
|
|
46460
|
+
...fields.timezone !== void 0 ? { timezone: fields.timezone } : {},
|
|
46461
|
+
...opts.disabled === true ? { enabled: false } : {}
|
|
46462
|
+
};
|
|
46545
46463
|
}
|
|
46546
|
-
|
|
46547
|
-
|
|
46548
|
-
|
|
46549
|
-
const
|
|
46550
|
-
const
|
|
46551
|
-
|
|
46552
|
-
);
|
|
46553
|
-
const [first] = matches;
|
|
46554
|
-
if (first === void 0) {
|
|
46464
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
46465
|
+
async function resolveTaskId(client, task, signal) {
|
|
46466
|
+
if (UUID_RE.test(task)) return task;
|
|
46467
|
+
const tasks = await listTasks(client, void 0, { signal });
|
|
46468
|
+
const match = tasks.find((t) => t.name === task);
|
|
46469
|
+
if (match === void 0) {
|
|
46555
46470
|
throw new Error(
|
|
46556
|
-
`no
|
|
46471
|
+
`no scheduled task named "${task}" in this org (see: dr connectors tasks list)`
|
|
46557
46472
|
);
|
|
46558
46473
|
}
|
|
46559
|
-
|
|
46560
|
-
const ids = matches.map((q) => `#${q.query_id}`).join(", ");
|
|
46561
|
-
throw new Error(`filebox ${wanted} has several connector bindings (${ids})`);
|
|
46562
|
-
}
|
|
46563
|
-
return first;
|
|
46474
|
+
return match.id;
|
|
46564
46475
|
}
|
|
46565
|
-
function
|
|
46566
|
-
|
|
46476
|
+
function explainTaskError(err) {
|
|
46477
|
+
const forbidden = err instanceof ControlPlaneError && err.status === 403 || err instanceof Error && /\bHTTP 403\b/.test(err.message);
|
|
46478
|
+
if (forbidden) {
|
|
46479
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
46480
|
+
const body = err instanceof ControlPlaneError ? err.body : (/HTTP 403[^:]*(?:\([^)]*\))?:\s*([\s\S]*)$/.exec(message)?.[1] ?? "").trim();
|
|
46481
|
+
return withCause(
|
|
46482
|
+
new ControlPlaneError(
|
|
46483
|
+
403,
|
|
46484
|
+
body,
|
|
46485
|
+
`${message}
|
|
46486
|
+
Scheduled tasks need a support user or an org admin: log in as one (--account) \u2014 e.g. dr login-as the customer's support user.`
|
|
46487
|
+
),
|
|
46488
|
+
err
|
|
46489
|
+
);
|
|
46490
|
+
}
|
|
46491
|
+
if (!(err instanceof ControlPlaneError)) return err;
|
|
46492
|
+
const detail = detailOf(err.body);
|
|
46493
|
+
if (err.status === 404) {
|
|
46494
|
+
const message = `scheduled task not found (${detail || err.message})`;
|
|
46495
|
+
return withCause(new ControlPlaneError(404, err.body, message), err);
|
|
46496
|
+
}
|
|
46497
|
+
if (err.status === 409 || err.status === 422) {
|
|
46498
|
+
return withCause(new ControlPlaneError(err.status, err.body, detail || err.message), err);
|
|
46499
|
+
}
|
|
46500
|
+
return err;
|
|
46567
46501
|
}
|
|
46568
|
-
function
|
|
46569
|
-
|
|
46502
|
+
function withCause(error, cause) {
|
|
46503
|
+
Object.defineProperty(error, "cause", { value: cause, enumerable: false, configurable: true });
|
|
46504
|
+
return error;
|
|
46570
46505
|
}
|
|
46571
|
-
|
|
46506
|
+
function detailOf(body) {
|
|
46572
46507
|
try {
|
|
46573
|
-
|
|
46574
|
-
|
|
46575
|
-
if (
|
|
46576
|
-
|
|
46577
|
-
|
|
46578
|
-
{
|
|
46579
|
-
);
|
|
46508
|
+
const parsed2 = JSON.parse(body);
|
|
46509
|
+
const d = parsed2.detail;
|
|
46510
|
+
if (typeof d === "string") return d;
|
|
46511
|
+
if (Array.isArray(d)) {
|
|
46512
|
+
return d.map(
|
|
46513
|
+
(e) => `${Array.isArray(e.loc) ? e.loc.slice(1).join(".") : ""}: ${e.msg ?? ""}`.trim()
|
|
46514
|
+
).join("; ");
|
|
46580
46515
|
}
|
|
46581
|
-
|
|
46516
|
+
} catch {
|
|
46582
46517
|
}
|
|
46518
|
+
return body;
|
|
46583
46519
|
}
|
|
46584
|
-
|
|
46585
|
-
|
|
46586
|
-
const
|
|
46587
|
-
const
|
|
46588
|
-
|
|
46589
|
-
|
|
46590
|
-
|
|
46591
|
-
|
|
46592
|
-
|
|
46520
|
+
function scheduleText(t) {
|
|
46521
|
+
if (t.schedule_kind === "cron") return `cron "${t.cron ?? ""}" ${t.timezone}`;
|
|
46522
|
+
const s = t.every_seconds ?? 0;
|
|
46523
|
+
const unit = s % 86400 === 0 ? `${s / 86400}d` : s % 3600 === 0 ? `${s / 3600}h` : s % 60 === 0 ? `${s / 60}m` : `${s}s`;
|
|
46524
|
+
return `every ${unit}`;
|
|
46525
|
+
}
|
|
46526
|
+
function stateText(t) {
|
|
46527
|
+
if (!t.enabled) return "disabled";
|
|
46528
|
+
if (t.paused_reason !== null) return `paused (${t.paused_reason})`;
|
|
46529
|
+
return t.running ? "running" : "enabled";
|
|
46530
|
+
}
|
|
46531
|
+
function writeTask(out, t) {
|
|
46532
|
+
const roles = Object.entries(t.datasources).map(([role, id]) => `${role}=${id}${role === t.primary_role ? " (primary)" : ""}`).join(", ");
|
|
46533
|
+
out.write(`Task: ${t.name} (${t.id})
|
|
46534
|
+
`);
|
|
46535
|
+
out.write(` Org: ${t.org_id}
|
|
46536
|
+
`);
|
|
46537
|
+
out.write(` State: ${stateText(t)}
|
|
46538
|
+
`);
|
|
46539
|
+
out.write(` Artifact: ${t.artifact_name} [${t.runtime_flavor}]
|
|
46540
|
+
`);
|
|
46541
|
+
out.write(` Schedule: ${scheduleText(t)}
|
|
46542
|
+
`);
|
|
46543
|
+
out.write(` Next run: ${t.next_run_at}
|
|
46544
|
+
`);
|
|
46545
|
+
out.write(` Last run: ${t.last_run_at ?? "-"} ${t.last_run_status ?? ""}
|
|
46546
|
+
`);
|
|
46547
|
+
if (t.consecutive_failures > 0) out.write(` Failures: ${t.consecutive_failures} in a row
|
|
46548
|
+
`);
|
|
46549
|
+
out.write(` Datasources: ${roles}
|
|
46550
|
+
`);
|
|
46551
|
+
out.write(` Config: ${truncate3(JSON.stringify(t.config), 200)}
|
|
46552
|
+
`);
|
|
46553
|
+
}
|
|
46554
|
+
function writeTaskList(out, tasks) {
|
|
46555
|
+
if (tasks.length === 0) {
|
|
46556
|
+
out.write("No scheduled tasks.\n");
|
|
46557
|
+
return;
|
|
46558
|
+
}
|
|
46559
|
+
for (const t of tasks) {
|
|
46560
|
+
out.write(
|
|
46561
|
+
`${t.name.padEnd(28)} ${stateText(t).padEnd(20)} ${scheduleText(t).padEnd(26)} next ${t.next_run_at} last ${t.last_run_status ?? "-"} ${t.id}
|
|
46562
|
+
`
|
|
46593
46563
|
);
|
|
46594
46564
|
}
|
|
46595
|
-
return { id: only.id, name: only.name };
|
|
46596
46565
|
}
|
|
46597
|
-
function
|
|
46598
|
-
if (
|
|
46599
|
-
|
|
46600
|
-
|
|
46601
|
-
|
|
46566
|
+
function writeRuns(out, runs) {
|
|
46567
|
+
if (runs.length === 0) {
|
|
46568
|
+
out.write("No runs yet.\n");
|
|
46569
|
+
return;
|
|
46570
|
+
}
|
|
46571
|
+
for (const r of runs) {
|
|
46572
|
+
const tail = r.error !== null ? ` error: ${truncate3(r.error, 160)}` : r.summary !== null ? ` ${truncate3(JSON.stringify(r.summary), 160)}` : "";
|
|
46573
|
+
out.write(
|
|
46574
|
+
`${r.started_at} ${r.status.padEnd(15)} ${r.trigger.padEnd(8)} attempt ${r.attempt} ${r.artifact_version ?? "-"}${tail}
|
|
46575
|
+
`
|
|
46576
|
+
);
|
|
46577
|
+
}
|
|
46602
46578
|
}
|
|
46603
|
-
function
|
|
46604
|
-
|
|
46579
|
+
function withFlags(cmd) {
|
|
46580
|
+
cmd.option("--json", "Output raw JSON instead of a summary", false);
|
|
46581
|
+
addServerFlag(cmd);
|
|
46582
|
+
addAccountFlag(cmd);
|
|
46583
|
+
addConnectorsUrlFlag(cmd);
|
|
46584
|
+
return cmd;
|
|
46605
46585
|
}
|
|
46606
|
-
function
|
|
46607
|
-
return
|
|
46586
|
+
function addScheduleFlags(cmd) {
|
|
46587
|
+
return cmd.option("--artifact <name>", "published artifact name, e.g. sftp_export__daily_sales").option("--flavor <flavor>", "runner flavor the artifact was published for, e.g. sftp").option("--datasource <role=id>", "monolith datasource for a role (repeat per role)", collect).option(
|
|
46588
|
+
"--primary-role <role>",
|
|
46589
|
+
"the role passed as the primary datasource (default: the first)"
|
|
46590
|
+
).option("--config <json|@file>", "task config: a JSON object, or @path to a JSON file").option("--every <duration>", "run on an interval, e.g. 5m, 1h (at least 1m)").option("--cron <expr>", 'run on a five-field cron line, e.g. "15 * * * *"').option("--tz <iana>", "timezone the cron line is read in (default UTC)");
|
|
46608
46591
|
}
|
|
46609
|
-
function
|
|
46610
|
-
|
|
46611
|
-
|
|
46592
|
+
async function onTask(task, opts, act, print) {
|
|
46593
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
46594
|
+
try {
|
|
46595
|
+
const taskId = await resolveTaskId(client, task, signal);
|
|
46596
|
+
const result = await act(client, taskId, signal);
|
|
46597
|
+
if (opts.json === true) writeJSONIndented(process.stdout, result);
|
|
46598
|
+
else print(process.stdout, result);
|
|
46599
|
+
} catch (err) {
|
|
46600
|
+
throw explainTaskError(err);
|
|
46601
|
+
}
|
|
46602
|
+
});
|
|
46612
46603
|
}
|
|
46613
|
-
function
|
|
46614
|
-
|
|
46604
|
+
function registerConnectorsTasks(connectors) {
|
|
46605
|
+
const tasks = connectors.command("tasks").description("DRIP scheduled tasks: run an artifact on a schedule (support user or org admin)");
|
|
46606
|
+
withFlags(
|
|
46607
|
+
addScheduleFlags(
|
|
46608
|
+
tasks.command("create").description("Create a scheduled task").argument("<name>", "task name, unique in the org (a-z, 0-9, _ and -)").option("--org-id <id>", "org id (defaults to the JWT org)").option("--disabled", "create it disabled", false)
|
|
46609
|
+
)
|
|
46610
|
+
).action(
|
|
46611
|
+
async (name, opts) => {
|
|
46612
|
+
const explicitOrg = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
|
|
46613
|
+
const parsed2 = buildCreateFields(name, explicitOrg ?? 0, opts);
|
|
46614
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
46615
|
+
const fields = { ...parsed2, org_id: explicitOrg ?? client.getCurrentOrgId() };
|
|
46616
|
+
try {
|
|
46617
|
+
const t = await createTask(client, fields, { signal });
|
|
46618
|
+
if (opts.json === true) writeJSONIndented(process.stdout, t);
|
|
46619
|
+
else writeTask(process.stdout, t);
|
|
46620
|
+
} catch (err) {
|
|
46621
|
+
throw explainTaskError(err);
|
|
46622
|
+
}
|
|
46623
|
+
});
|
|
46624
|
+
}
|
|
46625
|
+
);
|
|
46626
|
+
withFlags(
|
|
46627
|
+
tasks.command("list").description("List the org's scheduled tasks").option("--org-id <id>", "org id (defaults to the JWT org)")
|
|
46628
|
+
).action(async (opts) => {
|
|
46629
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
46630
|
+
const orgId = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
|
|
46631
|
+
try {
|
|
46632
|
+
const list28 = await listTasks(client, orgId, { signal });
|
|
46633
|
+
if (opts.json === true) writeJSONIndented(process.stdout, list28);
|
|
46634
|
+
else writeTaskList(process.stdout, list28);
|
|
46635
|
+
} catch (err) {
|
|
46636
|
+
throw explainTaskError(err);
|
|
46637
|
+
}
|
|
46638
|
+
});
|
|
46639
|
+
});
|
|
46640
|
+
withFlags(
|
|
46641
|
+
tasks.command("get").description("Show a scheduled task").argument("<task>", "task id or name")
|
|
46642
|
+
).action(
|
|
46643
|
+
async (task, opts) => onTask(
|
|
46644
|
+
task,
|
|
46645
|
+
opts,
|
|
46646
|
+
(c, id, signal) => getTask(c, id, { signal }),
|
|
46647
|
+
(o, r) => writeTask(o, r)
|
|
46648
|
+
)
|
|
46649
|
+
);
|
|
46650
|
+
withFlags(
|
|
46651
|
+
addScheduleFlags(
|
|
46652
|
+
tasks.command("update").description("Change a scheduled task (only the flags given change)").argument("<task>", "task id or name").option("--enable", "enable the task").option("--disable", "disable the task")
|
|
46653
|
+
)
|
|
46654
|
+
).action(
|
|
46655
|
+
async (task, opts) => {
|
|
46656
|
+
if (opts.enable === true && opts.disable === true)
|
|
46657
|
+
throw new Error("give --enable or --disable, not both");
|
|
46658
|
+
const fields = parseScheduleFlags(opts);
|
|
46659
|
+
if (opts.enable === true) fields.enabled = true;
|
|
46660
|
+
if (opts.disable === true) fields.enabled = false;
|
|
46661
|
+
if (Object.keys(fields).length === 0)
|
|
46662
|
+
throw new Error("nothing to change: give at least one flag");
|
|
46663
|
+
await onTask(
|
|
46664
|
+
task,
|
|
46665
|
+
opts,
|
|
46666
|
+
(c, id, signal) => updateTask(c, id, fields, { signal }),
|
|
46667
|
+
(o, r) => writeTask(o, r)
|
|
46668
|
+
);
|
|
46669
|
+
}
|
|
46670
|
+
);
|
|
46671
|
+
withFlags(
|
|
46672
|
+
tasks.command("delete").description("Delete a scheduled task and its run history").argument("<task>", "task id or name")
|
|
46673
|
+
).action(
|
|
46674
|
+
async (task, opts) => onTask(
|
|
46675
|
+
task,
|
|
46676
|
+
opts,
|
|
46677
|
+
async (c, id, signal) => {
|
|
46678
|
+
await deleteTask(c, id, { signal });
|
|
46679
|
+
return { deleted: id };
|
|
46680
|
+
},
|
|
46681
|
+
(o, r) => o.write(`Deleted scheduled task ${r.deleted}
|
|
46682
|
+
`)
|
|
46683
|
+
)
|
|
46684
|
+
);
|
|
46685
|
+
withFlags(
|
|
46686
|
+
tasks.command("run").description("Run a task now (does not move its schedule)").argument("<task>", "task id or name")
|
|
46687
|
+
).action(
|
|
46688
|
+
async (task, opts) => onTask(
|
|
46689
|
+
task,
|
|
46690
|
+
opts,
|
|
46691
|
+
(c, id, signal) => runTask(c, id, { signal }),
|
|
46692
|
+
(o, r) => {
|
|
46693
|
+
const run2 = r;
|
|
46694
|
+
o.write(`Queued run ${run2.id} (${run2.status}); see: dr connectors tasks runs ${task}
|
|
46695
|
+
`);
|
|
46696
|
+
}
|
|
46697
|
+
)
|
|
46698
|
+
);
|
|
46699
|
+
withFlags(
|
|
46700
|
+
tasks.command("pause").description("Pause a scheduled task").argument("<task>", "task id or name")
|
|
46701
|
+
).action(
|
|
46702
|
+
async (task, opts) => onTask(
|
|
46703
|
+
task,
|
|
46704
|
+
opts,
|
|
46705
|
+
(c, id, signal) => pauseTask(c, id, { signal }),
|
|
46706
|
+
(o, r) => writeTask(o, r)
|
|
46707
|
+
)
|
|
46708
|
+
);
|
|
46709
|
+
withFlags(
|
|
46710
|
+
tasks.command("resume").description("Resume a paused task (clears its failure count, schedules the next run)").argument("<task>", "task id or name")
|
|
46711
|
+
).action(
|
|
46712
|
+
async (task, opts) => onTask(
|
|
46713
|
+
task,
|
|
46714
|
+
opts,
|
|
46715
|
+
(c, id, signal) => resumeTask(c, id, { signal }),
|
|
46716
|
+
(o, r) => writeTask(o, r)
|
|
46717
|
+
)
|
|
46718
|
+
);
|
|
46719
|
+
withFlags(
|
|
46720
|
+
tasks.command("runs").description("A task's run history, newest first").argument("<task>", "task id or name").option("--limit <n>", "how many runs (1-200)", "20")
|
|
46721
|
+
).action(async (task, opts) => {
|
|
46722
|
+
const limit = parsePositiveIntArg2(opts.limit, "--limit");
|
|
46723
|
+
if (limit > 200) throw new Error("invalid --limit: at most 200");
|
|
46724
|
+
await onTask(
|
|
46725
|
+
task,
|
|
46726
|
+
opts,
|
|
46727
|
+
(c, id, signal) => listRuns(c, id, limit, { signal }),
|
|
46728
|
+
(o, r) => writeRuns(o, r)
|
|
46729
|
+
);
|
|
46730
|
+
});
|
|
46615
46731
|
}
|
|
46616
|
-
|
|
46732
|
+
|
|
46733
|
+
// src/commands/connectorsAws.ts
|
|
46734
|
+
var ROLE_ARN_RE = /^arn:aws[a-z-]*:iam::\d{12}:role\/\S+$/;
|
|
46735
|
+
var OTHER_ORG_HINT = "if your session is valid, the connection or --org-id may belong to another org than the --account you are logged in as";
|
|
46736
|
+
function explainAwsError(err, id) {
|
|
46737
|
+
if (!(err instanceof ControlPlaneError)) {
|
|
46738
|
+
if (err instanceof Error && /\bHTTP 403\b/.test(err.message)) {
|
|
46739
|
+
return withCause(
|
|
46740
|
+
new ControlPlaneError(
|
|
46741
|
+
403,
|
|
46742
|
+
"",
|
|
46743
|
+
`${err.message}
|
|
46744
|
+
${OTHER_ORG_HINT}`
|
|
46745
|
+
),
|
|
46746
|
+
err
|
|
46747
|
+
);
|
|
46748
|
+
}
|
|
46749
|
+
return err;
|
|
46750
|
+
}
|
|
46751
|
+
if (!hasDetail(err.body)) return err;
|
|
46752
|
+
const detail = detailOf(err.body) || err.message;
|
|
46753
|
+
const messages = {
|
|
46754
|
+
403: `forbidden: ${detail} (${OTHER_ORG_HINT})`,
|
|
46755
|
+
404: `AWS connection${id === void 0 ? "" : ` ${id}`} not found: ${detail}`,
|
|
46756
|
+
409: `conflict: ${detail}`,
|
|
46757
|
+
422: `rejected: ${detail}`,
|
|
46758
|
+
503: `the control plane cannot broker AWS roles on this site: ${detail}`
|
|
46759
|
+
};
|
|
46760
|
+
const message = messages[err.status];
|
|
46761
|
+
if (message === void 0) return err;
|
|
46762
|
+
return withCause(new ControlPlaneError(err.status, err.body, message), err);
|
|
46763
|
+
}
|
|
46764
|
+
function hasDetail(body) {
|
|
46765
|
+
try {
|
|
46766
|
+
const parsed2 = JSON.parse(body);
|
|
46767
|
+
return typeof parsed2 === "object" && parsed2 !== null && "detail" in parsed2;
|
|
46768
|
+
} catch {
|
|
46769
|
+
return false;
|
|
46770
|
+
}
|
|
46771
|
+
}
|
|
46772
|
+
function oneLine3(s) {
|
|
46773
|
+
return s.replace(/\s+/g, " ").trim();
|
|
46774
|
+
}
|
|
46775
|
+
function writeConnection(out, c) {
|
|
46617
46776
|
const row = (label, value) => {
|
|
46618
|
-
out.write(` ${`${label}:`.padEnd(
|
|
46777
|
+
out.write(` ${`${label}:`.padEnd(14)}${value}
|
|
46619
46778
|
`);
|
|
46620
46779
|
};
|
|
46621
|
-
|
|
46622
|
-
|
|
46623
|
-
|
|
46780
|
+
out.write(`AWS connection #${c.id} (org ${c.org_id})
|
|
46781
|
+
`);
|
|
46782
|
+
row("Datasource", c.secret_ref);
|
|
46783
|
+
row("Role ARN", c.role_arn);
|
|
46784
|
+
row("Status", `${c.status}${c.enabled ? "" : " (disabled)"}`);
|
|
46785
|
+
row("Broker ARN", c.broker_arn ?? "-");
|
|
46786
|
+
row("External ID", c.external_id);
|
|
46787
|
+
row("Expires", c.expires_at ?? "-");
|
|
46788
|
+
row("Last assumed", c.last_assumed_at ?? "-");
|
|
46789
|
+
row("Last error", c.last_error === null ? "-" : oneLine3(c.last_error));
|
|
46790
|
+
row("Failures", String(c.consecutive_failures));
|
|
46791
|
+
if (c.trust_statement === null) {
|
|
46792
|
+
out.write("\nTrust statement: none returned by the control plane.\n");
|
|
46793
|
+
} else {
|
|
46794
|
+
out.write("\nTrust statement (add it to the customer role's trust policy):\n");
|
|
46795
|
+
out.write(`${JSON.stringify(c.trust_statement, null, 2)}
|
|
46624
46796
|
`);
|
|
46625
|
-
|
|
46626
|
-
|
|
46627
|
-
|
|
46628
|
-
row("Since", p.since ?? "-");
|
|
46629
|
-
row("Lookback", `${p.lookback_days}d`);
|
|
46630
|
-
row("Settle", formatSeconds(p.settle_seconds));
|
|
46631
|
-
row("Same name", sameNameLabel(p.change_policy));
|
|
46632
|
-
row("Max attempts", String(p.max_attempts));
|
|
46633
|
-
row("Last poll", `${p.last_poll_at ?? "-"} ${oneLine3(p.last_poll_status ?? "")}`.trimEnd());
|
|
46634
|
-
row("Next poll", p.next_poll_at);
|
|
46635
|
-
row("Failures", String(p.consecutive_failures));
|
|
46636
|
-
row("Discover", p.discover_in_flight ? `in flight since ${p.discover_sent_at ?? "?"}` : "-");
|
|
46637
|
-
row("Last digest", p.last_digest ?? "-");
|
|
46638
|
-
row("Pending seed", p.poll_run_seed_until ?? "-");
|
|
46639
|
-
row(
|
|
46640
|
-
"Pending rekey",
|
|
46641
|
-
p.poll_run_rekey_to === null ? "-" : `to ${sameNameLabel(p.poll_run_rekey_to)}`
|
|
46797
|
+
}
|
|
46798
|
+
out.write(
|
|
46799
|
+
"\nThe customer role's permissions should be read-only on their bucket/prefix (plus kms:Decrypt for a customer-managed KMS key).\n"
|
|
46642
46800
|
);
|
|
46643
|
-
row("Items", counts === "" ? "(none)" : counts);
|
|
46644
46801
|
}
|
|
46645
|
-
function
|
|
46646
|
-
|
|
46647
|
-
|
|
46648
|
-
|
|
46649
|
-
|
|
46650
|
-
|
|
46802
|
+
function writeConnectionTable(out, list28) {
|
|
46803
|
+
if (list28.length === 0) {
|
|
46804
|
+
out.write("No AWS connections for this org.\n");
|
|
46805
|
+
return;
|
|
46806
|
+
}
|
|
46807
|
+
const rows = [["ID", "SECRET_REF", "ROLE_ARN", "STATUS", "EXPIRES_AT", "LAST_ERROR"]];
|
|
46808
|
+
for (const c of list28) {
|
|
46809
|
+
rows.push([
|
|
46810
|
+
String(c.id),
|
|
46811
|
+
oneLine3(c.secret_ref),
|
|
46812
|
+
oneLine3(c.role_arn),
|
|
46813
|
+
c.status,
|
|
46814
|
+
c.expires_at ?? "-",
|
|
46815
|
+
c.last_error === null ? "-" : truncate3(oneLine3(c.last_error), 60)
|
|
46816
|
+
]);
|
|
46651
46817
|
}
|
|
46818
|
+
const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => r[i].length)));
|
|
46652
46819
|
for (const r of rows) {
|
|
46653
|
-
|
|
46654
|
-
(cell, i) =>
|
|
46820
|
+
out.write(
|
|
46821
|
+
`${r.map((cell, i) => cell.padEnd(widths[i] + 2)).join("").trimEnd()}
|
|
46822
|
+
`
|
|
46655
46823
|
);
|
|
46656
|
-
out.write(`${cells.join("").trimEnd()}
|
|
46657
|
-
`);
|
|
46658
46824
|
}
|
|
46659
46825
|
}
|
|
46660
|
-
function
|
|
46661
|
-
|
|
46662
|
-
|
|
46663
|
-
|
|
46664
|
-
|
|
46665
|
-
|
|
46666
|
-
|
|
46667
|
-
|
|
46668
|
-
|
|
46669
|
-
|
|
46670
|
-
|
|
46671
|
-
|
|
46672
|
-
|
|
46826
|
+
function withFlags2(cmd) {
|
|
46827
|
+
cmd.option("--json", "Output raw JSON instead of a summary", false);
|
|
46828
|
+
addServerFlag(cmd);
|
|
46829
|
+
addAccountFlag(cmd);
|
|
46830
|
+
addConnectorsUrlFlag(cmd);
|
|
46831
|
+
return cmd;
|
|
46832
|
+
}
|
|
46833
|
+
async function run(opts, act, print, id) {
|
|
46834
|
+
await withConnectorsSetup(opts, async (setup) => {
|
|
46835
|
+
let result;
|
|
46836
|
+
try {
|
|
46837
|
+
result = await act(setup);
|
|
46838
|
+
} catch (err) {
|
|
46839
|
+
throw explainAwsError(err, id);
|
|
46840
|
+
}
|
|
46841
|
+
if (opts.json === true) writeJSONIndented(process.stdout, result);
|
|
46842
|
+
else print(process.stdout, result);
|
|
46843
|
+
});
|
|
46844
|
+
}
|
|
46845
|
+
function registerConnectorsAws(connectors) {
|
|
46846
|
+
const aws = connectors.command("aws").description("Customer IAM roles DRIP assumes to read the customer's S3 (external ID, trust)");
|
|
46847
|
+
withFlags2(
|
|
46848
|
+
aws.command("create").description("Register a customer IAM role for a datasource; prints the trust statement").requiredOption("--secret-ref <datasourceId>", "monolith datasource id the role serves").requiredOption("--role-arn <arn>", "customer role, e.g. arn:aws:iam::123456789012:role/x").option("--org-id <id>", "org id (defaults to the JWT org)")
|
|
46849
|
+
).action(async (opts) => {
|
|
46850
|
+
const secretRef = opts.secretRef.trim();
|
|
46851
|
+
if (secretRef === "" || secretRef.length > 200) {
|
|
46852
|
+
throw new Error(`invalid --secret-ref "${opts.secretRef}": expected 1-200 characters`);
|
|
46853
|
+
}
|
|
46854
|
+
if (!ROLE_ARN_RE.test(opts.roleArn)) {
|
|
46855
|
+
throw new Error(
|
|
46856
|
+
`invalid --role-arn "${opts.roleArn}": expected arn:aws:iam::<12-digit account>:role/<name>`
|
|
46857
|
+
);
|
|
46858
|
+
}
|
|
46859
|
+
const orgId = opts.orgId === void 0 ? void 0 : parsePositiveIntArg2(opts.orgId, "--org-id");
|
|
46860
|
+
const fields = {
|
|
46861
|
+
secret_ref: secretRef,
|
|
46862
|
+
role_arn: opts.roleArn,
|
|
46863
|
+
...orgId !== void 0 ? { org_id: orgId } : {}
|
|
46864
|
+
};
|
|
46865
|
+
await run(
|
|
46866
|
+
opts,
|
|
46867
|
+
({ client, signal }) => createAwsConnection(client, fields, { signal }),
|
|
46868
|
+
writeConnection
|
|
46869
|
+
);
|
|
46870
|
+
});
|
|
46871
|
+
withFlags2(
|
|
46872
|
+
aws.command("list").description("List the org's AWS connections").option("--secret-ref <datasourceId>", "only the connection of this datasource")
|
|
46873
|
+
).action(async (opts) => {
|
|
46874
|
+
await run(
|
|
46875
|
+
opts,
|
|
46876
|
+
({ client, signal }) => listAwsConnections(client, opts.secretRef, { signal }),
|
|
46877
|
+
writeConnectionTable
|
|
46878
|
+
);
|
|
46879
|
+
});
|
|
46880
|
+
withFlags2(
|
|
46881
|
+
aws.command("get").description("Show an AWS connection and its trust statement").argument("<id>", "connection id")
|
|
46882
|
+
).action(async (idArg, opts) => {
|
|
46883
|
+
const id = parsePositiveIntArg2(idArg, "connection id");
|
|
46884
|
+
await run(
|
|
46885
|
+
opts,
|
|
46886
|
+
({ client, signal }) => getAwsConnection(client, id, { signal }),
|
|
46887
|
+
writeConnection,
|
|
46888
|
+
id
|
|
46889
|
+
);
|
|
46890
|
+
});
|
|
46891
|
+
for (const [verb, enabled, help] of [
|
|
46892
|
+
["enable", true, "Enable an AWS connection; also retries now (stored credentials are dropped)"],
|
|
46893
|
+
["disable", false, "Disable an AWS connection (stored credentials are dropped)"]
|
|
46894
|
+
]) {
|
|
46895
|
+
withFlags2(aws.command(verb).description(help).argument("<id>", "connection id")).action(
|
|
46896
|
+
async (idArg, opts) => {
|
|
46897
|
+
const id = parsePositiveIntArg2(idArg, "connection id");
|
|
46898
|
+
await run(
|
|
46899
|
+
opts,
|
|
46900
|
+
({ client, signal }) => updateAwsConnection(client, id, { enabled }, { signal }),
|
|
46901
|
+
(out, c) => {
|
|
46902
|
+
writeConnection(out, c);
|
|
46903
|
+
if (enabled)
|
|
46904
|
+
out.write(
|
|
46905
|
+
"\nStored credentials were dropped; the poller assumes the role again now.\n"
|
|
46906
|
+
);
|
|
46907
|
+
},
|
|
46908
|
+
id
|
|
46909
|
+
);
|
|
46910
|
+
}
|
|
46911
|
+
);
|
|
46673
46912
|
}
|
|
46674
|
-
writeTable26(out, rows);
|
|
46675
46913
|
}
|
|
46676
|
-
|
|
46677
|
-
|
|
46678
|
-
|
|
46679
|
-
|
|
46680
|
-
|
|
46681
|
-
|
|
46682
|
-
|
|
46683
|
-
|
|
46914
|
+
|
|
46915
|
+
// src/commands/connectorsDevFetch.ts
|
|
46916
|
+
import { promises as fs19 } from "fs";
|
|
46917
|
+
import * as path9 from "path";
|
|
46918
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
46919
|
+
function buildDevEnvelope(i) {
|
|
46920
|
+
const config = {
|
|
46921
|
+
...i.publicConfig,
|
|
46922
|
+
...i.queryConfig,
|
|
46923
|
+
// Coerce absent values to null (not undefined) so JSON.stringify keeps the
|
|
46924
|
+
// key — matches build_hook_job's dq.get(...) -> None; a dropped objectType
|
|
46925
|
+
// becomes a cryptic KeyError in the runner's dispatch.
|
|
46926
|
+
objectType: i.dataQuery.objectType ?? null,
|
|
46927
|
+
type: i.dataQuery.type ?? null,
|
|
46928
|
+
query: i.dataQuery.query
|
|
46929
|
+
};
|
|
46930
|
+
if (i.period) config.period = i.period;
|
|
46931
|
+
return {
|
|
46932
|
+
correlation_id: i.correlationId,
|
|
46933
|
+
config,
|
|
46934
|
+
secret_ref: i.secretRef,
|
|
46935
|
+
cred_token: "dev-local",
|
|
46936
|
+
output_sas: "file:///out/output.csv",
|
|
46937
|
+
input_sas: null,
|
|
46938
|
+
cursor: null,
|
|
46939
|
+
contract_version: i.contractVersion,
|
|
46940
|
+
metadata: {}
|
|
46941
|
+
};
|
|
46942
|
+
}
|
|
46943
|
+
function asObject(v) {
|
|
46944
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
46945
|
+
return v;
|
|
46684
46946
|
}
|
|
46685
|
-
|
|
46686
|
-
|
|
46947
|
+
return null;
|
|
46948
|
+
}
|
|
46949
|
+
function extractDataQueryFields(raw2) {
|
|
46950
|
+
const root = asObject(decodeShape(raw2));
|
|
46951
|
+
if (root === null || Object.keys(root).length === 0) {
|
|
46687
46952
|
throw new Error(
|
|
46688
|
-
|
|
46953
|
+
"no data-query attached to this filebox (empty data_query response) \u2014 attach one in the filebox UI first"
|
|
46689
46954
|
);
|
|
46690
46955
|
}
|
|
46691
|
-
|
|
46956
|
+
const cfg = asObject(root.config);
|
|
46957
|
+
const src = cfg ?? root;
|
|
46958
|
+
const query = src.query;
|
|
46959
|
+
let queryObj;
|
|
46960
|
+
if (query === void 0 || query === null) {
|
|
46961
|
+
queryObj = {};
|
|
46962
|
+
} else {
|
|
46963
|
+
const asObj = asObject(query);
|
|
46964
|
+
if (asObj === null) {
|
|
46965
|
+
throw new Error(
|
|
46966
|
+
`data-query \`query\` is not an object (got ${typeof query}) \u2014 expected the Sync-query field`
|
|
46967
|
+
);
|
|
46968
|
+
}
|
|
46969
|
+
queryObj = asObj;
|
|
46970
|
+
}
|
|
46971
|
+
return { objectType: queryObj.objectType, type: src.type, query: queryObj };
|
|
46972
|
+
}
|
|
46973
|
+
function extractCredentials(integration) {
|
|
46974
|
+
const cfg = asObject(integration.config);
|
|
46975
|
+
if (cfg === null || Object.keys(cfg).length === 0) {
|
|
46692
46976
|
throw new Error(
|
|
46693
|
-
`
|
|
46977
|
+
`datasource ${integration.id} has an empty/missing \`config\` \u2014 nothing to use as connector credentials`
|
|
46694
46978
|
);
|
|
46695
46979
|
}
|
|
46696
|
-
|
|
46697
|
-
|
|
46698
|
-
|
|
46699
|
-
|
|
46700
|
-
|
|
46701
|
-
|
|
46980
|
+
return { ...cfg };
|
|
46981
|
+
}
|
|
46982
|
+
var ENV_FILE = "env.json";
|
|
46983
|
+
var SECRETS_FILE = "secrets.json";
|
|
46984
|
+
var CONNECTION_FILE = "connection.py";
|
|
46985
|
+
var QUERY_FILE = "query.py";
|
|
46986
|
+
async function writeRunDir(input) {
|
|
46987
|
+
let connectionSource;
|
|
46988
|
+
try {
|
|
46989
|
+
connectionSource = await fs19.readFile(input.connectionPath, "utf-8");
|
|
46990
|
+
} catch {
|
|
46991
|
+
throw new Error(`${input.connectionPath}: connection source not found or unreadable`);
|
|
46992
|
+
}
|
|
46993
|
+
let querySource;
|
|
46994
|
+
try {
|
|
46995
|
+
querySource = await fs19.readFile(input.queryPath, "utf-8");
|
|
46996
|
+
} catch {
|
|
46997
|
+
throw new Error(`${input.queryPath}: dataquery source not found or unreadable`);
|
|
46998
|
+
}
|
|
46999
|
+
await fs19.mkdir(input.outDir, { recursive: true });
|
|
47000
|
+
await fs19.writeFile(
|
|
47001
|
+
path9.join(input.outDir, ENV_FILE),
|
|
47002
|
+
JSON.stringify(input.env, null, 2) + "\n",
|
|
47003
|
+
"utf-8"
|
|
46702
47004
|
);
|
|
46703
|
-
|
|
46704
|
-
|
|
46705
|
-
|
|
47005
|
+
const secretsPath = path9.join(input.outDir, SECRETS_FILE);
|
|
47006
|
+
await fs19.writeFile(
|
|
47007
|
+
secretsPath,
|
|
47008
|
+
JSON.stringify({ [input.secretRef]: input.credentials }, null, 2) + "\n",
|
|
47009
|
+
"utf-8"
|
|
47010
|
+
);
|
|
47011
|
+
if (process.platform !== "win32") {
|
|
47012
|
+
await fs19.chmod(secretsPath, 384);
|
|
46706
47013
|
}
|
|
46707
|
-
|
|
47014
|
+
await fs19.writeFile(path9.join(input.outDir, CONNECTION_FILE), connectionSource, "utf-8");
|
|
47015
|
+
await fs19.writeFile(path9.join(input.outDir, QUERY_FILE), querySource, "utf-8");
|
|
46708
47016
|
}
|
|
46709
|
-
function
|
|
46710
|
-
const
|
|
46711
|
-
|
|
47017
|
+
async function ensureGitignored(entry) {
|
|
47018
|
+
const gitignorePath = path9.join(process.cwd(), ".gitignore");
|
|
47019
|
+
let current = "";
|
|
47020
|
+
try {
|
|
47021
|
+
current = await fs19.readFile(gitignorePath, "utf-8");
|
|
47022
|
+
} catch {
|
|
47023
|
+
}
|
|
47024
|
+
const normalized = entry.split("\\").join("/").replace(/\/+$/, "") + "/";
|
|
47025
|
+
const lines = current.split(/\r?\n/).map((l) => l.trim());
|
|
47026
|
+
if (lines.includes(normalized) || lines.includes(entry)) return;
|
|
47027
|
+
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
47028
|
+
await fs19.writeFile(gitignorePath, `${current}${sep}${normalized}
|
|
47029
|
+
`, "utf-8");
|
|
46712
47030
|
}
|
|
46713
|
-
function
|
|
47031
|
+
function parsePositiveInt3(raw2, label) {
|
|
47032
|
+
const n = Number(raw2);
|
|
47033
|
+
if (!Number.isInteger(n) || String(n) !== raw2.trim() || n <= 0) {
|
|
47034
|
+
throw new Error(`invalid ${label}: "${raw2}"`);
|
|
47035
|
+
}
|
|
47036
|
+
return n;
|
|
47037
|
+
}
|
|
47038
|
+
function registerDevFetch(connectors) {
|
|
47039
|
+
const cmd = connectors.command("dev-fetch").description(
|
|
47040
|
+
"Assemble a local run dir (env.json + secrets.json + connection.py + query.py) for dev-run.sh"
|
|
47041
|
+
).argument("<fileboxUuid>", "filebox UUID of the connector binding").requiredOption(
|
|
47042
|
+
"--datasource <id>",
|
|
47043
|
+
"datasource (data-integration) id whose credentials to fetch"
|
|
47044
|
+
).requiredOption(
|
|
47045
|
+
"--document-id <n>",
|
|
47046
|
+
"numeric filebox document id (no uuid->id lookup exists; required)"
|
|
47047
|
+
).requiredOption("--connection <path>", "path to the connector's connection .py source").requiredOption("--query <path>", "path to the connector's dataquery .py source").option("--from <date>", "period start (paired with --to)").option("--to <date>", "period end (paired with --from)").option("-o, --output <dir>", "output run dir (default ./dev-run/<fileboxUuid>/)").action(async (fileboxUuid, opts) => {
|
|
47048
|
+
const datasourceId = parsePositiveInt3(opts.datasource, "datasource id");
|
|
47049
|
+
const documentId = parsePositiveInt3(opts.documentId, "document-id");
|
|
47050
|
+
if (opts.from === void 0 !== (opts.to === void 0)) {
|
|
47051
|
+
throw new Error("--from and --to must be provided together");
|
|
47052
|
+
}
|
|
47053
|
+
const defaultRoot = path9.join("dev-run", fileboxUuid);
|
|
47054
|
+
const outDir = opts.output ?? defaultRoot;
|
|
47055
|
+
const gitignoreEntry = opts.output ?? "dev-run";
|
|
47056
|
+
const serverKey = resolveServer(opts.server);
|
|
47057
|
+
const host = resolveConnectorsUrl(serverKey, opts.connectorsUrl);
|
|
47058
|
+
const monolithHost = resolve(serverKey).host;
|
|
47059
|
+
const cpSetup = await setupCall(opts.server, opts.account, { host, authHost: monolithHost });
|
|
47060
|
+
try {
|
|
47061
|
+
const binding = await getBinding(cpSetup.client, fileboxUuid, {
|
|
47062
|
+
signal: cpSetup.signal
|
|
47063
|
+
});
|
|
47064
|
+
const monoSetup = await setupCall(opts.server, opts.account, {
|
|
47065
|
+
host: monolithHost,
|
|
47066
|
+
authHost: monolithHost
|
|
47067
|
+
});
|
|
47068
|
+
try {
|
|
47069
|
+
const rawDataQuery = await getDataQueryRaw(monoSetup.client, documentId, {
|
|
47070
|
+
signal: monoSetup.signal
|
|
47071
|
+
});
|
|
47072
|
+
const dataQuery = extractDataQueryFields(rawDataQuery);
|
|
47073
|
+
if (dataQuery.objectType === void 0 || dataQuery.objectType === null || dataQuery.objectType === "") {
|
|
47074
|
+
process.stderr.write(
|
|
47075
|
+
'WARNING: objectType is not set inside the Sync-query (config.query.objectType). get_query_class will receive None \u2014 fine only if the connector has a single default dataset. Add it to the data-query, e.g. config.query.objectType = "<dataset>".\n'
|
|
47076
|
+
);
|
|
47077
|
+
}
|
|
47078
|
+
const integration = await itGet(monoSetup.client, datasourceId, {
|
|
47079
|
+
signal: monoSetup.signal
|
|
47080
|
+
});
|
|
47081
|
+
const credentials = extractCredentials(integration);
|
|
47082
|
+
let period;
|
|
47083
|
+
if (opts.from !== void 0 && opts.to !== void 0) {
|
|
47084
|
+
period = { from: opts.from, to: opts.to };
|
|
47085
|
+
}
|
|
47086
|
+
const env = buildDevEnvelope({
|
|
47087
|
+
correlationId: randomUUID2(),
|
|
47088
|
+
secretRef: binding.secret_ref,
|
|
47089
|
+
publicConfig: binding.public_config,
|
|
47090
|
+
queryConfig: binding.query_config,
|
|
47091
|
+
dataQuery,
|
|
47092
|
+
...period !== void 0 ? { period } : {},
|
|
47093
|
+
// Deliberate sandbox stub: the fetch/dispatch path doesn't branch on
|
|
47094
|
+
// contract_version, so a fixed "1" is sufficient for a local run.
|
|
47095
|
+
contractVersion: "1"
|
|
47096
|
+
});
|
|
47097
|
+
await writeRunDir({
|
|
47098
|
+
outDir,
|
|
47099
|
+
env,
|
|
47100
|
+
secretRef: binding.secret_ref,
|
|
47101
|
+
credentials,
|
|
47102
|
+
connectionPath: opts.connection,
|
|
47103
|
+
queryPath: opts.query
|
|
47104
|
+
});
|
|
47105
|
+
await ensureGitignored(gitignoreEntry);
|
|
47106
|
+
process.stdout.write(`Wrote run dir: ${outDir}
|
|
47107
|
+
`);
|
|
47108
|
+
process.stdout.write(
|
|
47109
|
+
` env.json (secret_ref=${binding.secret_ref}, flavor=${binding.runtime_flavor})
|
|
47110
|
+
`
|
|
47111
|
+
);
|
|
47112
|
+
process.stdout.write(" secrets.json (credentials NOT shown)\n");
|
|
47113
|
+
process.stdout.write(" connection.py, query.py\n");
|
|
47114
|
+
} finally {
|
|
47115
|
+
monoSetup.cleanup();
|
|
47116
|
+
}
|
|
47117
|
+
} catch (err) {
|
|
47118
|
+
throw augmentTlsCertError(err);
|
|
47119
|
+
} finally {
|
|
47120
|
+
cpSetup.cleanup();
|
|
47121
|
+
}
|
|
47122
|
+
});
|
|
47123
|
+
addServerFlag(cmd);
|
|
47124
|
+
addAccountFlag(cmd);
|
|
47125
|
+
cmd.option(
|
|
47126
|
+
"--connectors-url <url>",
|
|
47127
|
+
"Override the control-plane base URL (e.g. a kubectl port-forward http://localhost:8000)"
|
|
47128
|
+
);
|
|
47129
|
+
}
|
|
47130
|
+
|
|
47131
|
+
// src/commands/connectorsPoll.ts
|
|
47132
|
+
import * as readline2 from "readline";
|
|
47133
|
+
import { createHash as createHash11 } from "crypto";
|
|
47134
|
+
|
|
47135
|
+
// src/types/connectors.ts
|
|
47136
|
+
var POLL_ITEM_STATES = [
|
|
47137
|
+
"seen",
|
|
47138
|
+
"new",
|
|
47139
|
+
"dispatched",
|
|
47140
|
+
"running",
|
|
47141
|
+
"retry_wait",
|
|
47142
|
+
"landed",
|
|
47143
|
+
"failed",
|
|
47144
|
+
"skipped",
|
|
47145
|
+
"seeded",
|
|
47146
|
+
"superseded"
|
|
47147
|
+
];
|
|
47148
|
+
|
|
47149
|
+
// src/commands/connectorsPoll.ts
|
|
47150
|
+
var DURATION_UNIT_SECONDS2 = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
47151
|
+
var DURATION_BOUNDS = {
|
|
47152
|
+
"--interval": [300, 7 * 86400],
|
|
47153
|
+
"--settle": [0, 86400]
|
|
47154
|
+
};
|
|
47155
|
+
var LOOKBACK_MAX_DAYS = 3650;
|
|
47156
|
+
function parseDurationSeconds(raw2, flag) {
|
|
47157
|
+
const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
|
|
47158
|
+
const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS2[m[2] ?? "s"] ?? 1);
|
|
47159
|
+
const [min, max] = DURATION_BOUNDS[flag] ?? [0, Number.MAX_SAFE_INTEGER];
|
|
47160
|
+
if (m === null) {
|
|
47161
|
+
throw new Error(`invalid ${flag} "${raw2}": expected a duration like 90s, 5m or 1h`);
|
|
47162
|
+
}
|
|
47163
|
+
if (!Number.isSafeInteger(seconds) || seconds < min || seconds > max) {
|
|
47164
|
+
throw new Error(
|
|
47165
|
+
`invalid ${flag} "${raw2}": must be between ${formatSeconds(min)} and ${formatSeconds(max)}`
|
|
47166
|
+
);
|
|
47167
|
+
}
|
|
47168
|
+
return seconds;
|
|
47169
|
+
}
|
|
47170
|
+
function parseLookbackDays(raw2) {
|
|
47171
|
+
const m = /^(\d+)d?$/.exec(raw2.trim());
|
|
47172
|
+
const days = m === null ? 0 : Number(m[1]);
|
|
47173
|
+
if (days < 1 || days > LOOKBACK_MAX_DAYS) {
|
|
47174
|
+
throw new Error(
|
|
47175
|
+
`invalid --lookback "${raw2}": must be between 1 and ${LOOKBACK_MAX_DAYS} days, e.g. 180d`
|
|
47176
|
+
);
|
|
47177
|
+
}
|
|
47178
|
+
return days;
|
|
47179
|
+
}
|
|
47180
|
+
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
47181
|
+
var DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))$/;
|
|
47182
|
+
function inRange(value, max, min = 0) {
|
|
47183
|
+
const n = Number(value ?? "0");
|
|
47184
|
+
return n >= min && n <= max;
|
|
47185
|
+
}
|
|
47186
|
+
function isCalendarDay(y, m, d) {
|
|
47187
|
+
if (!inRange(m, 12, 1)) return false;
|
|
47188
|
+
const daysInMonth3 = new Date(Date.UTC(Number(y), Number(m), 0)).getUTCDate();
|
|
47189
|
+
return inRange(d, daysInMonth3, 1);
|
|
47190
|
+
}
|
|
47191
|
+
function parseTimestampFlag(raw2, flag) {
|
|
47192
|
+
const invalid = new Error(
|
|
47193
|
+
`invalid ${flag} "${raw2}": expected YYYY-MM-DD or a timestamp with a zone (e.g. 2026-10-08T17:30:00+03:00)`
|
|
47194
|
+
);
|
|
47195
|
+
const value = raw2.trim();
|
|
47196
|
+
const dateOnly = DATE_ONLY_RE.exec(value);
|
|
47197
|
+
if (dateOnly !== null) {
|
|
47198
|
+
if (!isCalendarDay(dateOnly[1], dateOnly[2], dateOnly[3])) throw invalid;
|
|
47199
|
+
return (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString();
|
|
47200
|
+
}
|
|
47201
|
+
const dt = DATE_TIME_RE.exec(value);
|
|
47202
|
+
const valid = dt !== null && isCalendarDay(dt[1], dt[2], dt[3]) && inRange(dt[4], 23) && inRange(dt[5], 59) && inRange(dt[6], 59) && inRange(dt[7], 23) && inRange(dt[8], 59);
|
|
47203
|
+
const ms = valid ? Date.parse(value) : NaN;
|
|
47204
|
+
if (Number.isNaN(ms)) throw invalid;
|
|
47205
|
+
return new Date(ms).toISOString();
|
|
47206
|
+
}
|
|
47207
|
+
function parseSameName(raw2) {
|
|
47208
|
+
if (raw2 === "new-version") return "new_version";
|
|
47209
|
+
if (raw2 === "ignore") return "ignore";
|
|
47210
|
+
throw new Error(`--same-name must be new-version or ignore (got "${raw2}")`);
|
|
47211
|
+
}
|
|
47212
|
+
function parseChoice(raw2, flag, choices) {
|
|
47213
|
+
const match = choices.find((c) => c === raw2);
|
|
47214
|
+
if (match === void 0) {
|
|
47215
|
+
throw new Error(`${flag} must be ${choices.join(" or ")} (got "${raw2}")`);
|
|
47216
|
+
}
|
|
47217
|
+
return match;
|
|
47218
|
+
}
|
|
47219
|
+
var WIDEN_CHOICES = ["seed", "land"];
|
|
47220
|
+
var POLICY_CHOICES = ["rekey", "seed", "land"];
|
|
47221
|
+
function parseSettings(opts) {
|
|
47222
|
+
const fields = {};
|
|
47223
|
+
if (opts.interval !== void 0) {
|
|
47224
|
+
fields.interval_seconds = parseDurationSeconds(opts.interval, "--interval");
|
|
47225
|
+
}
|
|
47226
|
+
if (opts.since !== void 0) fields.since = parseTimestampFlag(opts.since, "--since");
|
|
47227
|
+
if (opts.lookback !== void 0) fields.lookback_days = parseLookbackDays(opts.lookback);
|
|
47228
|
+
if (opts.settle !== void 0) {
|
|
47229
|
+
fields.settle_seconds = parseDurationSeconds(opts.settle, "--settle");
|
|
47230
|
+
}
|
|
47231
|
+
if (opts.sameName !== void 0) fields.change_policy = parseSameName(opts.sameName);
|
|
47232
|
+
return fields;
|
|
47233
|
+
}
|
|
47234
|
+
function settingsArgs(opts) {
|
|
47235
|
+
const pairs = [
|
|
47236
|
+
["--interval", opts.interval],
|
|
47237
|
+
["--since", opts.since],
|
|
47238
|
+
["--lookback", opts.lookback],
|
|
47239
|
+
["--settle", opts.settle],
|
|
47240
|
+
["--same-name", opts.sameName]
|
|
47241
|
+
];
|
|
47242
|
+
return pairs.filter((p) => p[1] !== void 0).map(([flag, value]) => ` ${flag} ${value.trim()}`).join("");
|
|
47243
|
+
}
|
|
47244
|
+
async function resolvePollBinding(client, filebox, signal) {
|
|
47245
|
+
const wanted = filebox.trim();
|
|
47246
|
+
const byId = /^\d+$/.test(wanted);
|
|
47247
|
+
const rows = await listQueries(client, { signal });
|
|
47248
|
+
const matches = rows.filter(
|
|
47249
|
+
(q) => byId ? q.filebox_id === Number(wanted) : q.filebox_uuid.toLowerCase() === wanted.toLowerCase()
|
|
47250
|
+
);
|
|
47251
|
+
const [first] = matches;
|
|
47252
|
+
if (first === void 0) {
|
|
47253
|
+
throw new Error(
|
|
47254
|
+
`no connector binding for filebox ${wanted} in this org (see dr connectors query list)`
|
|
47255
|
+
);
|
|
47256
|
+
}
|
|
47257
|
+
if (matches.length > 1) {
|
|
47258
|
+
const ids = matches.map((q) => `#${q.query_id}`).join(", ");
|
|
47259
|
+
throw new Error(`filebox ${wanted} has several connector bindings (${ids})`);
|
|
47260
|
+
}
|
|
47261
|
+
return first;
|
|
47262
|
+
}
|
|
47263
|
+
function isNotPolled(err) {
|
|
47264
|
+
return err instanceof ControlPlaneError && err.status === 404 && err.body.includes("not polled");
|
|
47265
|
+
}
|
|
47266
|
+
function isPollThrottled(err) {
|
|
47267
|
+
return err instanceof ControlPlaneError && err.status === 409 && (err.body.includes("discover is in flight") || err.body.includes("polled less than"));
|
|
47268
|
+
}
|
|
47269
|
+
async function explainNotPolled(filebox, call3) {
|
|
47270
|
+
try {
|
|
47271
|
+
return await call3;
|
|
47272
|
+
} catch (err) {
|
|
47273
|
+
if (isNotPolled(err)) {
|
|
47274
|
+
throw new Error(
|
|
47275
|
+
`filebox ${filebox} is not set to polled ingestion; run dr connectors poll enable ${filebox}`,
|
|
47276
|
+
{ cause: err }
|
|
47277
|
+
);
|
|
47278
|
+
}
|
|
47279
|
+
throw err;
|
|
47280
|
+
}
|
|
47281
|
+
}
|
|
47282
|
+
async function readFileboxDateTag(monolith, fileboxId, signal) {
|
|
47283
|
+
const details = await getDetails(monolith, fileboxId, { signal });
|
|
47284
|
+
const dateTags = (details.tags_config ?? []).filter((t) => t.type === "DATE");
|
|
47285
|
+
const [only] = dateTags;
|
|
47286
|
+
if (only === void 0) return {};
|
|
47287
|
+
if (dateTags.length > 1) {
|
|
47288
|
+
const names = dateTags.map((t) => t.name).join(", ");
|
|
47289
|
+
throw new Error(
|
|
47290
|
+
`filebox ${fileboxId} has several DATE tags (${names}); a polled filebox needs exactly one`
|
|
47291
|
+
);
|
|
47292
|
+
}
|
|
47293
|
+
return { id: only.id, name: only.name };
|
|
47294
|
+
}
|
|
47295
|
+
function formatSeconds(sec) {
|
|
47296
|
+
if (sec > 0 && sec % 86400 === 0) return `${sec / 86400}d`;
|
|
47297
|
+
if (sec > 0 && sec % 3600 === 0) return `${sec / 3600}h`;
|
|
47298
|
+
if (sec > 0 && sec % 60 === 0) return `${sec / 60}m`;
|
|
47299
|
+
return `${sec}s`;
|
|
47300
|
+
}
|
|
47301
|
+
function oneLine4(s) {
|
|
47302
|
+
return s.replace(/\s+/g, " ").trim();
|
|
47303
|
+
}
|
|
47304
|
+
function sameNameLabel(policy) {
|
|
47305
|
+
return policy === "new_version" ? "new-version" : String(policy);
|
|
47306
|
+
}
|
|
47307
|
+
function formatDateTag(tag) {
|
|
47308
|
+
if (tag?.id === void 0) return "none (undated filebox)";
|
|
47309
|
+
return `${tag.name ?? "?"} (id ${tag.id})`;
|
|
47310
|
+
}
|
|
47311
|
+
function bindingTarget(binding) {
|
|
47312
|
+
return `filebox ${binding.filebox_uuid} (query #${binding.query_id})`;
|
|
47313
|
+
}
|
|
47314
|
+
function renderPoll(out, headline, binding, p) {
|
|
47315
|
+
const row = (label, value) => {
|
|
47316
|
+
out.write(` ${`${label}:`.padEnd(15)}${value}
|
|
47317
|
+
`);
|
|
47318
|
+
};
|
|
47319
|
+
const status2 = `${p.enabled ? "enabled" : "disabled"}${p.paused_reason === null ? "" : `, paused: ${oneLine4(p.paused_reason)}`}`;
|
|
47320
|
+
const counts = Object.entries(p.counts).sort(([a], [b]) => a.localeCompare(b)).map(([state, n]) => `${state}=${n}`).join(" ");
|
|
47321
|
+
out.write(`${headline} for ${bindingTarget(binding)}
|
|
47322
|
+
`);
|
|
47323
|
+
row("Status", status2);
|
|
47324
|
+
row("Interval", formatSeconds(p.interval_seconds));
|
|
47325
|
+
row("Date tag", formatDateTag(p.date_tag));
|
|
47326
|
+
row("Since", p.since ?? "-");
|
|
47327
|
+
row("Lookback", `${p.lookback_days}d`);
|
|
47328
|
+
row("Settle", formatSeconds(p.settle_seconds));
|
|
47329
|
+
row("Same name", sameNameLabel(p.change_policy));
|
|
47330
|
+
row("Max attempts", String(p.max_attempts));
|
|
47331
|
+
row("Last poll", `${p.last_poll_at ?? "-"} ${oneLine4(p.last_poll_status ?? "")}`.trimEnd());
|
|
47332
|
+
row("Next poll", p.next_poll_at);
|
|
47333
|
+
row("Failures", String(p.consecutive_failures));
|
|
47334
|
+
row("Discover", p.discover_in_flight ? `in flight since ${p.discover_sent_at ?? "?"}` : "-");
|
|
47335
|
+
row("Last digest", p.last_digest ?? "-");
|
|
47336
|
+
row("Pending seed", p.poll_run_seed_until ?? "-");
|
|
47337
|
+
row(
|
|
47338
|
+
"Pending rekey",
|
|
47339
|
+
p.poll_run_rekey_to === null ? "-" : `to ${sameNameLabel(p.poll_run_rekey_to)}`
|
|
47340
|
+
);
|
|
47341
|
+
row("Items", counts === "" ? "(none)" : counts);
|
|
47342
|
+
}
|
|
47343
|
+
function writeTable26(out, rows) {
|
|
47344
|
+
const widths = [];
|
|
47345
|
+
for (const r of rows) {
|
|
47346
|
+
r.forEach((cell, i) => {
|
|
47347
|
+
widths[i] = Math.max(widths[i] ?? 0, cell.length);
|
|
47348
|
+
});
|
|
47349
|
+
}
|
|
47350
|
+
for (const r of rows) {
|
|
47351
|
+
const cells = r.map(
|
|
47352
|
+
(cell, i) => i === r.length - 1 ? cell : cell.padEnd((widths[i] ?? 0) + 2)
|
|
47353
|
+
);
|
|
47354
|
+
out.write(`${cells.join("").trimEnd()}
|
|
47355
|
+
`);
|
|
47356
|
+
}
|
|
47357
|
+
}
|
|
47358
|
+
function renderItemTable(out, items) {
|
|
47359
|
+
const rows = [["ID", "LABEL", "DATE", "STATE", "ATTEMPTS", "VERSION", "ERROR"]];
|
|
47360
|
+
for (const it of items) {
|
|
47361
|
+
const error = it.last_error ?? (it.skip_reason === null ? null : `skipped: ${it.skip_reason}`);
|
|
47362
|
+
rows.push([
|
|
47363
|
+
String(it.id),
|
|
47364
|
+
oneLine4(it.label),
|
|
47365
|
+
it.item_date ?? "-",
|
|
47366
|
+
it.state,
|
|
47367
|
+
String(it.attempts),
|
|
47368
|
+
it.version_id === null ? "-" : String(it.version_id),
|
|
47369
|
+
error === null ? "-" : truncate3(oneLine4(error), 80)
|
|
47370
|
+
]);
|
|
47371
|
+
}
|
|
47372
|
+
writeTable26(out, rows);
|
|
47373
|
+
}
|
|
47374
|
+
async function runPollEnable(cfg) {
|
|
47375
|
+
const binding = await resolvePollBinding(cfg.client, cfg.filebox, cfg.signal);
|
|
47376
|
+
let existing;
|
|
47377
|
+
try {
|
|
47378
|
+
existing = await getPoll(cfg.client, binding.query_id, { signal: cfg.signal });
|
|
47379
|
+
} catch (err) {
|
|
47380
|
+
if (!isNotPolled(err)) throw err;
|
|
47381
|
+
existing = null;
|
|
47382
|
+
}
|
|
47383
|
+
const s = cfg.settings;
|
|
47384
|
+
if (existing !== null && (s.since !== void 0 || s.lookback_days !== void 0 || s.change_policy !== void 0)) {
|
|
47385
|
+
throw new Error(
|
|
47386
|
+
`filebox ${cfg.filebox} is already polled; change --since, --lookback or --same-name with dr connectors poll set ${cfg.filebox}, which asks what to do with a wider window or a policy switch`
|
|
47387
|
+
);
|
|
47388
|
+
}
|
|
47389
|
+
if (binding.filebox_id <= 0) {
|
|
47390
|
+
throw new Error(
|
|
47391
|
+
`connector-query #${binding.query_id} has no filebox id recorded, so its date tag cannot be read; record it with dr connectors query repoint ${binding.query_id} --filebox-id <id>`
|
|
47392
|
+
);
|
|
47393
|
+
}
|
|
47394
|
+
const dateTag = await readFileboxDateTag(cfg.monolith, binding.filebox_id, cfg.signal);
|
|
47395
|
+
const stored = await putPoll(
|
|
47396
|
+
cfg.client,
|
|
47397
|
+
binding.query_id,
|
|
47398
|
+
{ enabled: true, date_tag: dateTag, ...s },
|
|
47399
|
+
{ signal: cfg.signal }
|
|
47400
|
+
);
|
|
47401
|
+
if (cfg.asJson) {
|
|
47402
|
+
writeJSONIndented(cfg.out, stored);
|
|
47403
|
+
return;
|
|
47404
|
+
}
|
|
47405
|
+
renderPoll(cfg.out, "Polling enabled", binding, stored);
|
|
47406
|
+
}
|
|
47407
|
+
function windowStart(since, lookbackDays, now) {
|
|
47408
|
+
const start = now - lookbackDays * 864e5;
|
|
47409
|
+
return since === null ? start : Math.max(start, Date.parse(since));
|
|
47410
|
+
}
|
|
47411
|
+
function widening(current, changes, now) {
|
|
46714
47412
|
if (changes.since === void 0 && changes.lookback_days === void 0) return null;
|
|
46715
47413
|
const from = windowStart(current.since, current.lookback_days, now);
|
|
46716
47414
|
const to = windowStart(
|
|
@@ -47083,793 +47781,332 @@ async function withMonolith(opts, action) {
|
|
|
47083
47781
|
function scopeOf(opts) {
|
|
47084
47782
|
const serverKey = resolveServer(opts.server);
|
|
47085
47783
|
return {
|
|
47086
|
-
serverKey,
|
|
47087
|
-
account: normaliseAccount(opts.account),
|
|
47088
|
-
controlPlane: resolveConnectorsUrl(serverKey, opts.connectorsUrl)
|
|
47089
|
-
};
|
|
47090
|
-
}
|
|
47091
|
-
function targetArgsOf(opts) {
|
|
47092
|
-
const url = (opts.connectorsUrl ?? "").trim();
|
|
47093
|
-
return ` --server ${resolveServer(opts.server)}${url === "" ? "" : ` --connectors-url ${url}`}`;
|
|
47094
|
-
}
|
|
47095
|
-
function addCommonFlags(cmd, jsonHelp) {
|
|
47096
|
-
cmd.option("--json", jsonHelp, false);
|
|
47097
|
-
addServerFlag(cmd);
|
|
47098
|
-
addAccountFlag(cmd);
|
|
47099
|
-
addConnectorsUrlFlag(cmd);
|
|
47100
|
-
}
|
|
47101
|
-
function addConfirmTokenFlag(cmd) {
|
|
47102
|
-
return cmd.option("--confirm-token <token>", "Token from a prior plan run \u2014 required to proceed");
|
|
47103
|
-
}
|
|
47104
|
-
function addSettingsFlags(cmd) {
|
|
47105
|
-
return cmd.option("--interval <duration>", "how often to poll, 5m to 7d (default: per source)").option("--since <date>", "never pick up items modified before this date or timestamp").option("--lookback <days>", "window every poll lists, 1d to 3650d (default: per source)").option("--settle <duration>", "how long an item must stay unchanged before landing, 0s to 1d").option(
|
|
47106
|
-
"--same-name <policy>",
|
|
47107
|
-
"a changed file under the same name: new-version (lands again) or ignore"
|
|
47108
|
-
);
|
|
47109
|
-
}
|
|
47110
|
-
function ttyChoicePrompt(message, choices, defaultChoice, io = { input: process.stdin, output: process.stderr }) {
|
|
47111
|
-
return new Promise((resolvePrompt) => {
|
|
47112
|
-
const rl = readline2.createInterface({
|
|
47113
|
-
input: io.input,
|
|
47114
|
-
output: io.output,
|
|
47115
|
-
...io.terminal !== void 0 ? { terminal: io.terminal } : {}
|
|
47116
|
-
});
|
|
47117
|
-
let settled = false;
|
|
47118
|
-
const settle = (value) => {
|
|
47119
|
-
if (settled) return;
|
|
47120
|
-
settled = true;
|
|
47121
|
-
rl.close();
|
|
47122
|
-
resolvePrompt(value);
|
|
47123
|
-
};
|
|
47124
|
-
rl.on("SIGINT", () => settle(null));
|
|
47125
|
-
rl.on("close", () => settle(null));
|
|
47126
|
-
io.output.write(`${message}
|
|
47127
|
-
`);
|
|
47128
|
-
const question = defaultChoice === void 0 ? `[${choices.join("/")}]: ` : `[${choices.join("/")}] (Enter = ${defaultChoice}): `;
|
|
47129
|
-
const askOnce = () => {
|
|
47130
|
-
rl.question(question, (answer) => {
|
|
47131
|
-
const picked = answer.trim().toLowerCase();
|
|
47132
|
-
if (picked === "" && defaultChoice !== void 0) {
|
|
47133
|
-
settle(defaultChoice);
|
|
47134
|
-
} else if (choices.includes(picked)) {
|
|
47135
|
-
settle(picked);
|
|
47136
|
-
} else {
|
|
47137
|
-
io.output.write(`Answer one of: ${choices.join(", ")}
|
|
47138
|
-
`);
|
|
47139
|
-
askOnce();
|
|
47140
|
-
}
|
|
47141
|
-
});
|
|
47142
|
-
};
|
|
47143
|
-
askOnce();
|
|
47144
|
-
});
|
|
47145
|
-
}
|
|
47146
|
-
var POLL_CONFIRM_HELP = [
|
|
47147
|
-
"Confirmation:",
|
|
47148
|
-
" seed, skip and set --on-policy-change seed print a plan and a --confirm-token;",
|
|
47149
|
-
" re-run the printed command, token included, to apply it.",
|
|
47150
|
-
" No dialog opens on production, so an agent or script can run a cut-over unattended:",
|
|
47151
|
-
" dr connectors poll seed <filebox> --until <cut-over> --server <prod>",
|
|
47152
|
-
" dr connectors poll seed <filebox> --until <cut-over> --server <prod> --confirm-token <token>",
|
|
47153
|
-
" (seed --dry-run counts the already-discovered items a seed would mark.)",
|
|
47154
|
-
" A write with any answer that lands files (set --widen land, set --on-policy-change land,",
|
|
47155
|
-
" resume --source land) also asks a person to type a word in a dialog on production,",
|
|
47156
|
-
" even when the same set also seeds (--on-policy-change seed --widen land)."
|
|
47157
|
-
].join("\n");
|
|
47158
|
-
function registerConnectorsPoll(connectors) {
|
|
47159
|
-
const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version").addHelpText("after", `
|
|
47160
|
-
${POLL_CONFIRM_HELP}`);
|
|
47161
|
-
const enableCmd = addSettingsFlags(
|
|
47162
|
-
poll2.command("enable").description("Poll a filebox's binding (dated with the filebox's DATE tag)").argument("<filebox>", "filebox id or uuid")
|
|
47163
|
-
).action(async (filebox, opts) => {
|
|
47164
|
-
const settings = parseSettings(opts);
|
|
47165
|
-
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47166
|
-
await withMonolith(opts, async (monolith) => {
|
|
47167
|
-
await runPollEnable({
|
|
47168
|
-
client,
|
|
47169
|
-
monolith,
|
|
47170
|
-
filebox,
|
|
47171
|
-
settings,
|
|
47172
|
-
asJson: opts.json === true,
|
|
47173
|
-
signal,
|
|
47174
|
-
out: process.stdout
|
|
47175
|
-
});
|
|
47176
|
-
});
|
|
47177
|
-
});
|
|
47178
|
-
});
|
|
47179
|
-
addCommonFlags(enableCmd, "Output the stored binding as JSON");
|
|
47180
|
-
const setCmd = addConfirmTokenFlag(
|
|
47181
|
-
addSettingsFlags(
|
|
47182
|
-
poll2.command("set").description("Change polling settings (only the given flags change)").argument("<filebox>", "filebox id or uuid")
|
|
47183
|
-
).option(
|
|
47184
|
-
"--widen <seed|land>",
|
|
47185
|
-
"a wider window: seed (mark exposed items landed) or land them"
|
|
47186
|
-
).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land").addHelpText("after", `
|
|
47187
|
-
${POLL_CONFIRM_HELP}`)
|
|
47188
|
-
).action(
|
|
47189
|
-
async (filebox, opts) => {
|
|
47190
|
-
const changes = parseSettings(opts);
|
|
47191
|
-
const widen = opts.widen === void 0 ? void 0 : parseChoice(opts.widen, "--widen", WIDEN_CHOICES);
|
|
47192
|
-
const onPolicyChange = opts.onPolicyChange === void 0 ? void 0 : parseChoice(opts.onPolicyChange, "--on-policy-change", POLICY_CHOICES);
|
|
47193
|
-
await preflightConfirmToken(
|
|
47194
|
-
pollConfirmAction(filebox, pollSetConfirmVerb(widen, onPolicyChange), scopeOf(opts), {
|
|
47195
|
-
...changes,
|
|
47196
|
-
...widen !== void 0 ? { widen } : {},
|
|
47197
|
-
...onPolicyChange !== void 0 ? { on_policy_change: onPolicyChange } : {}
|
|
47198
|
-
}),
|
|
47199
|
-
opts.confirmToken
|
|
47200
|
-
);
|
|
47201
|
-
await withConnectorsSetup(opts, async (setup) => {
|
|
47202
|
-
const deadline = {
|
|
47203
|
-
pause: () => setup.pauseTimeout(),
|
|
47204
|
-
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
47205
|
-
};
|
|
47206
|
-
await runPollSet({
|
|
47207
|
-
client: setup.client,
|
|
47208
|
-
filebox,
|
|
47209
|
-
changes,
|
|
47210
|
-
settingsArgs: settingsArgs(opts),
|
|
47211
|
-
...widen !== void 0 ? { widen } : {},
|
|
47212
|
-
...onPolicyChange !== void 0 ? { onPolicyChange } : {},
|
|
47213
|
-
interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
|
|
47214
|
-
prompt: async (message, choices, defaultChoice) => {
|
|
47215
|
-
deadline.pause();
|
|
47216
|
-
try {
|
|
47217
|
-
return await ttyChoicePrompt(message, choices, defaultChoice);
|
|
47218
|
-
} finally {
|
|
47219
|
-
deadline.resume();
|
|
47220
|
-
}
|
|
47221
|
-
},
|
|
47222
|
-
deadline,
|
|
47223
|
-
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
47224
|
-
scope: scopeOf(opts),
|
|
47225
|
-
targetArgs: targetArgsOf(opts),
|
|
47226
|
-
asJson: opts.json === true,
|
|
47227
|
-
signal: setup.signal,
|
|
47228
|
-
out: process.stdout
|
|
47229
|
-
});
|
|
47230
|
-
});
|
|
47231
|
-
}
|
|
47232
|
-
);
|
|
47233
|
-
addCommonFlags(setCmd, "Output the stored binding as JSON");
|
|
47234
|
-
const disableCmd = poll2.command("disable").description("Stop polling (an item already in flight still finishes)").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
|
|
47235
|
-
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47236
|
-
await runPollDisable({
|
|
47237
|
-
client,
|
|
47238
|
-
filebox,
|
|
47239
|
-
asJson: opts.json === true,
|
|
47240
|
-
signal,
|
|
47241
|
-
out: process.stdout
|
|
47242
|
-
});
|
|
47243
|
-
});
|
|
47244
|
-
});
|
|
47245
|
-
addCommonFlags(disableCmd, "Output the stored binding as JSON");
|
|
47246
|
-
const getCmd = poll2.command("get").description("Show polling settings, last poll, pending seed / rekey and item counts").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
|
|
47247
|
-
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47248
|
-
await runPollGet({
|
|
47249
|
-
client,
|
|
47250
|
-
filebox,
|
|
47251
|
-
asJson: opts.json === true,
|
|
47252
|
-
signal,
|
|
47253
|
-
out: process.stdout
|
|
47254
|
-
});
|
|
47255
|
-
});
|
|
47256
|
-
});
|
|
47257
|
-
addCommonFlags(getCmd, "Output the raw binding as JSON");
|
|
47258
|
-
const runCmd = poll2.command("run").description("Poll now").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
|
|
47259
|
-
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47260
|
-
await runPollRun({
|
|
47261
|
-
client,
|
|
47262
|
-
filebox,
|
|
47263
|
-
asJson: opts.json === true,
|
|
47264
|
-
signal,
|
|
47265
|
-
out: process.stdout
|
|
47266
|
-
});
|
|
47267
|
-
});
|
|
47268
|
-
});
|
|
47269
|
-
addCommonFlags(runCmd, "Output the binding as JSON");
|
|
47270
|
-
const resumeCmd = addConfirmTokenFlag(
|
|
47271
|
-
poll2.command("resume").description("Resume a paused binding (after fixing credentials, or a source change)").argument("<filebox>", "filebox id or uuid").option(
|
|
47272
|
-
"--source <seed|land>",
|
|
47273
|
-
"after a source change: seed (mark present items landed) or land them"
|
|
47274
|
-
)
|
|
47275
|
-
).action(async (filebox, opts) => {
|
|
47276
|
-
const source = opts.source === void 0 ? void 0 : parseChoice(opts.source, "--source", WIDEN_CHOICES);
|
|
47277
|
-
if (source === "land") {
|
|
47278
|
-
await preflightConfirmToken(
|
|
47279
|
-
pollConfirmAction(filebox, "resume-land", scopeOf(opts)),
|
|
47280
|
-
opts.confirmToken
|
|
47281
|
-
);
|
|
47282
|
-
}
|
|
47283
|
-
await withConnectorsSetup(opts, async (setup) => {
|
|
47284
|
-
await runPollResume({
|
|
47285
|
-
client: setup.client,
|
|
47286
|
-
filebox,
|
|
47287
|
-
...source !== void 0 ? { source } : {},
|
|
47288
|
-
deadline: {
|
|
47289
|
-
pause: () => setup.pauseTimeout(),
|
|
47290
|
-
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
47291
|
-
},
|
|
47292
|
-
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
47293
|
-
scope: scopeOf(opts),
|
|
47294
|
-
targetArgs: targetArgsOf(opts),
|
|
47295
|
-
asJson: opts.json === true,
|
|
47296
|
-
signal: setup.signal,
|
|
47297
|
-
out: process.stdout
|
|
47298
|
-
});
|
|
47299
|
-
});
|
|
47300
|
-
});
|
|
47301
|
-
addCommonFlags(resumeCmd, "Output the binding as JSON");
|
|
47302
|
-
const itemsCmd = poll2.command("items").description("List the item ledger: label, date, state, attempts, version, error").argument("<filebox>", "filebox id or uuid").option("--state <state>", `only items in this state (${POLL_ITEM_STATES.join("|")})`).option("--limit <n>", "page size (default and maximum: the control-plane page size, 100)").option("--after <id>", "start after this item id (the next-page cursor)").action(
|
|
47303
|
-
async (filebox, opts) => {
|
|
47304
|
-
const state = opts.state === void 0 ? void 0 : POLL_ITEM_STATES.find((s) => s === opts.state);
|
|
47305
|
-
if (opts.state !== void 0 && state === void 0) {
|
|
47306
|
-
throw new Error(
|
|
47307
|
-
`invalid --state "${opts.state}": expected one of ${POLL_ITEM_STATES.join(", ")}`
|
|
47308
|
-
);
|
|
47309
|
-
}
|
|
47310
|
-
const limit = opts.limit === void 0 ? void 0 : parsePositiveIntArg2(opts.limit, "--limit");
|
|
47311
|
-
const after = opts.after === void 0 ? void 0 : parsePositiveIntArg2(opts.after, "--after");
|
|
47312
|
-
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47313
|
-
await runPollItems({
|
|
47314
|
-
client,
|
|
47315
|
-
filebox,
|
|
47316
|
-
...state !== void 0 ? { state } : {},
|
|
47317
|
-
...limit !== void 0 ? { limit } : {},
|
|
47318
|
-
...after !== void 0 ? { after } : {},
|
|
47319
|
-
asJson: opts.json === true,
|
|
47320
|
-
signal,
|
|
47321
|
-
out: process.stdout
|
|
47322
|
-
});
|
|
47323
|
-
});
|
|
47324
|
-
}
|
|
47325
|
-
);
|
|
47326
|
-
addCommonFlags(itemsCmd, "Output the raw page as JSON");
|
|
47327
|
-
const retryCmd = poll2.command("retry").description("Queue a failed, waiting or skipped item again").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)").action(async (filebox, item, opts) => {
|
|
47328
|
-
const itemId = parsePositiveIntArg2(item, "item id");
|
|
47329
|
-
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47330
|
-
await runPollRetry({
|
|
47331
|
-
client,
|
|
47332
|
-
filebox,
|
|
47333
|
-
itemId,
|
|
47334
|
-
asJson: opts.json === true,
|
|
47335
|
-
signal,
|
|
47336
|
-
out: process.stdout
|
|
47337
|
-
});
|
|
47338
|
-
});
|
|
47339
|
-
});
|
|
47340
|
-
addCommonFlags(retryCmd, "Output the item as JSON");
|
|
47341
|
-
const skipCmd = addConfirmTokenFlag(
|
|
47342
|
-
poll2.command("skip").description("Skip an item so it is never landed (two-step confirmation)").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)")
|
|
47343
|
-
).action(async (filebox, item, opts) => {
|
|
47344
|
-
const itemId = parsePositiveIntArg2(item, "item id");
|
|
47345
|
-
await preflightConfirmToken(
|
|
47346
|
-
pollItemConfirmAction(filebox, itemId, scopeOf(opts)),
|
|
47347
|
-
opts.confirmToken
|
|
47348
|
-
);
|
|
47349
|
-
await withConnectorsSetup(opts, async (setup) => {
|
|
47350
|
-
await runPollSkip({
|
|
47351
|
-
client: setup.client,
|
|
47352
|
-
filebox,
|
|
47353
|
-
itemId,
|
|
47354
|
-
deadline: {
|
|
47355
|
-
pause: () => setup.pauseTimeout(),
|
|
47356
|
-
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
47357
|
-
},
|
|
47358
|
-
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
47359
|
-
scope: scopeOf(opts),
|
|
47360
|
-
targetArgs: targetArgsOf(opts),
|
|
47361
|
-
asJson: opts.json === true,
|
|
47362
|
-
signal: setup.signal,
|
|
47363
|
-
out: process.stdout
|
|
47364
|
-
});
|
|
47365
|
-
});
|
|
47366
|
-
});
|
|
47367
|
-
addCommonFlags(skipCmd, "Output the item as JSON");
|
|
47368
|
-
const seedCmd = addConfirmTokenFlag(
|
|
47369
|
-
poll2.command("seed").description(
|
|
47370
|
-
"Mark items modified at or before --until as already landed (cut-over; token, no dialog)"
|
|
47371
|
-
).argument("<filebox>", "filebox id or uuid").requiredOption(
|
|
47372
|
-
"--until <timestamp>",
|
|
47373
|
-
"date or timestamp with a zone, e.g. 2026-10-08T17:30:00Z"
|
|
47374
|
-
).option(
|
|
47375
|
-
"--dry-run",
|
|
47376
|
-
"count the already-discovered items it would mark, without changing anything",
|
|
47377
|
-
false
|
|
47378
|
-
).addHelpText("after", `
|
|
47379
|
-
${POLL_CONFIRM_HELP}`)
|
|
47380
|
-
).action(
|
|
47381
|
-
async (filebox, opts) => {
|
|
47382
|
-
const until = parseTimestampFlag(opts.until, "--until");
|
|
47383
|
-
const dryRun = opts.dryRun === true;
|
|
47384
|
-
if (!dryRun) {
|
|
47385
|
-
await preflightConfirmToken(
|
|
47386
|
-
pollConfirmAction(filebox, "seed", scopeOf(opts), { until }),
|
|
47387
|
-
opts.confirmToken
|
|
47388
|
-
);
|
|
47389
|
-
}
|
|
47390
|
-
await withConnectorsSetup(opts, async (setup) => {
|
|
47391
|
-
await runPollSeed({
|
|
47392
|
-
client: setup.client,
|
|
47393
|
-
filebox,
|
|
47394
|
-
until,
|
|
47395
|
-
untilArg: opts.until.trim(),
|
|
47396
|
-
dryRun,
|
|
47397
|
-
deadline: {
|
|
47398
|
-
pause: () => setup.pauseTimeout(),
|
|
47399
|
-
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
47400
|
-
},
|
|
47401
|
-
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
47402
|
-
scope: scopeOf(opts),
|
|
47403
|
-
targetArgs: targetArgsOf(opts),
|
|
47404
|
-
asJson: opts.json === true,
|
|
47405
|
-
signal: setup.signal,
|
|
47406
|
-
out: process.stdout
|
|
47407
|
-
});
|
|
47408
|
-
});
|
|
47409
|
-
}
|
|
47410
|
-
);
|
|
47411
|
-
addCommonFlags(seedCmd, "Output the binding (or the dry-run count) as JSON");
|
|
47412
|
-
}
|
|
47413
|
-
|
|
47414
|
-
// src/commands/connectorsTasks.ts
|
|
47415
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
47416
|
-
|
|
47417
|
-
// src/api/scheduledTasks.ts
|
|
47418
|
-
var BASE2 = "/scheduled-tasks";
|
|
47419
|
-
async function call(method, path13, send, what) {
|
|
47420
|
-
let out;
|
|
47421
|
-
try {
|
|
47422
|
-
out = await send();
|
|
47423
|
-
} catch (err) {
|
|
47424
|
-
throw toControlPlaneError(method, path13, err);
|
|
47425
|
-
}
|
|
47426
|
-
if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
|
|
47427
|
-
return out;
|
|
47428
|
-
}
|
|
47429
|
-
async function createTask(client, fields, reqOptions) {
|
|
47430
|
-
return call(
|
|
47431
|
-
"POST",
|
|
47432
|
-
BASE2,
|
|
47433
|
-
() => client.postJSON(BASE2, fields, reqOptions),
|
|
47434
|
-
"task create"
|
|
47435
|
-
);
|
|
47436
|
-
}
|
|
47437
|
-
async function listTasks(client, orgId, reqOptions) {
|
|
47438
|
-
const path13 = orgId === void 0 ? BASE2 : `${BASE2}?org_id=${orgId}`;
|
|
47439
|
-
return call("GET", path13, () => client.getJSON(path13, reqOptions), "task list");
|
|
47440
|
-
}
|
|
47441
|
-
async function getTask(client, taskId, reqOptions) {
|
|
47442
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
|
|
47443
|
-
return call("GET", path13, () => client.getJSON(path13, reqOptions), "task get");
|
|
47444
|
-
}
|
|
47445
|
-
async function updateTask(client, taskId, fields, reqOptions) {
|
|
47446
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
|
|
47447
|
-
return call(
|
|
47448
|
-
"PATCH",
|
|
47449
|
-
path13,
|
|
47450
|
-
() => client.patchJSON(path13, fields, reqOptions),
|
|
47451
|
-
"task update"
|
|
47452
|
-
);
|
|
47453
|
-
}
|
|
47454
|
-
async function deleteTask(client, taskId, reqOptions) {
|
|
47455
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
|
|
47456
|
-
try {
|
|
47457
|
-
await client.deleteJSON(path13, reqOptions);
|
|
47458
|
-
} catch (err) {
|
|
47459
|
-
throw toControlPlaneError("DELETE", path13, err);
|
|
47460
|
-
}
|
|
47461
|
-
}
|
|
47462
|
-
async function runTask(client, taskId, reqOptions) {
|
|
47463
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/run`;
|
|
47464
|
-
return call(
|
|
47465
|
-
"POST",
|
|
47466
|
-
path13,
|
|
47467
|
-
() => client.postJSON(path13, {}, reqOptions),
|
|
47468
|
-
"task run"
|
|
47469
|
-
);
|
|
47470
|
-
}
|
|
47471
|
-
async function pauseTask(client, taskId, reqOptions) {
|
|
47472
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/pause`;
|
|
47473
|
-
return call(
|
|
47474
|
-
"POST",
|
|
47475
|
-
path13,
|
|
47476
|
-
() => client.postJSON(path13, {}, reqOptions),
|
|
47477
|
-
"task pause"
|
|
47478
|
-
);
|
|
47479
|
-
}
|
|
47480
|
-
async function resumeTask(client, taskId, reqOptions) {
|
|
47481
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/resume`;
|
|
47482
|
-
return call(
|
|
47483
|
-
"POST",
|
|
47484
|
-
path13,
|
|
47485
|
-
() => client.postJSON(path13, {}, reqOptions),
|
|
47486
|
-
"task resume"
|
|
47487
|
-
);
|
|
47488
|
-
}
|
|
47489
|
-
async function listRuns(client, taskId, limit, reqOptions) {
|
|
47490
|
-
const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/runs?limit=${limit}`;
|
|
47491
|
-
return call(
|
|
47492
|
-
"GET",
|
|
47493
|
-
path13,
|
|
47494
|
-
() => client.getJSON(path13, reqOptions),
|
|
47495
|
-
"task runs"
|
|
47496
|
-
);
|
|
47497
|
-
}
|
|
47498
|
-
|
|
47499
|
-
// src/commands/connectorsTasks.ts
|
|
47500
|
-
var DURATION_UNIT_SECONDS2 = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
47501
|
-
var MIN_EVERY_SECONDS = 60;
|
|
47502
|
-
var MAX_EVERY_SECONDS = 2 ** 31 - 1;
|
|
47503
|
-
function parseEvery(raw2) {
|
|
47504
|
-
const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
|
|
47505
|
-
const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS2[m[2] ?? "s"] ?? 1);
|
|
47506
|
-
if (m === null)
|
|
47507
|
-
throw new Error(`invalid --every "${raw2}": expected a duration like 5m, 1h or 1d`);
|
|
47508
|
-
if (!Number.isSafeInteger(seconds) || seconds < MIN_EVERY_SECONDS || seconds > MAX_EVERY_SECONDS) {
|
|
47509
|
-
throw new Error(`invalid --every "${raw2}": must be at least 1m`);
|
|
47510
|
-
}
|
|
47511
|
-
return seconds;
|
|
47512
|
-
}
|
|
47513
|
-
function parseCron(raw2) {
|
|
47514
|
-
const fields = raw2.trim().split(/\s+/);
|
|
47515
|
-
if (fields.length !== 5 || fields.some((f) => f === "")) {
|
|
47516
|
-
throw new Error(`invalid --cron "${raw2}": expected five fields, e.g. "15 * * * *"`);
|
|
47517
|
-
}
|
|
47518
|
-
return fields.join(" ");
|
|
47519
|
-
}
|
|
47520
|
-
var ROLE_RE = /^[a-z][a-z_]*$/;
|
|
47521
|
-
var DATASOURCE_ID_RE = /^[0-9]{1,18}$/;
|
|
47522
|
-
function parseDatasources(values) {
|
|
47523
|
-
const out = {};
|
|
47524
|
-
for (const raw2 of values) {
|
|
47525
|
-
const eq = raw2.indexOf("=");
|
|
47526
|
-
const role = eq < 0 ? "" : raw2.slice(0, eq).trim();
|
|
47527
|
-
const id = eq < 0 ? "" : raw2.slice(eq + 1).trim();
|
|
47528
|
-
if (!ROLE_RE.test(role) || !DATASOURCE_ID_RE.test(id)) {
|
|
47529
|
-
throw new Error(
|
|
47530
|
-
`invalid --datasource "${raw2}": expected <role>=<datasource id>, e.g. target=4242 (role: lowercase letters and _; id: digits)`
|
|
47531
|
-
);
|
|
47532
|
-
}
|
|
47533
|
-
if (Object.hasOwn(out, role)) throw new Error(`--datasource role "${role}" given twice`);
|
|
47534
|
-
out[role] = id;
|
|
47535
|
-
}
|
|
47536
|
-
const count = Object.keys(out).length;
|
|
47537
|
-
if (count < 1 || count > 4) throw new Error("give between 1 and 4 --datasource roles");
|
|
47538
|
-
return out;
|
|
47539
|
-
}
|
|
47540
|
-
function parseConfig(raw2, readFile9 = readUtf8) {
|
|
47541
|
-
const text2 = raw2.startsWith("@") ? readFile9(raw2.slice(1)) : raw2;
|
|
47542
|
-
let parsed2;
|
|
47543
|
-
try {
|
|
47544
|
-
parsed2 = JSON.parse(text2);
|
|
47545
|
-
} catch (err) {
|
|
47546
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
47547
|
-
throw new Error(`invalid --config JSON: ${msg}`);
|
|
47548
|
-
}
|
|
47549
|
-
if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2)) {
|
|
47550
|
-
throw new Error("invalid --config: expected a JSON object");
|
|
47551
|
-
}
|
|
47552
|
-
return parsed2;
|
|
47553
|
-
}
|
|
47554
|
-
function readUtf8(p) {
|
|
47555
|
-
return readFileSync3(p, "utf-8");
|
|
47556
|
-
}
|
|
47557
|
-
function collect(value, previous = []) {
|
|
47558
|
-
return [...previous, value];
|
|
47559
|
-
}
|
|
47560
|
-
function parseScheduleFlags(opts) {
|
|
47561
|
-
if (opts.every !== void 0 && opts.cron !== void 0) {
|
|
47562
|
-
throw new Error("give --every or --cron, not both");
|
|
47563
|
-
}
|
|
47564
|
-
const fields = {};
|
|
47565
|
-
if (opts.artifact !== void 0) fields.artifact_name = opts.artifact;
|
|
47566
|
-
if (opts.flavor !== void 0) fields.runtime_flavor = opts.flavor;
|
|
47567
|
-
if (opts.datasource !== void 0) fields.datasources = parseDatasources(opts.datasource);
|
|
47568
|
-
if (opts.primaryRole !== void 0) fields.primary_role = opts.primaryRole;
|
|
47569
|
-
if (opts.config !== void 0) fields.config = parseConfig(opts.config);
|
|
47570
|
-
if (opts.every !== void 0) fields.every_seconds = parseEvery(opts.every);
|
|
47571
|
-
if (opts.cron !== void 0) fields.cron = parseCron(opts.cron);
|
|
47572
|
-
if (opts.tz !== void 0) fields.timezone = opts.tz;
|
|
47573
|
-
if (fields.primary_role !== void 0 && fields.datasources !== void 0) {
|
|
47574
|
-
if (!Object.hasOwn(fields.datasources, fields.primary_role)) {
|
|
47575
|
-
throw new Error(
|
|
47576
|
-
`--primary-role "${fields.primary_role}" is not one of the --datasource roles`
|
|
47577
|
-
);
|
|
47578
|
-
}
|
|
47579
|
-
}
|
|
47580
|
-
return fields;
|
|
47581
|
-
}
|
|
47582
|
-
function buildCreateFields(name, orgId, opts) {
|
|
47583
|
-
const missing = [
|
|
47584
|
-
["--artifact", opts.artifact],
|
|
47585
|
-
["--flavor", opts.flavor],
|
|
47586
|
-
["--datasource", opts.datasource],
|
|
47587
|
-
["--config", opts.config]
|
|
47588
|
-
].filter(([, v]) => v === void 0).map(([flag]) => flag);
|
|
47589
|
-
if (opts.every === void 0 && opts.cron === void 0) missing.push("--every or --cron");
|
|
47590
|
-
if (missing.length > 0) throw new Error(`missing ${missing.join(", ")}`);
|
|
47591
|
-
const fields = parseScheduleFlags(opts);
|
|
47592
|
-
return {
|
|
47593
|
-
org_id: orgId,
|
|
47594
|
-
name,
|
|
47595
|
-
artifact_name: fields.artifact_name,
|
|
47596
|
-
runtime_flavor: fields.runtime_flavor,
|
|
47597
|
-
datasources: fields.datasources,
|
|
47598
|
-
config: fields.config,
|
|
47599
|
-
...fields.primary_role !== void 0 ? { primary_role: fields.primary_role } : {},
|
|
47600
|
-
...fields.every_seconds !== void 0 ? { every_seconds: fields.every_seconds } : {},
|
|
47601
|
-
...fields.cron !== void 0 ? { cron: fields.cron } : {},
|
|
47602
|
-
...fields.timezone !== void 0 ? { timezone: fields.timezone } : {},
|
|
47603
|
-
...opts.disabled === true ? { enabled: false } : {}
|
|
47604
|
-
};
|
|
47605
|
-
}
|
|
47606
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
47607
|
-
async function resolveTaskId(client, task, signal) {
|
|
47608
|
-
if (UUID_RE.test(task)) return task;
|
|
47609
|
-
const tasks = await listTasks(client, void 0, { signal });
|
|
47610
|
-
const match = tasks.find((t) => t.name === task);
|
|
47611
|
-
if (match === void 0) {
|
|
47612
|
-
throw new Error(
|
|
47613
|
-
`no scheduled task named "${task}" in this org (see: dr connectors tasks list)`
|
|
47614
|
-
);
|
|
47615
|
-
}
|
|
47616
|
-
return match.id;
|
|
47617
|
-
}
|
|
47618
|
-
function explainTaskError(err) {
|
|
47619
|
-
const forbidden = err instanceof ControlPlaneError && err.status === 403 || err instanceof Error && /\bHTTP 403\b/.test(err.message);
|
|
47620
|
-
if (forbidden) {
|
|
47621
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
47622
|
-
const body = err instanceof ControlPlaneError ? err.body : (/HTTP 403[^:]*(?:\([^)]*\))?:\s*([\s\S]*)$/.exec(message)?.[1] ?? "").trim();
|
|
47623
|
-
return withCause(
|
|
47624
|
-
new ControlPlaneError(
|
|
47625
|
-
403,
|
|
47626
|
-
body,
|
|
47627
|
-
`${message}
|
|
47628
|
-
Scheduled tasks need a support user or an org admin: log in as one (--account) \u2014 e.g. dr login-as the customer's support user.`
|
|
47629
|
-
),
|
|
47630
|
-
err
|
|
47631
|
-
);
|
|
47632
|
-
}
|
|
47633
|
-
if (!(err instanceof ControlPlaneError)) return err;
|
|
47634
|
-
const detail = detailOf(err.body);
|
|
47635
|
-
if (err.status === 404) {
|
|
47636
|
-
const message = `scheduled task not found (${detail || err.message})`;
|
|
47637
|
-
return withCause(new ControlPlaneError(404, err.body, message), err);
|
|
47638
|
-
}
|
|
47639
|
-
if (err.status === 409 || err.status === 422) {
|
|
47640
|
-
return withCause(new ControlPlaneError(err.status, err.body, detail || err.message), err);
|
|
47641
|
-
}
|
|
47642
|
-
return err;
|
|
47643
|
-
}
|
|
47644
|
-
function withCause(error, cause) {
|
|
47645
|
-
Object.defineProperty(error, "cause", { value: cause, enumerable: false, configurable: true });
|
|
47646
|
-
return error;
|
|
47647
|
-
}
|
|
47648
|
-
function detailOf(body) {
|
|
47649
|
-
try {
|
|
47650
|
-
const parsed2 = JSON.parse(body);
|
|
47651
|
-
const d = parsed2.detail;
|
|
47652
|
-
if (typeof d === "string") return d;
|
|
47653
|
-
if (Array.isArray(d)) {
|
|
47654
|
-
return d.map(
|
|
47655
|
-
(e) => `${Array.isArray(e.loc) ? e.loc.slice(1).join(".") : ""}: ${e.msg ?? ""}`.trim()
|
|
47656
|
-
).join("; ");
|
|
47657
|
-
}
|
|
47658
|
-
} catch {
|
|
47659
|
-
}
|
|
47660
|
-
return body;
|
|
47661
|
-
}
|
|
47662
|
-
function scheduleText(t) {
|
|
47663
|
-
if (t.schedule_kind === "cron") return `cron "${t.cron ?? ""}" ${t.timezone}`;
|
|
47664
|
-
const s = t.every_seconds ?? 0;
|
|
47665
|
-
const unit = s % 86400 === 0 ? `${s / 86400}d` : s % 3600 === 0 ? `${s / 3600}h` : s % 60 === 0 ? `${s / 60}m` : `${s}s`;
|
|
47666
|
-
return `every ${unit}`;
|
|
47667
|
-
}
|
|
47668
|
-
function stateText(t) {
|
|
47669
|
-
if (!t.enabled) return "disabled";
|
|
47670
|
-
if (t.paused_reason !== null) return `paused (${t.paused_reason})`;
|
|
47671
|
-
return t.running ? "running" : "enabled";
|
|
47672
|
-
}
|
|
47673
|
-
function writeTask(out, t) {
|
|
47674
|
-
const roles = Object.entries(t.datasources).map(([role, id]) => `${role}=${id}${role === t.primary_role ? " (primary)" : ""}`).join(", ");
|
|
47675
|
-
out.write(`Task: ${t.name} (${t.id})
|
|
47676
|
-
`);
|
|
47677
|
-
out.write(` Org: ${t.org_id}
|
|
47678
|
-
`);
|
|
47679
|
-
out.write(` State: ${stateText(t)}
|
|
47680
|
-
`);
|
|
47681
|
-
out.write(` Artifact: ${t.artifact_name} [${t.runtime_flavor}]
|
|
47682
|
-
`);
|
|
47683
|
-
out.write(` Schedule: ${scheduleText(t)}
|
|
47684
|
-
`);
|
|
47685
|
-
out.write(` Next run: ${t.next_run_at}
|
|
47686
|
-
`);
|
|
47687
|
-
out.write(` Last run: ${t.last_run_at ?? "-"} ${t.last_run_status ?? ""}
|
|
47688
|
-
`);
|
|
47689
|
-
if (t.consecutive_failures > 0) out.write(` Failures: ${t.consecutive_failures} in a row
|
|
47690
|
-
`);
|
|
47691
|
-
out.write(` Datasources: ${roles}
|
|
47692
|
-
`);
|
|
47693
|
-
out.write(` Config: ${truncate3(JSON.stringify(t.config), 200)}
|
|
47694
|
-
`);
|
|
47695
|
-
}
|
|
47696
|
-
function writeTaskList(out, tasks) {
|
|
47697
|
-
if (tasks.length === 0) {
|
|
47698
|
-
out.write("No scheduled tasks.\n");
|
|
47699
|
-
return;
|
|
47700
|
-
}
|
|
47701
|
-
for (const t of tasks) {
|
|
47702
|
-
out.write(
|
|
47703
|
-
`${t.name.padEnd(28)} ${stateText(t).padEnd(20)} ${scheduleText(t).padEnd(26)} next ${t.next_run_at} last ${t.last_run_status ?? "-"} ${t.id}
|
|
47704
|
-
`
|
|
47705
|
-
);
|
|
47706
|
-
}
|
|
47707
|
-
}
|
|
47708
|
-
function writeRuns(out, runs) {
|
|
47709
|
-
if (runs.length === 0) {
|
|
47710
|
-
out.write("No runs yet.\n");
|
|
47711
|
-
return;
|
|
47712
|
-
}
|
|
47713
|
-
for (const r of runs) {
|
|
47714
|
-
const tail = r.error !== null ? ` error: ${truncate3(r.error, 160)}` : r.summary !== null ? ` ${truncate3(JSON.stringify(r.summary), 160)}` : "";
|
|
47715
|
-
out.write(
|
|
47716
|
-
`${r.started_at} ${r.status.padEnd(15)} ${r.trigger.padEnd(8)} attempt ${r.attempt} ${r.artifact_version ?? "-"}${tail}
|
|
47717
|
-
`
|
|
47718
|
-
);
|
|
47719
|
-
}
|
|
47784
|
+
serverKey,
|
|
47785
|
+
account: normaliseAccount(opts.account),
|
|
47786
|
+
controlPlane: resolveConnectorsUrl(serverKey, opts.connectorsUrl)
|
|
47787
|
+
};
|
|
47720
47788
|
}
|
|
47721
|
-
function
|
|
47722
|
-
|
|
47789
|
+
function targetArgsOf(opts) {
|
|
47790
|
+
const url = (opts.connectorsUrl ?? "").trim();
|
|
47791
|
+
return ` --server ${resolveServer(opts.server)}${url === "" ? "" : ` --connectors-url ${url}`}`;
|
|
47792
|
+
}
|
|
47793
|
+
function addCommonFlags(cmd, jsonHelp) {
|
|
47794
|
+
cmd.option("--json", jsonHelp, false);
|
|
47723
47795
|
addServerFlag(cmd);
|
|
47724
47796
|
addAccountFlag(cmd);
|
|
47725
47797
|
addConnectorsUrlFlag(cmd);
|
|
47726
|
-
return cmd;
|
|
47727
47798
|
}
|
|
47728
|
-
function
|
|
47729
|
-
return cmd.option("--
|
|
47730
|
-
"--primary-role <role>",
|
|
47731
|
-
"the role passed as the primary datasource (default: the first)"
|
|
47732
|
-
).option("--config <json|@file>", "task config: a JSON object, or @path to a JSON file").option("--every <duration>", "run on an interval, e.g. 5m, 1h (at least 1m)").option("--cron <expr>", 'run on a five-field cron line, e.g. "15 * * * *"').option("--tz <iana>", "timezone the cron line is read in (default UTC)");
|
|
47799
|
+
function addConfirmTokenFlag(cmd) {
|
|
47800
|
+
return cmd.option("--confirm-token <token>", "Token from a prior plan run \u2014 required to proceed");
|
|
47733
47801
|
}
|
|
47734
|
-
|
|
47735
|
-
|
|
47736
|
-
|
|
47737
|
-
|
|
47738
|
-
|
|
47739
|
-
|
|
47740
|
-
|
|
47741
|
-
|
|
47742
|
-
|
|
47743
|
-
|
|
47802
|
+
function addSettingsFlags(cmd) {
|
|
47803
|
+
return cmd.option("--interval <duration>", "how often to poll, 5m to 7d (default: per source)").option("--since <date>", "never pick up items modified before this date or timestamp").option("--lookback <days>", "window every poll lists, 1d to 3650d (default: per source)").option("--settle <duration>", "how long an item must stay unchanged before landing, 0s to 1d").option(
|
|
47804
|
+
"--same-name <policy>",
|
|
47805
|
+
"a changed file under the same name: new-version (lands again) or ignore"
|
|
47806
|
+
);
|
|
47807
|
+
}
|
|
47808
|
+
function ttyChoicePrompt(message, choices, defaultChoice, io = { input: process.stdin, output: process.stderr }) {
|
|
47809
|
+
return new Promise((resolvePrompt) => {
|
|
47810
|
+
const rl = readline2.createInterface({
|
|
47811
|
+
input: io.input,
|
|
47812
|
+
output: io.output,
|
|
47813
|
+
...io.terminal !== void 0 ? { terminal: io.terminal } : {}
|
|
47814
|
+
});
|
|
47815
|
+
let settled = false;
|
|
47816
|
+
const settle = (value) => {
|
|
47817
|
+
if (settled) return;
|
|
47818
|
+
settled = true;
|
|
47819
|
+
rl.close();
|
|
47820
|
+
resolvePrompt(value);
|
|
47821
|
+
};
|
|
47822
|
+
rl.on("SIGINT", () => settle(null));
|
|
47823
|
+
rl.on("close", () => settle(null));
|
|
47824
|
+
io.output.write(`${message}
|
|
47825
|
+
`);
|
|
47826
|
+
const question = defaultChoice === void 0 ? `[${choices.join("/")}]: ` : `[${choices.join("/")}] (Enter = ${defaultChoice}): `;
|
|
47827
|
+
const askOnce = () => {
|
|
47828
|
+
rl.question(question, (answer) => {
|
|
47829
|
+
const picked = answer.trim().toLowerCase();
|
|
47830
|
+
if (picked === "" && defaultChoice !== void 0) {
|
|
47831
|
+
settle(defaultChoice);
|
|
47832
|
+
} else if (choices.includes(picked)) {
|
|
47833
|
+
settle(picked);
|
|
47834
|
+
} else {
|
|
47835
|
+
io.output.write(`Answer one of: ${choices.join(", ")}
|
|
47836
|
+
`);
|
|
47837
|
+
askOnce();
|
|
47838
|
+
}
|
|
47839
|
+
});
|
|
47840
|
+
};
|
|
47841
|
+
askOnce();
|
|
47744
47842
|
});
|
|
47745
47843
|
}
|
|
47746
|
-
|
|
47747
|
-
|
|
47748
|
-
|
|
47749
|
-
|
|
47750
|
-
|
|
47751
|
-
|
|
47844
|
+
var POLL_CONFIRM_HELP = [
|
|
47845
|
+
"Confirmation:",
|
|
47846
|
+
" seed, skip and set --on-policy-change seed print a plan and a --confirm-token;",
|
|
47847
|
+
" re-run the printed command, token included, to apply it.",
|
|
47848
|
+
" No dialog opens on production, so an agent or script can run a cut-over unattended:",
|
|
47849
|
+
" dr connectors poll seed <filebox> --until <cut-over> --server <prod>",
|
|
47850
|
+
" dr connectors poll seed <filebox> --until <cut-over> --server <prod> --confirm-token <token>",
|
|
47851
|
+
" (seed --dry-run counts the already-discovered items a seed would mark.)",
|
|
47852
|
+
" A write with any answer that lands files (set --widen land, set --on-policy-change land,",
|
|
47853
|
+
" resume --source land) also asks a person to type a word in a dialog on production,",
|
|
47854
|
+
" even when the same set also seeds (--on-policy-change seed --widen land)."
|
|
47855
|
+
].join("\n");
|
|
47856
|
+
function registerConnectorsPoll(connectors) {
|
|
47857
|
+
const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version").addHelpText("after", `
|
|
47858
|
+
${POLL_CONFIRM_HELP}`);
|
|
47859
|
+
const enableCmd = addSettingsFlags(
|
|
47860
|
+
poll2.command("enable").description("Poll a filebox's binding (dated with the filebox's DATE tag)").argument("<filebox>", "filebox id or uuid")
|
|
47861
|
+
).action(async (filebox, opts) => {
|
|
47862
|
+
const settings = parseSettings(opts);
|
|
47863
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47864
|
+
await withMonolith(opts, async (monolith) => {
|
|
47865
|
+
await runPollEnable({
|
|
47866
|
+
client,
|
|
47867
|
+
monolith,
|
|
47868
|
+
filebox,
|
|
47869
|
+
settings,
|
|
47870
|
+
asJson: opts.json === true,
|
|
47871
|
+
signal,
|
|
47872
|
+
out: process.stdout
|
|
47873
|
+
});
|
|
47874
|
+
});
|
|
47875
|
+
});
|
|
47876
|
+
});
|
|
47877
|
+
addCommonFlags(enableCmd, "Output the stored binding as JSON");
|
|
47878
|
+
const setCmd = addConfirmTokenFlag(
|
|
47879
|
+
addSettingsFlags(
|
|
47880
|
+
poll2.command("set").description("Change polling settings (only the given flags change)").argument("<filebox>", "filebox id or uuid")
|
|
47881
|
+
).option(
|
|
47882
|
+
"--widen <seed|land>",
|
|
47883
|
+
"a wider window: seed (mark exposed items landed) or land them"
|
|
47884
|
+
).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land").addHelpText("after", `
|
|
47885
|
+
${POLL_CONFIRM_HELP}`)
|
|
47752
47886
|
).action(
|
|
47753
|
-
async (
|
|
47754
|
-
const
|
|
47755
|
-
const
|
|
47756
|
-
|
|
47757
|
-
|
|
47758
|
-
|
|
47759
|
-
|
|
47760
|
-
|
|
47761
|
-
|
|
47762
|
-
}
|
|
47763
|
-
|
|
47764
|
-
|
|
47887
|
+
async (filebox, opts) => {
|
|
47888
|
+
const changes = parseSettings(opts);
|
|
47889
|
+
const widen = opts.widen === void 0 ? void 0 : parseChoice(opts.widen, "--widen", WIDEN_CHOICES);
|
|
47890
|
+
const onPolicyChange = opts.onPolicyChange === void 0 ? void 0 : parseChoice(opts.onPolicyChange, "--on-policy-change", POLICY_CHOICES);
|
|
47891
|
+
await preflightConfirmToken(
|
|
47892
|
+
pollConfirmAction(filebox, pollSetConfirmVerb(widen, onPolicyChange), scopeOf(opts), {
|
|
47893
|
+
...changes,
|
|
47894
|
+
...widen !== void 0 ? { widen } : {},
|
|
47895
|
+
...onPolicyChange !== void 0 ? { on_policy_change: onPolicyChange } : {}
|
|
47896
|
+
}),
|
|
47897
|
+
opts.confirmToken
|
|
47898
|
+
);
|
|
47899
|
+
await withConnectorsSetup(opts, async (setup) => {
|
|
47900
|
+
const deadline = {
|
|
47901
|
+
pause: () => setup.pauseTimeout(),
|
|
47902
|
+
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
47903
|
+
};
|
|
47904
|
+
await runPollSet({
|
|
47905
|
+
client: setup.client,
|
|
47906
|
+
filebox,
|
|
47907
|
+
changes,
|
|
47908
|
+
settingsArgs: settingsArgs(opts),
|
|
47909
|
+
...widen !== void 0 ? { widen } : {},
|
|
47910
|
+
...onPolicyChange !== void 0 ? { onPolicyChange } : {},
|
|
47911
|
+
interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
|
|
47912
|
+
prompt: async (message, choices, defaultChoice) => {
|
|
47913
|
+
deadline.pause();
|
|
47914
|
+
try {
|
|
47915
|
+
return await ttyChoicePrompt(message, choices, defaultChoice);
|
|
47916
|
+
} finally {
|
|
47917
|
+
deadline.resume();
|
|
47918
|
+
}
|
|
47919
|
+
},
|
|
47920
|
+
deadline,
|
|
47921
|
+
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
47922
|
+
scope: scopeOf(opts),
|
|
47923
|
+
targetArgs: targetArgsOf(opts),
|
|
47924
|
+
asJson: opts.json === true,
|
|
47925
|
+
signal: setup.signal,
|
|
47926
|
+
out: process.stdout
|
|
47927
|
+
});
|
|
47765
47928
|
});
|
|
47766
47929
|
}
|
|
47767
47930
|
);
|
|
47768
|
-
|
|
47769
|
-
|
|
47770
|
-
).action(async (opts) => {
|
|
47931
|
+
addCommonFlags(setCmd, "Output the stored binding as JSON");
|
|
47932
|
+
const disableCmd = poll2.command("disable").description("Stop polling (an item already in flight still finishes)").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
|
|
47771
47933
|
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47772
|
-
|
|
47773
|
-
|
|
47774
|
-
|
|
47775
|
-
|
|
47776
|
-
|
|
47777
|
-
|
|
47778
|
-
|
|
47779
|
-
}
|
|
47934
|
+
await runPollDisable({
|
|
47935
|
+
client,
|
|
47936
|
+
filebox,
|
|
47937
|
+
asJson: opts.json === true,
|
|
47938
|
+
signal,
|
|
47939
|
+
out: process.stdout
|
|
47940
|
+
});
|
|
47780
47941
|
});
|
|
47781
47942
|
});
|
|
47782
|
-
|
|
47783
|
-
|
|
47784
|
-
|
|
47785
|
-
|
|
47786
|
-
|
|
47787
|
-
|
|
47788
|
-
|
|
47789
|
-
|
|
47790
|
-
|
|
47791
|
-
|
|
47792
|
-
|
|
47793
|
-
|
|
47794
|
-
|
|
47943
|
+
addCommonFlags(disableCmd, "Output the stored binding as JSON");
|
|
47944
|
+
const getCmd = poll2.command("get").description("Show polling settings, last poll, pending seed / rekey and item counts").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
|
|
47945
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47946
|
+
await runPollGet({
|
|
47947
|
+
client,
|
|
47948
|
+
filebox,
|
|
47949
|
+
asJson: opts.json === true,
|
|
47950
|
+
signal,
|
|
47951
|
+
out: process.stdout
|
|
47952
|
+
});
|
|
47953
|
+
});
|
|
47954
|
+
});
|
|
47955
|
+
addCommonFlags(getCmd, "Output the raw binding as JSON");
|
|
47956
|
+
const runCmd = poll2.command("run").description("Poll now").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
|
|
47957
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
47958
|
+
await runPollRun({
|
|
47959
|
+
client,
|
|
47960
|
+
filebox,
|
|
47961
|
+
asJson: opts.json === true,
|
|
47962
|
+
signal,
|
|
47963
|
+
out: process.stdout
|
|
47964
|
+
});
|
|
47965
|
+
});
|
|
47966
|
+
});
|
|
47967
|
+
addCommonFlags(runCmd, "Output the binding as JSON");
|
|
47968
|
+
const resumeCmd = addConfirmTokenFlag(
|
|
47969
|
+
poll2.command("resume").description("Resume a paused binding (after fixing credentials, or a source change)").argument("<filebox>", "filebox id or uuid").option(
|
|
47970
|
+
"--source <seed|land>",
|
|
47971
|
+
"after a source change: seed (mark present items landed) or land them"
|
|
47795
47972
|
)
|
|
47796
|
-
).action(
|
|
47797
|
-
|
|
47798
|
-
|
|
47799
|
-
|
|
47800
|
-
|
|
47801
|
-
|
|
47802
|
-
if (opts.disable === true) fields.enabled = false;
|
|
47803
|
-
if (Object.keys(fields).length === 0)
|
|
47804
|
-
throw new Error("nothing to change: give at least one flag");
|
|
47805
|
-
await onTask(
|
|
47806
|
-
task,
|
|
47807
|
-
opts,
|
|
47808
|
-
(c, id, signal) => updateTask(c, id, fields, { signal }),
|
|
47809
|
-
(o, r) => writeTask(o, r)
|
|
47973
|
+
).action(async (filebox, opts) => {
|
|
47974
|
+
const source = opts.source === void 0 ? void 0 : parseChoice(opts.source, "--source", WIDEN_CHOICES);
|
|
47975
|
+
if (source === "land") {
|
|
47976
|
+
await preflightConfirmToken(
|
|
47977
|
+
pollConfirmAction(filebox, "resume-land", scopeOf(opts)),
|
|
47978
|
+
opts.confirmToken
|
|
47810
47979
|
);
|
|
47811
47980
|
}
|
|
47812
|
-
|
|
47813
|
-
|
|
47814
|
-
|
|
47815
|
-
|
|
47816
|
-
|
|
47817
|
-
|
|
47818
|
-
|
|
47819
|
-
|
|
47820
|
-
|
|
47821
|
-
|
|
47822
|
-
|
|
47823
|
-
|
|
47824
|
-
|
|
47825
|
-
|
|
47826
|
-
|
|
47827
|
-
|
|
47828
|
-
|
|
47829
|
-
)
|
|
47830
|
-
|
|
47831
|
-
|
|
47832
|
-
|
|
47833
|
-
|
|
47834
|
-
(
|
|
47835
|
-
|
|
47836
|
-
|
|
47837
|
-
|
|
47981
|
+
await withConnectorsSetup(opts, async (setup) => {
|
|
47982
|
+
await runPollResume({
|
|
47983
|
+
client: setup.client,
|
|
47984
|
+
filebox,
|
|
47985
|
+
...source !== void 0 ? { source } : {},
|
|
47986
|
+
deadline: {
|
|
47987
|
+
pause: () => setup.pauseTimeout(),
|
|
47988
|
+
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
47989
|
+
},
|
|
47990
|
+
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
47991
|
+
scope: scopeOf(opts),
|
|
47992
|
+
targetArgs: targetArgsOf(opts),
|
|
47993
|
+
asJson: opts.json === true,
|
|
47994
|
+
signal: setup.signal,
|
|
47995
|
+
out: process.stdout
|
|
47996
|
+
});
|
|
47997
|
+
});
|
|
47998
|
+
});
|
|
47999
|
+
addCommonFlags(resumeCmd, "Output the binding as JSON");
|
|
48000
|
+
const itemsCmd = poll2.command("items").description("List the item ledger: label, date, state, attempts, version, error").argument("<filebox>", "filebox id or uuid").option("--state <state>", `only items in this state (${POLL_ITEM_STATES.join("|")})`).option("--limit <n>", "page size (default and maximum: the control-plane page size, 100)").option("--after <id>", "start after this item id (the next-page cursor)").action(
|
|
48001
|
+
async (filebox, opts) => {
|
|
48002
|
+
const state = opts.state === void 0 ? void 0 : POLL_ITEM_STATES.find((s) => s === opts.state);
|
|
48003
|
+
if (opts.state !== void 0 && state === void 0) {
|
|
48004
|
+
throw new Error(
|
|
48005
|
+
`invalid --state "${opts.state}": expected one of ${POLL_ITEM_STATES.join(", ")}`
|
|
48006
|
+
);
|
|
47838
48007
|
}
|
|
47839
|
-
|
|
47840
|
-
|
|
47841
|
-
|
|
47842
|
-
|
|
47843
|
-
|
|
47844
|
-
|
|
47845
|
-
|
|
47846
|
-
|
|
47847
|
-
|
|
47848
|
-
|
|
47849
|
-
|
|
47850
|
-
|
|
47851
|
-
|
|
47852
|
-
|
|
47853
|
-
|
|
47854
|
-
async (task, opts) => onTask(
|
|
47855
|
-
task,
|
|
47856
|
-
opts,
|
|
47857
|
-
(c, id, signal) => resumeTask(c, id, { signal }),
|
|
47858
|
-
(o, r) => writeTask(o, r)
|
|
47859
|
-
)
|
|
48008
|
+
const limit = opts.limit === void 0 ? void 0 : parsePositiveIntArg2(opts.limit, "--limit");
|
|
48009
|
+
const after = opts.after === void 0 ? void 0 : parsePositiveIntArg2(opts.after, "--after");
|
|
48010
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
48011
|
+
await runPollItems({
|
|
48012
|
+
client,
|
|
48013
|
+
filebox,
|
|
48014
|
+
...state !== void 0 ? { state } : {},
|
|
48015
|
+
...limit !== void 0 ? { limit } : {},
|
|
48016
|
+
...after !== void 0 ? { after } : {},
|
|
48017
|
+
asJson: opts.json === true,
|
|
48018
|
+
signal,
|
|
48019
|
+
out: process.stdout
|
|
48020
|
+
});
|
|
48021
|
+
});
|
|
48022
|
+
}
|
|
47860
48023
|
);
|
|
47861
|
-
|
|
47862
|
-
|
|
47863
|
-
|
|
47864
|
-
|
|
47865
|
-
|
|
47866
|
-
|
|
47867
|
-
|
|
47868
|
-
|
|
47869
|
-
|
|
47870
|
-
|
|
48024
|
+
addCommonFlags(itemsCmd, "Output the raw page as JSON");
|
|
48025
|
+
const retryCmd = poll2.command("retry").description("Queue a failed, waiting or skipped item again").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)").action(async (filebox, item, opts) => {
|
|
48026
|
+
const itemId = parsePositiveIntArg2(item, "item id");
|
|
48027
|
+
await withConnectorsSetup(opts, async ({ client, signal }) => {
|
|
48028
|
+
await runPollRetry({
|
|
48029
|
+
client,
|
|
48030
|
+
filebox,
|
|
48031
|
+
itemId,
|
|
48032
|
+
asJson: opts.json === true,
|
|
48033
|
+
signal,
|
|
48034
|
+
out: process.stdout
|
|
48035
|
+
});
|
|
48036
|
+
});
|
|
48037
|
+
});
|
|
48038
|
+
addCommonFlags(retryCmd, "Output the item as JSON");
|
|
48039
|
+
const skipCmd = addConfirmTokenFlag(
|
|
48040
|
+
poll2.command("skip").description("Skip an item so it is never landed (two-step confirmation)").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)")
|
|
48041
|
+
).action(async (filebox, item, opts) => {
|
|
48042
|
+
const itemId = parsePositiveIntArg2(item, "item id");
|
|
48043
|
+
await preflightConfirmToken(
|
|
48044
|
+
pollItemConfirmAction(filebox, itemId, scopeOf(opts)),
|
|
48045
|
+
opts.confirmToken
|
|
47871
48046
|
);
|
|
48047
|
+
await withConnectorsSetup(opts, async (setup) => {
|
|
48048
|
+
await runPollSkip({
|
|
48049
|
+
client: setup.client,
|
|
48050
|
+
filebox,
|
|
48051
|
+
itemId,
|
|
48052
|
+
deadline: {
|
|
48053
|
+
pause: () => setup.pauseTimeout(),
|
|
48054
|
+
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
48055
|
+
},
|
|
48056
|
+
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
48057
|
+
scope: scopeOf(opts),
|
|
48058
|
+
targetArgs: targetArgsOf(opts),
|
|
48059
|
+
asJson: opts.json === true,
|
|
48060
|
+
signal: setup.signal,
|
|
48061
|
+
out: process.stdout
|
|
48062
|
+
});
|
|
48063
|
+
});
|
|
47872
48064
|
});
|
|
48065
|
+
addCommonFlags(skipCmd, "Output the item as JSON");
|
|
48066
|
+
const seedCmd = addConfirmTokenFlag(
|
|
48067
|
+
poll2.command("seed").description(
|
|
48068
|
+
"Mark items modified at or before --until as already landed (cut-over; token, no dialog)"
|
|
48069
|
+
).argument("<filebox>", "filebox id or uuid").requiredOption(
|
|
48070
|
+
"--until <timestamp>",
|
|
48071
|
+
"date or timestamp with a zone, e.g. 2026-10-08T17:30:00Z"
|
|
48072
|
+
).option(
|
|
48073
|
+
"--dry-run",
|
|
48074
|
+
"count the already-discovered items it would mark, without changing anything",
|
|
48075
|
+
false
|
|
48076
|
+
).addHelpText("after", `
|
|
48077
|
+
${POLL_CONFIRM_HELP}`)
|
|
48078
|
+
).action(
|
|
48079
|
+
async (filebox, opts) => {
|
|
48080
|
+
const until = parseTimestampFlag(opts.until, "--until");
|
|
48081
|
+
const dryRun = opts.dryRun === true;
|
|
48082
|
+
if (!dryRun) {
|
|
48083
|
+
await preflightConfirmToken(
|
|
48084
|
+
pollConfirmAction(filebox, "seed", scopeOf(opts), { until }),
|
|
48085
|
+
opts.confirmToken
|
|
48086
|
+
);
|
|
48087
|
+
}
|
|
48088
|
+
await withConnectorsSetup(opts, async (setup) => {
|
|
48089
|
+
await runPollSeed({
|
|
48090
|
+
client: setup.client,
|
|
48091
|
+
filebox,
|
|
48092
|
+
until,
|
|
48093
|
+
untilArg: opts.until.trim(),
|
|
48094
|
+
dryRun,
|
|
48095
|
+
deadline: {
|
|
48096
|
+
pause: () => setup.pauseTimeout(),
|
|
48097
|
+
resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
|
|
48098
|
+
},
|
|
48099
|
+
...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
|
|
48100
|
+
scope: scopeOf(opts),
|
|
48101
|
+
targetArgs: targetArgsOf(opts),
|
|
48102
|
+
asJson: opts.json === true,
|
|
48103
|
+
signal: setup.signal,
|
|
48104
|
+
out: process.stdout
|
|
48105
|
+
});
|
|
48106
|
+
});
|
|
48107
|
+
}
|
|
48108
|
+
);
|
|
48109
|
+
addCommonFlags(seedCmd, "Output the binding (or the dry-run count) as JSON");
|
|
47873
48110
|
}
|
|
47874
48111
|
|
|
47875
48112
|
// src/commands/connectors.ts
|
|
@@ -48530,6 +48767,7 @@ ${TLS_VPN_HELP}`);
|
|
|
48530
48767
|
registerDevFetch(connectors);
|
|
48531
48768
|
registerConnectorsPoll(connectors);
|
|
48532
48769
|
registerConnectorsTasks(connectors);
|
|
48770
|
+
registerConnectorsAws(connectors);
|
|
48533
48771
|
}
|
|
48534
48772
|
|
|
48535
48773
|
// src/types/dashboards.ts
|
|
@@ -54001,13 +54239,13 @@ async function runReport(cfg) {
|
|
|
54001
54239
|
function collectFile(value, previous) {
|
|
54002
54240
|
return previous.concat([value]);
|
|
54003
54241
|
}
|
|
54004
|
-
async function withOnboardingClient(opts,
|
|
54242
|
+
async function withOnboardingClient(opts, run2, setupOptions = {}) {
|
|
54005
54243
|
const { serverKey, host } = resolveOnboardingTarget(opts.server);
|
|
54006
54244
|
const callOptions = {};
|
|
54007
54245
|
if (setupOptions.timeoutMs !== void 0) callOptions.timeoutMs = setupOptions.timeoutMs;
|
|
54008
54246
|
const setup = await setupCall(serverKey, opts.account, callOptions);
|
|
54009
54247
|
try {
|
|
54010
|
-
await
|
|
54248
|
+
await run2(setup.client, host, setup.signal);
|
|
54011
54249
|
} finally {
|
|
54012
54250
|
setup.cleanup();
|
|
54013
54251
|
}
|
|
@@ -69438,8 +69676,8 @@ async function runExtractFromFiles(cfg) {
|
|
|
69438
69676
|
const p = await preflightFile(f);
|
|
69439
69677
|
parts.push({ blob: p.blob, filename: p.filename, sourcePath: f });
|
|
69440
69678
|
}
|
|
69441
|
-
const
|
|
69442
|
-
const { results: allResults, sourceFiles, outcomes, abortedMidRun } =
|
|
69679
|
+
const run2 = cfg.useAsync === false ? await extractSync(cfg, parts) : await extractAsync(cfg, parts);
|
|
69680
|
+
const { results: allResults, sourceFiles, outcomes, abortedMidRun } = run2;
|
|
69443
69681
|
await emitExtractionDraft(cfg, allResults, sourceFiles, outcomes, abortedMidRun);
|
|
69444
69682
|
}
|
|
69445
69683
|
async function extractAsync(cfg, parts) {
|
|
@@ -71557,8 +71795,8 @@ function querySuffix(domain) {
|
|
|
71557
71795
|
if (domain === void 0 || domain === "") return "";
|
|
71558
71796
|
return `?domain=${encodeURIComponent(domain)}`;
|
|
71559
71797
|
}
|
|
71560
|
-
function required(out,
|
|
71561
|
-
if (out === null) throw new Error(`${
|
|
71798
|
+
function required(out, call3) {
|
|
71799
|
+
if (out === null) throw new Error(`${call3}: empty response body`);
|
|
71562
71800
|
return out;
|
|
71563
71801
|
}
|
|
71564
71802
|
|
|
@@ -73435,6 +73673,175 @@ function registerPalettesCommand(root) {
|
|
|
73435
73673
|
addAccountFlag(createCmd);
|
|
73436
73674
|
}
|
|
73437
73675
|
|
|
73676
|
+
// src/api/businessCalendars.ts
|
|
73677
|
+
function buildAuditQs(q) {
|
|
73678
|
+
if (q === void 0) return "";
|
|
73679
|
+
const sp = new URLSearchParams();
|
|
73680
|
+
if (q.since !== void 0) sp.set("since", q.since);
|
|
73681
|
+
if (q.until !== void 0) sp.set("until", q.until);
|
|
73682
|
+
if (q.action !== void 0) sp.set("action", q.action);
|
|
73683
|
+
if (q.actor_user_id !== void 0) sp.set("actor_user_id", String(q.actor_user_id));
|
|
73684
|
+
if (q.page !== void 0) sp.set("page", String(q.page));
|
|
73685
|
+
if (q.page_size !== void 0) sp.set("page_size", String(q.page_size));
|
|
73686
|
+
const s = sp.toString();
|
|
73687
|
+
return s === "" ? "" : `?${s}`;
|
|
73688
|
+
}
|
|
73689
|
+
async function listAudit(client, calendarId, query, options) {
|
|
73690
|
+
if (calendarId === "") {
|
|
73691
|
+
throw new Error("calendarId must be a non-empty string");
|
|
73692
|
+
}
|
|
73693
|
+
const path13 = `/api/business-calendar/v1/business-calendars/${encodeURIComponent(
|
|
73694
|
+
calendarId
|
|
73695
|
+
)}/audit${buildAuditQs(query)}`;
|
|
73696
|
+
const out = await client.getJSON(path13, options);
|
|
73697
|
+
if (out === null) {
|
|
73698
|
+
return { count: 0, results: [] };
|
|
73699
|
+
}
|
|
73700
|
+
return out;
|
|
73701
|
+
}
|
|
73702
|
+
|
|
73703
|
+
// src/commands/calendars.ts
|
|
73704
|
+
var KNOWN_ACTIONS = /* @__PURE__ */ new Set([
|
|
73705
|
+
"create",
|
|
73706
|
+
"edit",
|
|
73707
|
+
"archive",
|
|
73708
|
+
"restore",
|
|
73709
|
+
"add_exception",
|
|
73710
|
+
"remove_exception"
|
|
73711
|
+
]);
|
|
73712
|
+
function parseAuditAction(v) {
|
|
73713
|
+
if (!KNOWN_ACTIONS.has(v)) {
|
|
73714
|
+
throw new Error(`invalid --action "${v}" (expected one of: ${[...KNOWN_ACTIONS].join(", ")})`);
|
|
73715
|
+
}
|
|
73716
|
+
return v;
|
|
73717
|
+
}
|
|
73718
|
+
function parsePositiveInt4(flag, v) {
|
|
73719
|
+
const n = Number(v);
|
|
73720
|
+
if (!Number.isInteger(n) || String(n) !== v || n <= 0) {
|
|
73721
|
+
throw new Error(`invalid ${flag} "${v}" (expected: positive integer)`);
|
|
73722
|
+
}
|
|
73723
|
+
return n;
|
|
73724
|
+
}
|
|
73725
|
+
async function runCalendarsAuditList(cfg) {
|
|
73726
|
+
if (cfg.calendarId === "") {
|
|
73727
|
+
throw new Error("<calendar_id> is required");
|
|
73728
|
+
}
|
|
73729
|
+
const resp = await listAudit(
|
|
73730
|
+
cfg.client,
|
|
73731
|
+
cfg.calendarId,
|
|
73732
|
+
{
|
|
73733
|
+
since: cfg.since,
|
|
73734
|
+
until: cfg.until,
|
|
73735
|
+
action: cfg.action,
|
|
73736
|
+
actor_user_id: cfg.actorUserId,
|
|
73737
|
+
page: cfg.page,
|
|
73738
|
+
page_size: cfg.limit
|
|
73739
|
+
},
|
|
73740
|
+
{ signal: cfg.signal }
|
|
73741
|
+
);
|
|
73742
|
+
if (cfg.asJson) {
|
|
73743
|
+
writeJSONIndented(cfg.out, resp);
|
|
73744
|
+
return;
|
|
73745
|
+
}
|
|
73746
|
+
renderAuditTable(cfg.out, resp);
|
|
73747
|
+
}
|
|
73748
|
+
function writeTable44(out, rows) {
|
|
73749
|
+
if (rows.length === 0) return;
|
|
73750
|
+
const widths = [];
|
|
73751
|
+
for (const r of rows) {
|
|
73752
|
+
for (let i = 0; i < r.cells.length; i++) {
|
|
73753
|
+
const cell = r.cells[i] ?? "";
|
|
73754
|
+
if ((widths[i] ?? 0) < cell.length) widths[i] = cell.length;
|
|
73755
|
+
}
|
|
73756
|
+
}
|
|
73757
|
+
for (const r of rows) {
|
|
73758
|
+
const parts = [];
|
|
73759
|
+
for (let i = 0; i < r.cells.length; i++) {
|
|
73760
|
+
const cell = r.cells[i] ?? "";
|
|
73761
|
+
if (i === r.cells.length - 1) {
|
|
73762
|
+
parts.push(cell);
|
|
73763
|
+
} else {
|
|
73764
|
+
parts.push(cell.padEnd((widths[i] ?? 0) + 2, " "));
|
|
73765
|
+
}
|
|
73766
|
+
}
|
|
73767
|
+
out.write(parts.join("") + "\n");
|
|
73768
|
+
}
|
|
73769
|
+
}
|
|
73770
|
+
function renderActor(row) {
|
|
73771
|
+
const email = row.actor.email ?? "";
|
|
73772
|
+
return email === "" ? `#${row.actor.id}` : `${email} (#${row.actor.id})`;
|
|
73773
|
+
}
|
|
73774
|
+
function renderFieldSummary(row) {
|
|
73775
|
+
const n = row.field_changes.length;
|
|
73776
|
+
if (n === 0) return "(none)";
|
|
73777
|
+
if (n === 1) return row.field_changes[0]?.field ?? "(1 field)";
|
|
73778
|
+
return `${n} fields`;
|
|
73779
|
+
}
|
|
73780
|
+
function renderAuditTable(out, resp) {
|
|
73781
|
+
if (resp.results.length === 0) {
|
|
73782
|
+
out.write(`(no audit rows; count=${resp.count})
|
|
73783
|
+
`);
|
|
73784
|
+
return;
|
|
73785
|
+
}
|
|
73786
|
+
const rows = [{ cells: ["ID", "TIMESTAMP", "ACTION", "VERSION", "ACTOR", "CHANGES"] }];
|
|
73787
|
+
for (const r of resp.results) {
|
|
73788
|
+
rows.push({
|
|
73789
|
+
cells: [
|
|
73790
|
+
String(r.id),
|
|
73791
|
+
r.timestamp,
|
|
73792
|
+
r.action,
|
|
73793
|
+
String(r.resulting_version),
|
|
73794
|
+
renderActor(r),
|
|
73795
|
+
renderFieldSummary(r)
|
|
73796
|
+
]
|
|
73797
|
+
});
|
|
73798
|
+
}
|
|
73799
|
+
writeTable44(out, rows);
|
|
73800
|
+
if (resp.count > resp.results.length) {
|
|
73801
|
+
out.write(`
|
|
73802
|
+
(showing ${resp.results.length} of ${resp.count}; use --limit / --page)
|
|
73803
|
+
`);
|
|
73804
|
+
}
|
|
73805
|
+
}
|
|
73806
|
+
function registerCalendarsCommand(root) {
|
|
73807
|
+
const cmd = root.command("calendars").description("Business-calendar (dr-business-calendar) operations");
|
|
73808
|
+
const audit = cmd.command("audit").description("Calendar-change audit log (FR-L13)");
|
|
73809
|
+
const listCmd = audit.command("list").description(
|
|
73810
|
+
"List calendar-change audit rows for one calendar (GET /v1/business-calendars/{id}/audit; isAdminOrSuperAdmin)"
|
|
73811
|
+
).argument("<calendar_id>", "Calendar id (UUID, any non-empty string)").option("--since <ISO>", "Only rows at or after this ISO timestamp").option("--until <ISO>", "Only rows strictly before this ISO timestamp").option(
|
|
73812
|
+
"--action <enum>",
|
|
73813
|
+
"Only rows for one action: create | edit | archive | restore | add_exception | remove_exception"
|
|
73814
|
+
).option("--actor-user-id <id>", "Only rows written by this user id (positive integer)").option("--limit <N>", "Max rows per page (positive integer; server page_size)").option("--page <N>", "Page number (positive integer; defaults to server page 1)").option("--json", "Output raw JSON instead of a table", false).action(async (calendarId, opts) => {
|
|
73815
|
+
if (calendarId === "") {
|
|
73816
|
+
throw new Error("<calendar_id> is required");
|
|
73817
|
+
}
|
|
73818
|
+
const action = opts.action === void 0 ? void 0 : parseAuditAction(opts.action);
|
|
73819
|
+
const actorUserId = opts.actorUserId === void 0 ? void 0 : parsePositiveInt4("--actor-user-id", opts.actorUserId);
|
|
73820
|
+
const limit = opts.limit === void 0 ? void 0 : parsePositiveInt4("--limit", opts.limit);
|
|
73821
|
+
const page = opts.page === void 0 ? void 0 : parsePositiveInt4("--page", opts.page);
|
|
73822
|
+
const setup = await setupCall(opts.server, opts.account);
|
|
73823
|
+
try {
|
|
73824
|
+
await runCalendarsAuditList({
|
|
73825
|
+
client: setup.client,
|
|
73826
|
+
calendarId,
|
|
73827
|
+
since: opts.since,
|
|
73828
|
+
until: opts.until,
|
|
73829
|
+
action,
|
|
73830
|
+
actorUserId,
|
|
73831
|
+
limit,
|
|
73832
|
+
page,
|
|
73833
|
+
asJson: opts.json === true,
|
|
73834
|
+
signal: setup.signal,
|
|
73835
|
+
out: process.stdout
|
|
73836
|
+
});
|
|
73837
|
+
} finally {
|
|
73838
|
+
setup.cleanup();
|
|
73839
|
+
}
|
|
73840
|
+
});
|
|
73841
|
+
addServerFlag(listCmd);
|
|
73842
|
+
addAccountFlag(listCmd);
|
|
73843
|
+
}
|
|
73844
|
+
|
|
73438
73845
|
// src/commands/appSpace.ts
|
|
73439
73846
|
import { buildRootCommand as buildAppSpaceRoot } from "@datarails-ai/dr-appspace-cli/dist/cli.js";
|
|
73440
73847
|
function registerAppSpaceCommand(root) {
|
|
@@ -74048,6 +74455,7 @@ function buildRootCommand() {
|
|
|
74048
74455
|
registerMultiProductCommand(root);
|
|
74049
74456
|
registerDeskCommand(root);
|
|
74050
74457
|
registerPalettesCommand(root);
|
|
74458
|
+
registerCalendarsCommand(root);
|
|
74051
74459
|
registerAppSpaceCommand(root);
|
|
74052
74460
|
return root;
|
|
74053
74461
|
}
|