@sleds/cli 0.1.64 → 0.1.65
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 +20 -0
- package/dist/sleds.mjs +1132 -58
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -6061,7 +6061,6 @@ var init_adf = __esm(() => {
|
|
|
6061
6061
|
"decisionList"
|
|
6062
6062
|
]);
|
|
6063
6063
|
});
|
|
6064
|
-
|
|
6065
6064
|
// ../../packages/shared/src/help/whatsNew.ts
|
|
6066
6065
|
var SEPT_30 = "/whats-new/2026-09-30", WHATS_NEW;
|
|
6067
6066
|
var init_whatsNew = __esm(() => {
|
|
@@ -6836,6 +6835,30 @@ var init_stability = __esm(() => {
|
|
|
6836
6835
|
init_series();
|
|
6837
6836
|
});
|
|
6838
6837
|
|
|
6838
|
+
// ../../packages/shared/src/deploys/shipping.ts
|
|
6839
|
+
var DAY_MS5;
|
|
6840
|
+
var init_shipping = __esm(() => {
|
|
6841
|
+
DAY_MS5 = 24 * 60 * 60 * 1000;
|
|
6842
|
+
});
|
|
6843
|
+
|
|
6844
|
+
// ../../packages/shared/src/deploys/doraBands.ts
|
|
6845
|
+
var HOUR_MS2, DAY_MS6, WEEK_MS3, MONTH_DAYS = 30, MONTH_MS;
|
|
6846
|
+
var init_doraBands = __esm(() => {
|
|
6847
|
+
HOUR_MS2 = 60 * 60 * 1000;
|
|
6848
|
+
DAY_MS6 = 24 * HOUR_MS2;
|
|
6849
|
+
WEEK_MS3 = 7 * DAY_MS6;
|
|
6850
|
+
MONTH_MS = MONTH_DAYS * DAY_MS6;
|
|
6851
|
+
});
|
|
6852
|
+
|
|
6853
|
+
// ../../packages/shared/src/deploys/trends.ts
|
|
6854
|
+
var DAY_MS7;
|
|
6855
|
+
var init_trends = __esm(() => {
|
|
6856
|
+
init_batchSize();
|
|
6857
|
+
init_metrics();
|
|
6858
|
+
init_series();
|
|
6859
|
+
DAY_MS7 = 24 * 60 * 60 * 1000;
|
|
6860
|
+
});
|
|
6861
|
+
|
|
6839
6862
|
// ../../packages/shared/src/newrelic/newrelic.ts
|
|
6840
6863
|
var init_newrelic = () => {};
|
|
6841
6864
|
|
|
@@ -8353,6 +8376,9 @@ var init_src = __esm(() => {
|
|
|
8353
8376
|
init_batchSize();
|
|
8354
8377
|
init_report();
|
|
8355
8378
|
init_series();
|
|
8379
|
+
init_shipping();
|
|
8380
|
+
init_doraBands();
|
|
8381
|
+
init_trends();
|
|
8356
8382
|
init_sentry();
|
|
8357
8383
|
init_newrelic();
|
|
8358
8384
|
init_musher();
|
|
@@ -9071,8 +9097,8 @@ var init_seat = __esm(() => {
|
|
|
9071
9097
|
});
|
|
9072
9098
|
|
|
9073
9099
|
// src/index.ts
|
|
9074
|
-
import { existsSync as
|
|
9075
|
-
import { homedir as
|
|
9100
|
+
import { existsSync as existsSync37, realpathSync as realpathSync3 } from "fs";
|
|
9101
|
+
import { homedir as homedir11, platform as platform3 } from "os";
|
|
9076
9102
|
import { fileURLToPath } from "url";
|
|
9077
9103
|
|
|
9078
9104
|
// ../../packages/shared/src/index.ts
|
|
@@ -9124,6 +9150,9 @@ init_stability();
|
|
|
9124
9150
|
init_batchSize();
|
|
9125
9151
|
init_report();
|
|
9126
9152
|
init_series();
|
|
9153
|
+
init_shipping();
|
|
9154
|
+
init_doraBands();
|
|
9155
|
+
init_trends();
|
|
9127
9156
|
init_sentry();
|
|
9128
9157
|
init_newrelic();
|
|
9129
9158
|
init_musher();
|
|
@@ -11793,7 +11822,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
|
|
|
11793
11822
|
name: flags.name,
|
|
11794
11823
|
os: host.os ?? process.platform,
|
|
11795
11824
|
runner: flags.runner,
|
|
11796
|
-
version: host.version ?? "0.1.
|
|
11825
|
+
version: host.version ?? "0.1.65" ?? "dev",
|
|
11797
11826
|
running: live.length,
|
|
11798
11827
|
items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
|
|
11799
11828
|
...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
|
|
@@ -18611,7 +18640,7 @@ async function fetchVersion(o) {
|
|
|
18611
18640
|
const platform = o.platform ?? process.platform;
|
|
18612
18641
|
const arch = o.arch ?? process.arch;
|
|
18613
18642
|
const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
|
|
18614
|
-
const running = o.running ?? "0.1.
|
|
18643
|
+
const running = o.running ?? "0.1.65";
|
|
18615
18644
|
if (!publicKey)
|
|
18616
18645
|
return { ok: false, why: "no release key yet" };
|
|
18617
18646
|
if (!VERSION_RE2.test(o.version))
|
|
@@ -19837,7 +19866,7 @@ function engineUpdater(o) {
|
|
|
19837
19866
|
|
|
19838
19867
|
// src/version.ts
|
|
19839
19868
|
function cliVersion(env) {
|
|
19840
|
-
return "0.1.
|
|
19869
|
+
return "0.1.65";
|
|
19841
19870
|
}
|
|
19842
19871
|
|
|
19843
19872
|
// src/commands/update.ts
|
|
@@ -20223,7 +20252,7 @@ function engineClient(o, org, breaker) {
|
|
|
20223
20252
|
...src,
|
|
20224
20253
|
...breaker ? { fetchFn: breaker.fetch } : {},
|
|
20225
20254
|
timeoutMs: ENGINE_TIMEOUT_MS,
|
|
20226
|
-
version: "0.1.
|
|
20255
|
+
version: "0.1.65"
|
|
20227
20256
|
});
|
|
20228
20257
|
const asked = org?.trim() || env.SLEDS_ORG?.trim();
|
|
20229
20258
|
if (asked && !selectOrg(readConfig(o.home), org, env).seat)
|
|
@@ -20529,7 +20558,7 @@ async function driveEngine(o, flags, deps, until = {}) {
|
|
|
20529
20558
|
runner: deps.runner.name,
|
|
20530
20559
|
atOnce: flags.atOnce,
|
|
20531
20560
|
interval: flags.interval,
|
|
20532
|
-
cliVersion: "0.1.
|
|
20561
|
+
cliVersion: "0.1.65"
|
|
20533
20562
|
};
|
|
20534
20563
|
const beat = (checkedAt) => {
|
|
20535
20564
|
const m = deps.machine;
|
|
@@ -22398,7 +22427,7 @@ function engineClient2(o, org, breaker) {
|
|
|
22398
22427
|
...src,
|
|
22399
22428
|
...breaker ? { fetchFn: breaker.fetch } : {},
|
|
22400
22429
|
timeoutMs: ENGINE_TIMEOUT_MS2,
|
|
22401
|
-
version: "0.1.
|
|
22430
|
+
version: "0.1.65"
|
|
22402
22431
|
});
|
|
22403
22432
|
const asked = org?.trim() || env.SLEDS_ORG?.trim();
|
|
22404
22433
|
if (asked && !selectOrg(readConfig(o.home), org, env).seat)
|
|
@@ -22739,7 +22768,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
|
|
|
22739
22768
|
runner: deps.runner.name,
|
|
22740
22769
|
atOnce: flags.atOnce,
|
|
22741
22770
|
interval: flags.interval,
|
|
22742
|
-
cliVersion: "0.1.
|
|
22771
|
+
cliVersion: "0.1.65"
|
|
22743
22772
|
};
|
|
22744
22773
|
const beat = (checkedAt) => {
|
|
22745
22774
|
const m = deps.machine;
|
|
@@ -24850,7 +24879,7 @@ function contextFor(deps) {
|
|
|
24850
24879
|
...src,
|
|
24851
24880
|
fetchFn: deps.fetchFn,
|
|
24852
24881
|
timeoutMs: deps.timeoutMs,
|
|
24853
|
-
version: "0.1.
|
|
24882
|
+
version: "0.1.65"
|
|
24854
24883
|
}), deps.home, now) : null;
|
|
24855
24884
|
return {
|
|
24856
24885
|
channel: "code",
|
|
@@ -28367,9 +28396,1048 @@ var sessionEvent = async (o) => {
|
|
|
28367
28396
|
return 0;
|
|
28368
28397
|
};
|
|
28369
28398
|
|
|
28399
|
+
// src/commands/sleuth.ts
|
|
28400
|
+
import { homedir as homedir8 } from "node:os";
|
|
28401
|
+
import { join as join35, resolve as resolve12 } from "node:path";
|
|
28402
|
+
|
|
28403
|
+
// src/sleuth/export.ts
|
|
28404
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync28, readFileSync as readFileSync32, renameSync as renameSync13, writeFileSync as writeFileSync28 } from "node:fs";
|
|
28405
|
+
import { dirname as dirname25, join as join34 } from "node:path";
|
|
28406
|
+
|
|
28407
|
+
// src/sleuth/graphql.ts
|
|
28408
|
+
var SLEUTH_URL = "https://app.sleuth.io/graphql";
|
|
28409
|
+
|
|
28410
|
+
class SleuthError extends Error {
|
|
28411
|
+
adaptable;
|
|
28412
|
+
constructor(message, adaptable) {
|
|
28413
|
+
super(message);
|
|
28414
|
+
this.adaptable = adaptable;
|
|
28415
|
+
}
|
|
28416
|
+
}
|
|
28417
|
+
var RATE = /rate.?limit|throttl|too many requests/i;
|
|
28418
|
+
var backoffMs = (attempt, retryAfter) => {
|
|
28419
|
+
const s = Number(retryAfter);
|
|
28420
|
+
if (retryAfter && Number.isFinite(s) && s >= 0)
|
|
28421
|
+
return Math.min(s, 300) * 1000;
|
|
28422
|
+
return Math.min(60000, 1000 * 2 ** attempt);
|
|
28423
|
+
};
|
|
28424
|
+
var sleuthClient = (o) => {
|
|
28425
|
+
const url = o.url ?? SLEUTH_URL;
|
|
28426
|
+
const fetchFn = o.fetch ?? ((u, init) => fetch(u, init));
|
|
28427
|
+
const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
28428
|
+
const retries = o.retries ?? 6;
|
|
28429
|
+
const timeoutMs = o.timeoutMs ?? 120000;
|
|
28430
|
+
let scheme = "apikey";
|
|
28431
|
+
let settled = false;
|
|
28432
|
+
let count = 0;
|
|
28433
|
+
const request = async (query) => {
|
|
28434
|
+
let lastWhy = "";
|
|
28435
|
+
for (let attempt = 0;attempt <= retries; attempt++) {
|
|
28436
|
+
let res;
|
|
28437
|
+
count++;
|
|
28438
|
+
try {
|
|
28439
|
+
res = await fetchFn(url, {
|
|
28440
|
+
method: "POST",
|
|
28441
|
+
headers: { "content-type": "application/json", authorization: `${scheme} ${o.key}` },
|
|
28442
|
+
body: JSON.stringify({ query }),
|
|
28443
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
28444
|
+
});
|
|
28445
|
+
} catch (e) {
|
|
28446
|
+
lastWhy = e instanceof Error ? e.message : String(e);
|
|
28447
|
+
o.log?.(`sleuth: ${lastWhy} — retrying`);
|
|
28448
|
+
await sleep(backoffMs(attempt, null));
|
|
28449
|
+
continue;
|
|
28450
|
+
}
|
|
28451
|
+
if ((res.status === 401 || res.status === 403) && !settled) {
|
|
28452
|
+
settled = true;
|
|
28453
|
+
scheme = "Bearer";
|
|
28454
|
+
attempt--;
|
|
28455
|
+
continue;
|
|
28456
|
+
}
|
|
28457
|
+
if (res.status === 401 || res.status === 403)
|
|
28458
|
+
throw new SleuthError(`Sleuth refused the key (HTTP ${res.status}) — check SLEUTH_API_KEY`, false);
|
|
28459
|
+
if (res.status === 429 || res.status >= 500) {
|
|
28460
|
+
lastWhy = `HTTP ${res.status}`;
|
|
28461
|
+
const wait = backoffMs(attempt, res.headers.get("retry-after"));
|
|
28462
|
+
o.log?.(`sleuth: ${lastWhy} — waiting ${Math.round(wait / 1000)}s`);
|
|
28463
|
+
await sleep(wait);
|
|
28464
|
+
continue;
|
|
28465
|
+
}
|
|
28466
|
+
const text = await res.text();
|
|
28467
|
+
let body;
|
|
28468
|
+
try {
|
|
28469
|
+
body = JSON.parse(text);
|
|
28470
|
+
} catch {
|
|
28471
|
+
throw new SleuthError(`Sleuth answered HTTP ${res.status} with no JSON`, res.status < 500);
|
|
28472
|
+
}
|
|
28473
|
+
const errors = Array.isArray(body.errors) ? body.errors : [];
|
|
28474
|
+
if (res.status >= 400 && !errors.length)
|
|
28475
|
+
throw new SleuthError(`Sleuth answered HTTP ${res.status}`, false);
|
|
28476
|
+
settled = true;
|
|
28477
|
+
if (!body.data && errors.some((e) => RATE.test(e.message))) {
|
|
28478
|
+
lastWhy = errors[0]?.message ?? "rate limited";
|
|
28479
|
+
await sleep(backoffMs(attempt, null));
|
|
28480
|
+
continue;
|
|
28481
|
+
}
|
|
28482
|
+
return { data: body.data ?? null, errors };
|
|
28483
|
+
}
|
|
28484
|
+
throw new SleuthError(`Sleuth did not answer after ${retries + 1} tries (${lastWhy})`, true);
|
|
28485
|
+
};
|
|
28486
|
+
return { request, calls: () => count };
|
|
28487
|
+
};
|
|
28488
|
+
|
|
28489
|
+
// src/sleuth/schema.ts
|
|
28490
|
+
var TYPE_REF = "kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } }";
|
|
28491
|
+
var INTROSPECTION_QUERY = `query SledsSleuthIntrospection { __schema { queryType { name } types { kind name fields(includeDeprecated: false) { name args { name defaultValue type { ${TYPE_REF} } } type { ${TYPE_REF} } } inputFields { name defaultValue type { ${TYPE_REF} } } } } }`;
|
|
28492
|
+
var indexSchema = (intro) => ({
|
|
28493
|
+
query: intro.__schema.queryType.name,
|
|
28494
|
+
types: new Map(intro.__schema.types.map((t) => [t.name, t]))
|
|
28495
|
+
});
|
|
28496
|
+
var unwrap = (t) => {
|
|
28497
|
+
let cur = t;
|
|
28498
|
+
let list = false;
|
|
28499
|
+
const required = t.kind === "NON_NULL";
|
|
28500
|
+
while (cur && (cur.kind === "NON_NULL" || cur.kind === "LIST")) {
|
|
28501
|
+
if (cur.kind === "LIST")
|
|
28502
|
+
list = true;
|
|
28503
|
+
cur = cur.ofType;
|
|
28504
|
+
}
|
|
28505
|
+
return { name: cur?.name ?? "", list, required };
|
|
28506
|
+
};
|
|
28507
|
+
var kindOf = (s, name) => s.types.get(name)?.kind ?? "";
|
|
28508
|
+
var isLeaf = (s, name) => ["SCALAR", "ENUM"].includes(kindOf(s, name));
|
|
28509
|
+
var isComposite = (s, name) => ["OBJECT", "INTERFACE"].includes(kindOf(s, name));
|
|
28510
|
+
var isRequired = (a) => a.type.kind === "NON_NULL" && (a.defaultValue === null || a.defaultValue === undefined);
|
|
28511
|
+
var fieldOf2 = (s, type, name) => s.types.get(type)?.fields?.find((f) => f.name === name);
|
|
28512
|
+
var ORG = /^(org|orgSlug|organization|organizationSlug|orgId)$/i;
|
|
28513
|
+
var START = /^(start|startDate|startTime|startAt|from|fromDate|dateFrom|since|begin|after)$/i;
|
|
28514
|
+
var END = /^(end|endDate|endTime|endAt|to|toDate|dateTo|until|before)$/i;
|
|
28515
|
+
var CURSOR = /^(after|cursor|nextCursor|pageCursor|pageToken)$/i;
|
|
28516
|
+
var PAGE = /^(page|pageNumber|pageNum)$/i;
|
|
28517
|
+
var SIZE = /^(first|pageSize|limit|perPage|per_page|size|count|pageLimit)$/i;
|
|
28518
|
+
var OFFSET = /^(offset|skip)$/i;
|
|
28519
|
+
var DATEISH = /date|time/i;
|
|
28520
|
+
var dateArg = (path, typeName) => ({
|
|
28521
|
+
path,
|
|
28522
|
+
dateOnly: /^date$/i.test(typeName)
|
|
28523
|
+
});
|
|
28524
|
+
var planArgs = (s, args) => {
|
|
28525
|
+
const plan = { org: [], missing: [] };
|
|
28526
|
+
const visit = (vals, prefix, depth) => {
|
|
28527
|
+
for (const a of vals) {
|
|
28528
|
+
const u = unwrap(a.type);
|
|
28529
|
+
const path = [...prefix, a.name];
|
|
28530
|
+
const leaf = isLeaf(s, u.name);
|
|
28531
|
+
const dated = leaf && (DATEISH.test(u.name) || u.name === "String" && DATEISH.test(a.name) && (START.test(a.name) || END.test(a.name)));
|
|
28532
|
+
if (kindOf(s, u.name) === "INPUT_OBJECT") {
|
|
28533
|
+
const before = JSON.stringify(plan);
|
|
28534
|
+
const inputs = s.types.get(u.name)?.inputFields ?? [];
|
|
28535
|
+
if (depth < 3)
|
|
28536
|
+
visit(inputs, path, depth + 1);
|
|
28537
|
+
if (isRequired(a) && JSON.stringify(plan) === before && inputs.some(isRequired))
|
|
28538
|
+
plan.missing.push(path.join("."));
|
|
28539
|
+
continue;
|
|
28540
|
+
}
|
|
28541
|
+
if (leaf && !u.list && ORG.test(a.name))
|
|
28542
|
+
plan.org.push(path);
|
|
28543
|
+
else if (dated && START.test(a.name) && !plan.start)
|
|
28544
|
+
plan.start = dateArg(path, u.name);
|
|
28545
|
+
else if (dated && END.test(a.name) && !plan.end)
|
|
28546
|
+
plan.end = dateArg(path, u.name);
|
|
28547
|
+
else if (leaf && !dated && CURSOR.test(a.name) && !plan.cursor)
|
|
28548
|
+
plan.cursor = path;
|
|
28549
|
+
else if (leaf && PAGE.test(a.name) && !plan.page)
|
|
28550
|
+
plan.page = { path, first: Number(a.defaultValue ?? 1) || 0 };
|
|
28551
|
+
else if (leaf && SIZE.test(a.name) && !plan.size)
|
|
28552
|
+
plan.size = path;
|
|
28553
|
+
else if (leaf && OFFSET.test(a.name) && !plan.offset)
|
|
28554
|
+
plan.offset = path;
|
|
28555
|
+
else if (isRequired(a))
|
|
28556
|
+
plan.missing.push(path.join("."));
|
|
28557
|
+
}
|
|
28558
|
+
};
|
|
28559
|
+
visit(args, [], 0);
|
|
28560
|
+
return plan;
|
|
28561
|
+
};
|
|
28562
|
+
var windowed = (p) => !!(p.start && p.end);
|
|
28563
|
+
var pageable = (p) => !!(p.cursor || p.page || p.offset);
|
|
28564
|
+
var iso3 = (d, dateOnly) => dateOnly ? d.toISOString().slice(0, 10) : d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
28565
|
+
var gqlValue = (v) => {
|
|
28566
|
+
if (typeof v === "string")
|
|
28567
|
+
return JSON.stringify(v);
|
|
28568
|
+
if (typeof v === "number" || typeof v === "boolean")
|
|
28569
|
+
return String(v);
|
|
28570
|
+
if (Array.isArray(v))
|
|
28571
|
+
return `[${v.map(gqlValue).join(", ")}]`;
|
|
28572
|
+
if (v && typeof v === "object")
|
|
28573
|
+
return `{${Object.entries(v).map(([k, x]) => `${k}: ${gqlValue(x)}`).join(", ")}}`;
|
|
28574
|
+
return "null";
|
|
28575
|
+
};
|
|
28576
|
+
var renderArgs = (p, v) => {
|
|
28577
|
+
const root = {};
|
|
28578
|
+
const put = (path, value) => {
|
|
28579
|
+
if (!path || value === undefined)
|
|
28580
|
+
return;
|
|
28581
|
+
let o = root;
|
|
28582
|
+
for (const k of path.slice(0, -1)) {
|
|
28583
|
+
if (!o[k])
|
|
28584
|
+
o[k] = {};
|
|
28585
|
+
o = o[k];
|
|
28586
|
+
}
|
|
28587
|
+
o[path[path.length - 1]] = value;
|
|
28588
|
+
};
|
|
28589
|
+
for (const path of p.org)
|
|
28590
|
+
put(path, v.org);
|
|
28591
|
+
if (p.start && v.start)
|
|
28592
|
+
put(p.start.path, iso3(v.start, p.start.dateOnly));
|
|
28593
|
+
if (p.end && v.end)
|
|
28594
|
+
put(p.end.path, iso3(v.end, p.end.dateOnly));
|
|
28595
|
+
put(p.cursor, v.cursor);
|
|
28596
|
+
put(p.page?.path, v.page);
|
|
28597
|
+
put(p.size, v.size);
|
|
28598
|
+
put(p.offset, v.offset);
|
|
28599
|
+
const body = gqlValue(root);
|
|
28600
|
+
return body === "{}" ? "" : `(${body.slice(1, -1)})`;
|
|
28601
|
+
};
|
|
28602
|
+
var ITEMS = ["objects", "nodes", "items", "results", "edges", "data", "entries", "records"];
|
|
28603
|
+
var HAS_NEXT = /^(hasNextPage|hasNext|hasMore|more)$/;
|
|
28604
|
+
var NEXT = /^(endCursor|nextCursor|cursor|next|nextPageCursor|nextPage)$/;
|
|
28605
|
+
var TOTAL = /^(totalCount|total|count|totalObjects|totalItems)$/;
|
|
28606
|
+
var shapeOf = (s, f) => {
|
|
28607
|
+
const u = unwrap(f.type);
|
|
28608
|
+
if (!isComposite(s, u.name))
|
|
28609
|
+
return;
|
|
28610
|
+
if (u.list)
|
|
28611
|
+
return { kind: "list", item: u.name };
|
|
28612
|
+
const t = s.types.get(u.name);
|
|
28613
|
+
const lists = (t?.fields ?? []).filter((x) => {
|
|
28614
|
+
const xu = unwrap(x.type);
|
|
28615
|
+
return xu.list && isComposite(s, xu.name) && !x.args.some(isRequired);
|
|
28616
|
+
});
|
|
28617
|
+
const rank = (n) => ITEMS.includes(n) ? ITEMS.indexOf(n) : ITEMS.length;
|
|
28618
|
+
const itemsField = [...lists].sort((a, b) => rank(a.name) - rank(b.name))[0];
|
|
28619
|
+
const scalars = (t?.fields ?? []).filter((x) => isLeaf(s, unwrap(x.type).name));
|
|
28620
|
+
const info = (t?.fields ?? []).find((x) => /^pageInfo$/i.test(x.name) && isComposite(s, unwrap(x.type).name));
|
|
28621
|
+
const infoScalars = info ? (s.types.get(unwrap(info.type).name)?.fields ?? []).filter((x) => isLeaf(s, unwrap(x.type).name)) : [];
|
|
28622
|
+
const find = (re) => {
|
|
28623
|
+
const own = scalars.find((x) => re.test(x.name));
|
|
28624
|
+
if (own)
|
|
28625
|
+
return [own.name];
|
|
28626
|
+
const inInfo = infoScalars.find((x) => re.test(x.name));
|
|
28627
|
+
return inInfo && info ? [info.name, inInfo.name] : undefined;
|
|
28628
|
+
};
|
|
28629
|
+
const hasNext = find(HAS_NEXT);
|
|
28630
|
+
const next = find(NEXT);
|
|
28631
|
+
const total = find(TOTAL);
|
|
28632
|
+
const looksLikePage = itemsField && (ITEMS.includes(itemsField.name) || hasNext || next || total || info);
|
|
28633
|
+
if (!itemsField || !looksLikePage)
|
|
28634
|
+
return { kind: "object", item: u.name };
|
|
28635
|
+
let item = unwrap(itemsField.type).name;
|
|
28636
|
+
let node;
|
|
28637
|
+
if (itemsField.name === "edges") {
|
|
28638
|
+
const n = fieldOf2(s, item, "node");
|
|
28639
|
+
if (n && isComposite(s, unwrap(n.type).name)) {
|
|
28640
|
+
node = "node";
|
|
28641
|
+
item = unwrap(n.type).name;
|
|
28642
|
+
}
|
|
28643
|
+
}
|
|
28644
|
+
return {
|
|
28645
|
+
kind: "page",
|
|
28646
|
+
item,
|
|
28647
|
+
items: itemsField.name,
|
|
28648
|
+
...node ? { node } : {},
|
|
28649
|
+
...hasNext ? { hasNext } : {},
|
|
28650
|
+
...next ? { next } : {},
|
|
28651
|
+
...total ? { total } : {}
|
|
28652
|
+
};
|
|
28653
|
+
};
|
|
28654
|
+
var wrapShape = (shape, inner) => {
|
|
28655
|
+
if (shape.kind !== "page")
|
|
28656
|
+
return inner;
|
|
28657
|
+
const paths = [shape.hasNext, shape.next, shape.total].filter((p) => !!p);
|
|
28658
|
+
const own = paths.filter((p) => p.length === 1).map((p) => p[0]);
|
|
28659
|
+
const info = paths.filter((p) => p.length === 2);
|
|
28660
|
+
const infoName = info[0]?.[0];
|
|
28661
|
+
const infoSel = infoName ? ` ${infoName} { ${[...new Set(info.map((p) => p[1]))].join(" ")} }` : "";
|
|
28662
|
+
const items = shape.node ? `${shape.items} { ${shape.node} ${inner} }` : `${shape.items} ${inner}`;
|
|
28663
|
+
return `{ ${items}${own.length ? ` ${[...new Set(own)].join(" ")}` : ""}${infoSel} }`;
|
|
28664
|
+
};
|
|
28665
|
+
var at = (v, path) => {
|
|
28666
|
+
if (!path)
|
|
28667
|
+
return;
|
|
28668
|
+
let cur = v;
|
|
28669
|
+
for (const k of path)
|
|
28670
|
+
cur = cur && typeof cur === "object" ? cur[k] : undefined;
|
|
28671
|
+
return cur;
|
|
28672
|
+
};
|
|
28673
|
+
var readShape = (shape, v) => {
|
|
28674
|
+
const objs = (xs) => Array.isArray(xs) ? xs.filter((x) => !!x && typeof x === "object") : [];
|
|
28675
|
+
if (v === null || v === undefined)
|
|
28676
|
+
return { items: [], info: {} };
|
|
28677
|
+
if (shape.kind === "object")
|
|
28678
|
+
return { items: objs([v]), info: {} };
|
|
28679
|
+
if (shape.kind === "list")
|
|
28680
|
+
return { items: objs(v), info: {} };
|
|
28681
|
+
const raw = objs(at(v, [shape.items]));
|
|
28682
|
+
const items = shape.node ? objs(raw.map((e) => e[shape.node])) : raw;
|
|
28683
|
+
const hasNext = at(v, shape.hasNext);
|
|
28684
|
+
const next = at(v, shape.next);
|
|
28685
|
+
const total = at(v, shape.total);
|
|
28686
|
+
return {
|
|
28687
|
+
items,
|
|
28688
|
+
info: {
|
|
28689
|
+
...typeof hasNext === "boolean" ? { hasNext } : {},
|
|
28690
|
+
...typeof next === "string" && next ? { next } : {},
|
|
28691
|
+
...typeof next === "number" ? { next: String(next) } : {},
|
|
28692
|
+
...typeof total === "number" ? { total } : {}
|
|
28693
|
+
}
|
|
28694
|
+
};
|
|
28695
|
+
};
|
|
28696
|
+
var IDENTITY = ["id", "uid", "slug", "key", "name"];
|
|
28697
|
+
var buildSelection = (s, type, depth, exclude = new Set, above = []) => {
|
|
28698
|
+
const t = s.types.get(type);
|
|
28699
|
+
if (!t?.fields)
|
|
28700
|
+
return null;
|
|
28701
|
+
const parts = [];
|
|
28702
|
+
for (const f of t.fields) {
|
|
28703
|
+
if (f.name.startsWith("__") || exclude.has(`${type}.${f.name}`))
|
|
28704
|
+
continue;
|
|
28705
|
+
if (f.args.some(isRequired))
|
|
28706
|
+
continue;
|
|
28707
|
+
const u = unwrap(f.type);
|
|
28708
|
+
if (isLeaf(s, u.name)) {
|
|
28709
|
+
parts.push(f.name);
|
|
28710
|
+
continue;
|
|
28711
|
+
}
|
|
28712
|
+
if (!isComposite(s, u.name))
|
|
28713
|
+
continue;
|
|
28714
|
+
const shape = shapeOf(s, f);
|
|
28715
|
+
if (pageable(planArgs(s, f.args)) || shape?.kind === "page")
|
|
28716
|
+
continue;
|
|
28717
|
+
const sub = depth > 0 && ![...above, type].includes(u.name) ? buildSelection(s, u.name, depth - 1, exclude, [...above, type]) : identitySelection(s, u.name, exclude);
|
|
28718
|
+
if (sub)
|
|
28719
|
+
parts.push(`${f.name} ${sub}`);
|
|
28720
|
+
}
|
|
28721
|
+
return parts.length ? `{ ${parts.join(" ")} }` : null;
|
|
28722
|
+
};
|
|
28723
|
+
var identitySelection = (s, type, exclude = new Set) => {
|
|
28724
|
+
const fields = (s.types.get(type)?.fields ?? []).filter((f) => IDENTITY.includes(f.name) && !exclude.has(`${type}.${f.name}`) && !f.args.some(isRequired) && isLeaf(s, unwrap(f.type).name));
|
|
28725
|
+
return fields.length ? `{ ${fields.map((f) => f.name).join(" ")} }` : null;
|
|
28726
|
+
};
|
|
28727
|
+
var typeFieldAt = (s, path) => {
|
|
28728
|
+
let type = s.query;
|
|
28729
|
+
let last;
|
|
28730
|
+
for (const seg of path) {
|
|
28731
|
+
if (typeof seg === "number")
|
|
28732
|
+
continue;
|
|
28733
|
+
const f = fieldOf2(s, type, seg);
|
|
28734
|
+
if (!f)
|
|
28735
|
+
return;
|
|
28736
|
+
last = `${type}.${seg}`;
|
|
28737
|
+
type = unwrap(f.type).name;
|
|
28738
|
+
}
|
|
28739
|
+
return last;
|
|
28740
|
+
};
|
|
28741
|
+
var findSection = (s, patterns, maxSteps = 4) => {
|
|
28742
|
+
for (const re of patterns) {
|
|
28743
|
+
let frontier = [{ type: s.query, steps: [] }];
|
|
28744
|
+
for (let d = 0;d < maxSteps && frontier.length; d++) {
|
|
28745
|
+
const next = [];
|
|
28746
|
+
for (const { type, steps } of frontier) {
|
|
28747
|
+
for (const f of s.types.get(type)?.fields ?? []) {
|
|
28748
|
+
const shape = shapeOf(s, f);
|
|
28749
|
+
if (!shape)
|
|
28750
|
+
continue;
|
|
28751
|
+
const args = planArgs(s, f.args);
|
|
28752
|
+
if (args.missing.length)
|
|
28753
|
+
continue;
|
|
28754
|
+
if (d === 0 && !args.org.length)
|
|
28755
|
+
continue;
|
|
28756
|
+
const step = { field: f, parent: type, args, shape };
|
|
28757
|
+
if (re.test(f.name) && shape.kind !== "object")
|
|
28758
|
+
return [...steps, step];
|
|
28759
|
+
if (shape.kind === "page" || pageable(args) || args.start || args.end)
|
|
28760
|
+
continue;
|
|
28761
|
+
if (steps.some((x) => x.shape.item === shape.item) || shape.item === s.query)
|
|
28762
|
+
continue;
|
|
28763
|
+
next.push({ type: shape.item, steps: [...steps, step] });
|
|
28764
|
+
}
|
|
28765
|
+
}
|
|
28766
|
+
frontier = next;
|
|
28767
|
+
}
|
|
28768
|
+
}
|
|
28769
|
+
return;
|
|
28770
|
+
};
|
|
28771
|
+
var structural = (steps) => {
|
|
28772
|
+
const out = new Set;
|
|
28773
|
+
for (const st of steps) {
|
|
28774
|
+
out.add(`${st.parent}.${st.field.name}`);
|
|
28775
|
+
if (st.shape.kind === "page") {
|
|
28776
|
+
const pageType = unwrap(st.field.type).name;
|
|
28777
|
+
out.add(`${pageType}.${st.shape.items}`);
|
|
28778
|
+
}
|
|
28779
|
+
}
|
|
28780
|
+
return out;
|
|
28781
|
+
};
|
|
28782
|
+
|
|
28783
|
+
// src/sleuth/export.ts
|
|
28784
|
+
var SECTIONS = [
|
|
28785
|
+
{ name: "deploys", patterns: [/^orgChanges$/, /^changes$/, /^deploys$/], byMonth: true },
|
|
28786
|
+
{ name: "projects", patterns: [/^projects$/] },
|
|
28787
|
+
{ name: "deployments", patterns: [/^deployments$/, /^codeDeployments$/] },
|
|
28788
|
+
{ name: "environments", patterns: [/^environments$/] },
|
|
28789
|
+
{ name: "teams", patterns: [/^teams$/] },
|
|
28790
|
+
{ name: "labels", patterns: [/^labels$/] },
|
|
28791
|
+
{ name: "impactSources", patterns: [/^impactSources$/] },
|
|
28792
|
+
{ name: "incidents", patterns: [/^incidents$/, /^orgIncidents$/, /incidents$/i], byMonth: true },
|
|
28793
|
+
{
|
|
28794
|
+
name: "flagEvents",
|
|
28795
|
+
patterns: [
|
|
28796
|
+
/^(flagEvents|featureFlagEvents|flagChanges|featureFlagChanges)$/,
|
|
28797
|
+
/flag\w*(event|change)s$/i
|
|
28798
|
+
],
|
|
28799
|
+
byMonth: true
|
|
28800
|
+
}
|
|
28801
|
+
];
|
|
28802
|
+
var DAY = 86400000;
|
|
28803
|
+
var MIN_PAGE = 10;
|
|
28804
|
+
var MAX_PAGES = 5000;
|
|
28805
|
+
var MONTH_BUDGET = 8;
|
|
28806
|
+
var MAX_FAILED_MONTHS = 3;
|
|
28807
|
+
var NESTED_SIZE = 500;
|
|
28808
|
+
var MAX_WARNINGS = 200;
|
|
28809
|
+
var DATE_KEYS = [
|
|
28810
|
+
"date",
|
|
28811
|
+
"deployedAt",
|
|
28812
|
+
"deployDate",
|
|
28813
|
+
"createdAt",
|
|
28814
|
+
"startedAt",
|
|
28815
|
+
"occurredAt",
|
|
28816
|
+
"timestamp",
|
|
28817
|
+
"time",
|
|
28818
|
+
"created"
|
|
28819
|
+
];
|
|
28820
|
+
var MONTH = /^(\d{4})-(0[1-9]|1[0-2])$/;
|
|
28821
|
+
var monthsBetween = (since, until) => {
|
|
28822
|
+
const a = MONTH.exec(since);
|
|
28823
|
+
const b = MONTH.exec(until);
|
|
28824
|
+
if (!a || !b)
|
|
28825
|
+
return [];
|
|
28826
|
+
const out = [];
|
|
28827
|
+
let y = Number(a[1]);
|
|
28828
|
+
let m = Number(a[2]);
|
|
28829
|
+
const endKey = Number(b[1]) * 12 + Number(b[2]);
|
|
28830
|
+
while (y * 12 + m <= endKey) {
|
|
28831
|
+
out.push(`${y}-${String(m).padStart(2, "0")}`);
|
|
28832
|
+
m++;
|
|
28833
|
+
if (m > 12) {
|
|
28834
|
+
m = 1;
|
|
28835
|
+
y++;
|
|
28836
|
+
}
|
|
28837
|
+
}
|
|
28838
|
+
return out;
|
|
28839
|
+
};
|
|
28840
|
+
var monthWindow = (month) => {
|
|
28841
|
+
const [y, m] = month.split("-").map(Number);
|
|
28842
|
+
return { start: new Date(Date.UTC(y, m - 1, 1)), end: new Date(Date.UTC(y, m, 1)) };
|
|
28843
|
+
};
|
|
28844
|
+
var monthOf = (d) => d.toISOString().slice(0, 7);
|
|
28845
|
+
var keyOf2 = (item) => {
|
|
28846
|
+
for (const k of ["id", "uid"]) {
|
|
28847
|
+
const v = item[k];
|
|
28848
|
+
if (typeof v === "string" || typeof v === "number")
|
|
28849
|
+
return `${k}:${v}`;
|
|
28850
|
+
}
|
|
28851
|
+
if (typeof item.slug === "string")
|
|
28852
|
+
return `slug:${JSON.stringify(item._parents ?? {})}:${item.slug}`;
|
|
28853
|
+
return `json:${JSON.stringify(item)}`;
|
|
28854
|
+
};
|
|
28855
|
+
var itemMonth = (item) => {
|
|
28856
|
+
for (const k of DATE_KEYS) {
|
|
28857
|
+
const v = item[k];
|
|
28858
|
+
if (typeof v !== "string" && typeof v !== "number")
|
|
28859
|
+
continue;
|
|
28860
|
+
const d = new Date(v);
|
|
28861
|
+
if (!Number.isNaN(d.getTime()))
|
|
28862
|
+
return monthOf(d);
|
|
28863
|
+
}
|
|
28864
|
+
return;
|
|
28865
|
+
};
|
|
28866
|
+
var IDENTITY2 = ["id", "uid", "slug", "key", "name"];
|
|
28867
|
+
var identity = (item) => Object.fromEntries(IDENTITY2.filter((k) => item[k] !== undefined).map((k) => [k, item[k]]));
|
|
28868
|
+
var writeAtomic = (path, body) => {
|
|
28869
|
+
mkdirSync28(dirname25(path), { recursive: true });
|
|
28870
|
+
const tmp = `${path}.partial`;
|
|
28871
|
+
writeFileSync28(tmp, body);
|
|
28872
|
+
renameSync13(tmp, path);
|
|
28873
|
+
};
|
|
28874
|
+
var jsonl = (items) => items.length ? `${items.map((i) => JSON.stringify(i)).join(`
|
|
28875
|
+
`)}
|
|
28876
|
+
` : "";
|
|
28877
|
+
var readJsonl = (path) => readFileSync32(path, "utf8").split(`
|
|
28878
|
+
`).filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
28879
|
+
var CANNOT_QUERY = /Cannot query field "(\w+)" on type "(\w+)"/;
|
|
28880
|
+
var exportSleuth = async (o) => {
|
|
28881
|
+
const log = o.log ?? (() => {});
|
|
28882
|
+
const now = (o.now ?? (() => new Date))();
|
|
28883
|
+
const currentMonth = monthOf(now);
|
|
28884
|
+
const exclude = new Set;
|
|
28885
|
+
const warnings = new Set;
|
|
28886
|
+
const warn = (line) => {
|
|
28887
|
+
if (warnings.size < MAX_WARNINGS)
|
|
28888
|
+
warnings.add(line);
|
|
28889
|
+
};
|
|
28890
|
+
const manifest = {
|
|
28891
|
+
v: 1,
|
|
28892
|
+
tool: "sleds sleuth export",
|
|
28893
|
+
...o.cliVersion ? { cliVersion: o.cliVersion } : {},
|
|
28894
|
+
org: o.org,
|
|
28895
|
+
endpoint: o.endpoint,
|
|
28896
|
+
startedAt: now.toISOString(),
|
|
28897
|
+
since: o.since,
|
|
28898
|
+
until: o.until,
|
|
28899
|
+
depth: o.depth,
|
|
28900
|
+
pageSize: o.pageSize,
|
|
28901
|
+
schema: { file: "schema.json", types: 0 },
|
|
28902
|
+
sections: {},
|
|
28903
|
+
pruned: [],
|
|
28904
|
+
warnings: [],
|
|
28905
|
+
mismatches: [],
|
|
28906
|
+
requests: 0,
|
|
28907
|
+
complete: false
|
|
28908
|
+
};
|
|
28909
|
+
const save = () => {
|
|
28910
|
+
manifest.pruned = [...exclude].sort();
|
|
28911
|
+
manifest.warnings = [...warnings];
|
|
28912
|
+
manifest.requests = o.client.calls();
|
|
28913
|
+
writeAtomic(join34(o.out, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
28914
|
+
`);
|
|
28915
|
+
};
|
|
28916
|
+
mkdirSync28(o.out, { recursive: true });
|
|
28917
|
+
log("reading Sleuth's schema");
|
|
28918
|
+
const intro = await o.client.request(INTROSPECTION_QUERY);
|
|
28919
|
+
if (!intro.data?.__schema)
|
|
28920
|
+
throw new SleuthError(`Sleuth did not answer the schema query${intro.errors[0] ? ` — ${intro.errors[0].message}` : ""}`, false);
|
|
28921
|
+
writeAtomic(join34(o.out, "schema.json"), `${JSON.stringify(intro.data, null, 2)}
|
|
28922
|
+
`);
|
|
28923
|
+
const schema = indexSchema(intro.data);
|
|
28924
|
+
manifest.schema.types = schema.types.size;
|
|
28925
|
+
const ask = async (build, steps) => {
|
|
28926
|
+
const keep = structural(steps);
|
|
28927
|
+
const root = steps[0]?.field.name ?? "";
|
|
28928
|
+
for (let i = 0;i < 25; i++) {
|
|
28929
|
+
const r = await o.client.request(build());
|
|
28930
|
+
if (r.data && !r.errors.length)
|
|
28931
|
+
return r.data;
|
|
28932
|
+
const prune = new Set;
|
|
28933
|
+
for (const e of r.errors) {
|
|
28934
|
+
const m = CANNOT_QUERY.exec(e.message);
|
|
28935
|
+
const tf = e.path && typeFieldAt(schema, e.path) || (m ? `${m[2]}.${m[1]}` : undefined);
|
|
28936
|
+
if (tf && !keep.has(tf) && !exclude.has(tf))
|
|
28937
|
+
prune.add(tf);
|
|
28938
|
+
}
|
|
28939
|
+
const empty = !r.data || r.data[root] === null || r.data[root] === undefined;
|
|
28940
|
+
if (empty && prune.size) {
|
|
28941
|
+
for (const tf of prune) {
|
|
28942
|
+
exclude.add(tf);
|
|
28943
|
+
log(`sleuth refused ${tf} — leaving it out`);
|
|
28944
|
+
}
|
|
28945
|
+
continue;
|
|
28946
|
+
}
|
|
28947
|
+
const why = r.errors.slice(0, 3).map((e) => e.message).join("; ");
|
|
28948
|
+
if (empty)
|
|
28949
|
+
throw new SleuthError(why || "Sleuth answered with no data", true);
|
|
28950
|
+
for (const e of r.errors) {
|
|
28951
|
+
const tf = e.path ? typeFieldAt(schema, e.path) : undefined;
|
|
28952
|
+
warn(`${tf ? `${tf}: ` : ""}${e.message}`);
|
|
28953
|
+
}
|
|
28954
|
+
return r.data;
|
|
28955
|
+
}
|
|
28956
|
+
throw new SleuthError("Sleuth kept refusing fields after 25 tries", false);
|
|
28957
|
+
};
|
|
28958
|
+
const buildQuery = (steps, leafValues) => {
|
|
28959
|
+
const last = steps.length - 1;
|
|
28960
|
+
const argsAt = (i) => {
|
|
28961
|
+
const st = steps[i];
|
|
28962
|
+
if (i === last)
|
|
28963
|
+
return renderArgs(st.args, leafValues);
|
|
28964
|
+
return renderArgs(st.args, { org: o.org, ...st.args.size ? { size: NESTED_SIZE } : {} });
|
|
28965
|
+
};
|
|
28966
|
+
const leaf = steps[last];
|
|
28967
|
+
const sel = buildSelection(schema, leaf.shape.item, o.depth, exclude) ?? "{ __typename }";
|
|
28968
|
+
let inner = `${leaf.field.name}${argsAt(last)} ${wrapShape(leaf.shape, sel)}`;
|
|
28969
|
+
for (let i = last - 1;i >= 0; i--) {
|
|
28970
|
+
const st = steps[i];
|
|
28971
|
+
const ident = identitySelection(schema, st.shape.item, exclude);
|
|
28972
|
+
const body = `{ ${ident ? `${ident.slice(2, -2)} ` : ""}${inner} }`;
|
|
28973
|
+
inner = `${st.field.name}${argsAt(i)} ${wrapShape(st.shape, body)}`;
|
|
28974
|
+
}
|
|
28975
|
+
return `query { ${inner} }`;
|
|
28976
|
+
};
|
|
28977
|
+
const extract = (steps, data) => {
|
|
28978
|
+
let level = [
|
|
28979
|
+
{ value: data[steps[0]?.field.name ?? ""], parents: {} }
|
|
28980
|
+
];
|
|
28981
|
+
const items = [];
|
|
28982
|
+
const infos = [];
|
|
28983
|
+
steps.forEach((st, i) => {
|
|
28984
|
+
const next = [];
|
|
28985
|
+
for (const { value, parents } of level) {
|
|
28986
|
+
const r = readShape(st.shape, value);
|
|
28987
|
+
if (i === steps.length - 1) {
|
|
28988
|
+
infos.push(r.info);
|
|
28989
|
+
for (const it of r.items)
|
|
28990
|
+
items.push(Object.keys(parents).length ? { ...it, _parents: parents } : it);
|
|
28991
|
+
continue;
|
|
28992
|
+
}
|
|
28993
|
+
const child = steps[i + 1]?.field.name ?? "";
|
|
28994
|
+
for (const it of r.items)
|
|
28995
|
+
next.push({ value: it[child], parents: { ...parents, [st.shape.item]: identity(it) } });
|
|
28996
|
+
}
|
|
28997
|
+
level = next;
|
|
28998
|
+
});
|
|
28999
|
+
return { items, infos };
|
|
29000
|
+
};
|
|
29001
|
+
const fetchItems = async (steps, win, size) => {
|
|
29002
|
+
const leaf = steps[steps.length - 1];
|
|
29003
|
+
const a = leaf.args;
|
|
29004
|
+
const oneParent = steps.slice(0, -1).every((s) => s.shape.kind === "object");
|
|
29005
|
+
const canPage = oneParent && pageable(a);
|
|
29006
|
+
const out = [];
|
|
29007
|
+
let cursor;
|
|
29008
|
+
let page = a.page?.first ?? 1;
|
|
29009
|
+
let offset = 0;
|
|
29010
|
+
let prevFirst;
|
|
29011
|
+
let total;
|
|
29012
|
+
for (let n = 0;n < MAX_PAGES; n++) {
|
|
29013
|
+
const values = {
|
|
29014
|
+
org: o.org,
|
|
29015
|
+
...win ? { start: win.start, end: win.end } : {},
|
|
29016
|
+
...a.size ? { size: canPage ? size : Math.max(size, NESTED_SIZE) } : {},
|
|
29017
|
+
...canPage && a.cursor && cursor ? { cursor } : {},
|
|
29018
|
+
...canPage && a.page ? { page } : {},
|
|
29019
|
+
...canPage && !a.cursor && !a.page && a.offset ? { offset } : {}
|
|
29020
|
+
};
|
|
29021
|
+
const data = await ask(() => buildQuery(steps, values), steps);
|
|
29022
|
+
const { items, infos } = extract(steps, data);
|
|
29023
|
+
if (!canPage) {
|
|
29024
|
+
const full = oneParent && a.size !== undefined && items.length >= (values.size ?? 0);
|
|
29025
|
+
const truncated = infos.filter((i) => i.hasNext === true).length || (full ? 1 : 0);
|
|
29026
|
+
return {
|
|
29027
|
+
items,
|
|
29028
|
+
truncated,
|
|
29029
|
+
...oneParent && infos[0]?.total !== undefined ? { total: infos[0].total } : {}
|
|
29030
|
+
};
|
|
29031
|
+
}
|
|
29032
|
+
const info = infos[0] ?? {};
|
|
29033
|
+
if (n === 0 && info.total !== undefined)
|
|
29034
|
+
total = info.total;
|
|
29035
|
+
if (!items.length)
|
|
29036
|
+
break;
|
|
29037
|
+
const first = keyOf2(items[0]);
|
|
29038
|
+
if (first === prevFirst) {
|
|
29039
|
+
warn(`${leaf.field.name}: the next page repeated the last one — paging stopped there`);
|
|
29040
|
+
break;
|
|
29041
|
+
}
|
|
29042
|
+
prevFirst = first;
|
|
29043
|
+
out.push(...items);
|
|
29044
|
+
if (info.hasNext === false)
|
|
29045
|
+
break;
|
|
29046
|
+
if (a.cursor) {
|
|
29047
|
+
if (!info.next || info.next === cursor)
|
|
29048
|
+
break;
|
|
29049
|
+
cursor = info.next;
|
|
29050
|
+
continue;
|
|
29051
|
+
}
|
|
29052
|
+
if (info.hasNext === undefined && a.size && items.length < size)
|
|
29053
|
+
break;
|
|
29054
|
+
if (a.page)
|
|
29055
|
+
page = info.next && /^\d+$/.test(info.next) ? Number(info.next) : page + 1;
|
|
29056
|
+
else
|
|
29057
|
+
offset += items.length;
|
|
29058
|
+
}
|
|
29059
|
+
return { items: out, truncated: 0, ...total !== undefined ? { total } : {} };
|
|
29060
|
+
};
|
|
29061
|
+
const adaptive = async (steps, win, size, budget) => {
|
|
29062
|
+
try {
|
|
29063
|
+
return await fetchItems(steps, win, size);
|
|
29064
|
+
} catch (e) {
|
|
29065
|
+
if (!(e instanceof SleuthError) || !e.adaptable || budget.left <= 0)
|
|
29066
|
+
throw e;
|
|
29067
|
+
budget.left--;
|
|
29068
|
+
const leaf = steps[steps.length - 1];
|
|
29069
|
+
if (leaf.args.size && size > MIN_PAGE) {
|
|
29070
|
+
const half = Math.max(MIN_PAGE, Math.floor(size / 2));
|
|
29071
|
+
log(` ${e.message} — trying pages of ${half}`);
|
|
29072
|
+
return adaptive(steps, win, half, budget);
|
|
29073
|
+
}
|
|
29074
|
+
const days = win ? Math.floor((win.end.getTime() - win.start.getTime()) / DAY) : 0;
|
|
29075
|
+
if (!win || days < 2)
|
|
29076
|
+
throw e;
|
|
29077
|
+
const mid = new Date(win.start.getTime() + Math.floor(days / 2) * DAY);
|
|
29078
|
+
log(` ${e.message} — splitting ${win.start.toISOString().slice(0, 10)}…${win.end.toISOString().slice(0, 10)} in two`);
|
|
29079
|
+
const x = await adaptive(steps, { start: win.start, end: mid }, size, budget);
|
|
29080
|
+
const y = await adaptive(steps, { start: mid, end: win.end }, size, budget);
|
|
29081
|
+
return {
|
|
29082
|
+
items: [...x.items, ...y.items],
|
|
29083
|
+
truncated: x.truncated + y.truncated,
|
|
29084
|
+
...x.total !== undefined && y.total !== undefined ? { total: x.total + y.total } : {}
|
|
29085
|
+
};
|
|
29086
|
+
}
|
|
29087
|
+
};
|
|
29088
|
+
const months = monthsBetween(o.since, o.until);
|
|
29089
|
+
const runSection = async (sec) => {
|
|
29090
|
+
const steps = findSection(schema, sec.patterns);
|
|
29091
|
+
if (!steps)
|
|
29092
|
+
return { found: false, why: "Sleuth's schema has no field by that name" };
|
|
29093
|
+
const leaf = steps[steps.length - 1];
|
|
29094
|
+
const res = {
|
|
29095
|
+
found: true,
|
|
29096
|
+
path: steps.map((s) => s.field.name),
|
|
29097
|
+
windowed: windowed(leaf.args),
|
|
29098
|
+
paged: pageable(leaf.args),
|
|
29099
|
+
count: 0,
|
|
29100
|
+
duplicates: 0
|
|
29101
|
+
};
|
|
29102
|
+
const seen = new Set;
|
|
29103
|
+
const fresh = (items) => {
|
|
29104
|
+
const kept = [];
|
|
29105
|
+
for (const it of items) {
|
|
29106
|
+
const k = keyOf2(it);
|
|
29107
|
+
if (seen.has(k))
|
|
29108
|
+
res.duplicates++;
|
|
29109
|
+
else {
|
|
29110
|
+
seen.add(k);
|
|
29111
|
+
kept.push(it);
|
|
29112
|
+
}
|
|
29113
|
+
}
|
|
29114
|
+
return kept;
|
|
29115
|
+
};
|
|
29116
|
+
if (sec.byMonth && res.windowed) {
|
|
29117
|
+
res.months = [];
|
|
29118
|
+
let failedInARow = 0;
|
|
29119
|
+
for (const month of months) {
|
|
29120
|
+
const file = `${sec.name}/${month}.jsonl`;
|
|
29121
|
+
const path = join34(o.out, file);
|
|
29122
|
+
if (!o.fresh && month < currentMonth && existsSync31(path)) {
|
|
29123
|
+
const kept = fresh(readJsonl(path));
|
|
29124
|
+
res.months.push({ month, file, count: kept.length, resumed: true });
|
|
29125
|
+
res.count += kept.length;
|
|
29126
|
+
continue;
|
|
29127
|
+
}
|
|
29128
|
+
try {
|
|
29129
|
+
const got = await adaptive(steps, monthWindow(month), o.pageSize, { left: MONTH_BUDGET });
|
|
29130
|
+
const kept = fresh(got.items);
|
|
29131
|
+
writeAtomic(path, jsonl(kept));
|
|
29132
|
+
res.months.push({
|
|
29133
|
+
month,
|
|
29134
|
+
file,
|
|
29135
|
+
count: kept.length,
|
|
29136
|
+
...got.items.length !== kept.length ? { fetched: got.items.length } : {},
|
|
29137
|
+
...got.total !== undefined ? { total: got.total } : {}
|
|
29138
|
+
});
|
|
29139
|
+
res.count += kept.length;
|
|
29140
|
+
if (got.truncated)
|
|
29141
|
+
res.truncated = (res.truncated ?? 0) + got.truncated;
|
|
29142
|
+
log(`${sec.name} ${month}: ${kept.length}`);
|
|
29143
|
+
failedInARow = 0;
|
|
29144
|
+
} catch (e) {
|
|
29145
|
+
const why = e instanceof Error ? e.message : String(e);
|
|
29146
|
+
res.months.push({ month, file, count: 0, error: why });
|
|
29147
|
+
log(`${sec.name} ${month}: failed — ${why}`);
|
|
29148
|
+
if (++failedInARow >= MAX_FAILED_MONTHS) {
|
|
29149
|
+
res.error = `${MAX_FAILED_MONTHS} months in a row failed; stopped at ${month} — ${why}`;
|
|
29150
|
+
break;
|
|
29151
|
+
}
|
|
29152
|
+
}
|
|
29153
|
+
}
|
|
29154
|
+
return res;
|
|
29155
|
+
}
|
|
29156
|
+
const got = await adaptive(steps, undefined, o.pageSize, { left: MONTH_BUDGET });
|
|
29157
|
+
const kept = fresh(got.items);
|
|
29158
|
+
res.count = kept.length;
|
|
29159
|
+
if (got.truncated)
|
|
29160
|
+
res.truncated = got.truncated;
|
|
29161
|
+
if (!sec.byMonth) {
|
|
29162
|
+
res.file = `${sec.name}.jsonl`;
|
|
29163
|
+
writeAtomic(join34(o.out, res.file), jsonl(kept));
|
|
29164
|
+
log(`${sec.name}: ${kept.length}`);
|
|
29165
|
+
return res;
|
|
29166
|
+
}
|
|
29167
|
+
const byMonth = new Map;
|
|
29168
|
+
for (const it of kept) {
|
|
29169
|
+
const m = itemMonth(it) ?? "undated";
|
|
29170
|
+
byMonth.set(m, [...byMonth.get(m) ?? [], it]);
|
|
29171
|
+
}
|
|
29172
|
+
res.months = [...byMonth.keys()].sort().map((month) => {
|
|
29173
|
+
const file = `${sec.name}/${month}.jsonl`;
|
|
29174
|
+
const list = byMonth.get(month) ?? [];
|
|
29175
|
+
writeAtomic(join34(o.out, file), jsonl(list));
|
|
29176
|
+
return { month, file, count: list.length };
|
|
29177
|
+
});
|
|
29178
|
+
log(`${sec.name}: ${kept.length} (not windowed by Sleuth; filed by each item's date)`);
|
|
29179
|
+
return res;
|
|
29180
|
+
};
|
|
29181
|
+
const orgRoot = (schema.types.get(schema.query)?.fields ?? []).find((f) => {
|
|
29182
|
+
const sh = shapeOf(schema, f);
|
|
29183
|
+
const args = planArgs(schema, f.args);
|
|
29184
|
+
return /^(organization|org)$/i.test(f.name) && sh?.kind === "object" && args.org.length && !args.missing.length;
|
|
29185
|
+
});
|
|
29186
|
+
if (orgRoot) {
|
|
29187
|
+
const steps = [
|
|
29188
|
+
{
|
|
29189
|
+
field: orgRoot,
|
|
29190
|
+
parent: schema.query,
|
|
29191
|
+
args: planArgs(schema, orgRoot.args),
|
|
29192
|
+
shape: { kind: "object", item: unwrap(orgRoot.type).name }
|
|
29193
|
+
}
|
|
29194
|
+
];
|
|
29195
|
+
try {
|
|
29196
|
+
const sel = buildSelection(schema, unwrap(orgRoot.type).name, 0, exclude) ?? "{ __typename }";
|
|
29197
|
+
const data = await ask(() => `query { ${orgRoot.name}${renderArgs(steps[0]?.args ?? { org: [], missing: [] }, { org: o.org })} ${sel} }`, steps);
|
|
29198
|
+
writeAtomic(join34(o.out, "organization.json"), `${JSON.stringify(data[orgRoot.name], null, 2)}
|
|
29199
|
+
`);
|
|
29200
|
+
manifest.organization = { file: "organization.json" };
|
|
29201
|
+
} catch (e) {
|
|
29202
|
+
manifest.organization = { error: e instanceof Error ? e.message : String(e) };
|
|
29203
|
+
}
|
|
29204
|
+
} else
|
|
29205
|
+
manifest.organization = { error: "Sleuth's schema has no organization(orgSlug) field" };
|
|
29206
|
+
save();
|
|
29207
|
+
for (const sec of SECTIONS) {
|
|
29208
|
+
if (o.only?.length && !o.only.includes(sec.name))
|
|
29209
|
+
continue;
|
|
29210
|
+
log(`— ${sec.name}`);
|
|
29211
|
+
try {
|
|
29212
|
+
manifest.sections[sec.name] = await runSection(sec);
|
|
29213
|
+
} catch (e) {
|
|
29214
|
+
manifest.sections[sec.name] = {
|
|
29215
|
+
found: true,
|
|
29216
|
+
path: [],
|
|
29217
|
+
windowed: false,
|
|
29218
|
+
paged: false,
|
|
29219
|
+
count: 0,
|
|
29220
|
+
duplicates: 0,
|
|
29221
|
+
error: e instanceof Error ? e.message : String(e)
|
|
29222
|
+
};
|
|
29223
|
+
log(`${sec.name}: failed — ${e instanceof Error ? e.message : String(e)}`);
|
|
29224
|
+
}
|
|
29225
|
+
save();
|
|
29226
|
+
}
|
|
29227
|
+
const deploys = manifest.sections.deploys;
|
|
29228
|
+
const recap = orgRoot ? fieldOf2(schema, unwrap(orgRoot.type).name, "metricsRecap") : undefined;
|
|
29229
|
+
const recapArgs = recap ? planArgs(schema, recap.args) : undefined;
|
|
29230
|
+
if (orgRoot && recap && recapArgs && windowed(recapArgs) && !recapArgs.missing.length && fieldOf2(schema, unwrap(recap.type).name, "numOfDeploys") && deploys?.found && deploys.months) {
|
|
29231
|
+
const orgArgs = renderArgs(planArgs(schema, orgRoot.args), { org: o.org });
|
|
29232
|
+
for (const row of deploys.months) {
|
|
29233
|
+
if (row.error || row.month === "undated")
|
|
29234
|
+
continue;
|
|
29235
|
+
const w = monthWindow(row.month);
|
|
29236
|
+
try {
|
|
29237
|
+
const r = await o.client.request(`query { ${orgRoot.name}${orgArgs} { ${recap.name}${renderArgs(recapArgs, { start: w.start, end: w.end })} { numOfDeploys } } }`);
|
|
29238
|
+
const orgData = r.data?.[orgRoot.name];
|
|
29239
|
+
const n = orgData?.[recap.name]?.numOfDeploys;
|
|
29240
|
+
if (typeof n === "number")
|
|
29241
|
+
row.sleuthReports = n;
|
|
29242
|
+
} catch (e) {
|
|
29243
|
+
warn(`metricsRecap ${row.month}: ${e instanceof Error ? e.message : String(e)}`);
|
|
29244
|
+
}
|
|
29245
|
+
}
|
|
29246
|
+
}
|
|
29247
|
+
for (const [name, sec] of Object.entries(manifest.sections)) {
|
|
29248
|
+
if (!sec.found || !sec.months)
|
|
29249
|
+
continue;
|
|
29250
|
+
for (const row of sec.months) {
|
|
29251
|
+
const fetched = row.fetched ?? row.count;
|
|
29252
|
+
if (row.total !== undefined && row.total !== fetched)
|
|
29253
|
+
manifest.mismatches.push({
|
|
29254
|
+
section: name,
|
|
29255
|
+
month: row.month,
|
|
29256
|
+
exported: fetched,
|
|
29257
|
+
reported: row.total,
|
|
29258
|
+
source: "totalCount"
|
|
29259
|
+
});
|
|
29260
|
+
if (row.sleuthReports !== undefined && row.sleuthReports !== row.count)
|
|
29261
|
+
manifest.mismatches.push({
|
|
29262
|
+
section: name,
|
|
29263
|
+
month: row.month,
|
|
29264
|
+
exported: row.count,
|
|
29265
|
+
reported: row.sleuthReports,
|
|
29266
|
+
source: "metricsRecap"
|
|
29267
|
+
});
|
|
29268
|
+
}
|
|
29269
|
+
}
|
|
29270
|
+
manifest.finishedAt = new Date().toISOString();
|
|
29271
|
+
manifest.complete = !!orgRoot && Object.values(manifest.sections).every((s) => !s.found || !s.error && !(s.months ?? []).some((m) => m.error));
|
|
29272
|
+
save();
|
|
29273
|
+
return manifest;
|
|
29274
|
+
};
|
|
29275
|
+
|
|
29276
|
+
// src/commands/sleuth.ts
|
|
29277
|
+
var SLEUTH_USAGE = `usage: sleds sleuth export --org <slug> --out <dir> [options]
|
|
29278
|
+
|
|
29279
|
+
Archive a Sleuth.io org through its GraphQL API: the schema, the organization, every deploy
|
|
29280
|
+
(orgChanges) as JSONL by month, and its projects, deployments, environments, teams, labels,
|
|
29281
|
+
impact sources, incidents and flag events, with manifest.json counting each beside what
|
|
29282
|
+
Sleuth reports. Re-run to resume: finished past months on disk are not fetched again.
|
|
29283
|
+
|
|
29284
|
+
SLEUTH_API_KEY the org's API key or access token (Sleuth › Settings › API)
|
|
29285
|
+
--org <slug> the Sleuth org slug (required)
|
|
29286
|
+
--out <dir> where to write (required; created if missing)
|
|
29287
|
+
--since <YYYY-MM> first month (default 2022-01)
|
|
29288
|
+
--until <YYYY-MM> last month (default this month)
|
|
29289
|
+
--only <a,b> just these: ${SECTIONS.map((s) => s.name).join(", ")}
|
|
29290
|
+
--page-size <n> items a page (default 100; halved on its own when Sleuth struggles)
|
|
29291
|
+
--depth <n> how deep nested objects are read (default 2)
|
|
29292
|
+
--fresh fetch every month again, even the ones on disk
|
|
29293
|
+
--url <endpoint> the GraphQL endpoint (default ${SLEUTH_URL}; else SLEUTH_API_URL)
|
|
29294
|
+
--json print the manifest instead of the summary`;
|
|
29295
|
+
var parse3 = (argv) => {
|
|
29296
|
+
const words = [];
|
|
29297
|
+
const flags = {};
|
|
29298
|
+
for (let i = 0;i < argv.length; i++) {
|
|
29299
|
+
const a = argv[i];
|
|
29300
|
+
if (!a.startsWith("--")) {
|
|
29301
|
+
words.push(a);
|
|
29302
|
+
continue;
|
|
29303
|
+
}
|
|
29304
|
+
const eq = a.indexOf("=");
|
|
29305
|
+
if (eq !== -1) {
|
|
29306
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
29307
|
+
continue;
|
|
29308
|
+
}
|
|
29309
|
+
const next = argv[i + 1];
|
|
29310
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
29311
|
+
flags[a.slice(2)] = next;
|
|
29312
|
+
i++;
|
|
29313
|
+
} else
|
|
29314
|
+
flags[a.slice(2)] = true;
|
|
29315
|
+
}
|
|
29316
|
+
return { words, flags };
|
|
29317
|
+
};
|
|
29318
|
+
var outDir = (raw, home) => resolve12(raw.startsWith("~/") ? join35(home, raw.slice(2)) : raw);
|
|
29319
|
+
var str15 = (f, k) => typeof f[k] === "string" ? f[k] : undefined;
|
|
29320
|
+
var MONTH2 = /^\d{4}-(0[1-9]|1[0-2])$/;
|
|
29321
|
+
var KNOWN = [
|
|
29322
|
+
"org",
|
|
29323
|
+
"out",
|
|
29324
|
+
"since",
|
|
29325
|
+
"until",
|
|
29326
|
+
"only",
|
|
29327
|
+
"page-size",
|
|
29328
|
+
"depth",
|
|
29329
|
+
"fresh",
|
|
29330
|
+
"url",
|
|
29331
|
+
"json"
|
|
29332
|
+
];
|
|
29333
|
+
var sleuthCmd = async (o) => {
|
|
29334
|
+
const log = o.log ?? console.log;
|
|
29335
|
+
const error = o.error ?? console.error;
|
|
29336
|
+
const env = o.env ?? process.env;
|
|
29337
|
+
const { words, flags } = parse3(o.argv);
|
|
29338
|
+
const usage = (why) => {
|
|
29339
|
+
if (why)
|
|
29340
|
+
error(why);
|
|
29341
|
+
error(SLEUTH_USAGE);
|
|
29342
|
+
return 2;
|
|
29343
|
+
};
|
|
29344
|
+
if (words[0] !== "export" || words.length !== 1)
|
|
29345
|
+
return usage();
|
|
29346
|
+
const unknown = Object.keys(flags).filter((k) => !KNOWN.includes(k));
|
|
29347
|
+
if (unknown.length)
|
|
29348
|
+
return usage(`unknown option: --${unknown[0]}`);
|
|
29349
|
+
const org = str15(flags, "org");
|
|
29350
|
+
const outRaw = str15(flags, "out");
|
|
29351
|
+
if (!org || !outRaw)
|
|
29352
|
+
return usage("--org and --out are required");
|
|
29353
|
+
const key = env.SLEUTH_API_KEY?.trim();
|
|
29354
|
+
if (!key) {
|
|
29355
|
+
error("SLEUTH_API_KEY is not set — the org's key from Sleuth › Settings › API");
|
|
29356
|
+
return 2;
|
|
29357
|
+
}
|
|
29358
|
+
const now = (o.now ?? (() => new Date))();
|
|
29359
|
+
const since = str15(flags, "since") ?? "2022-01";
|
|
29360
|
+
const until = str15(flags, "until") ?? monthOf(now);
|
|
29361
|
+
if (!MONTH2.test(since) || !MONTH2.test(until) || !monthsBetween(since, until).length)
|
|
29362
|
+
return usage("--since and --until are months, YYYY-MM, since before until");
|
|
29363
|
+
const pageSize = Number(str15(flags, "page-size") ?? 100);
|
|
29364
|
+
const depth = Number(str15(flags, "depth") ?? 2);
|
|
29365
|
+
if (!Number.isInteger(pageSize) || pageSize < 1)
|
|
29366
|
+
return usage("--page-size is a whole number");
|
|
29367
|
+
if (!Number.isInteger(depth) || depth < 0 || depth > 5)
|
|
29368
|
+
return usage("--depth is 0 to 5");
|
|
29369
|
+
const only = str15(flags, "only")?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
29370
|
+
const names = SECTIONS.map((s) => s.name);
|
|
29371
|
+
const bad = only?.find((n) => !names.includes(n));
|
|
29372
|
+
if (bad)
|
|
29373
|
+
return usage(`--only: no section "${bad}" (${names.join(", ")})`);
|
|
29374
|
+
const out = outDir(outRaw, o.home ?? homedir8());
|
|
29375
|
+
const endpoint = str15(flags, "url") ?? env.SLEUTH_API_URL ?? SLEUTH_URL;
|
|
29376
|
+
const version = cliVersion(env);
|
|
29377
|
+
const progress = error;
|
|
29378
|
+
const client = sleuthClient({
|
|
29379
|
+
key,
|
|
29380
|
+
url: endpoint,
|
|
29381
|
+
...o.fetch ? { fetch: o.fetch } : {},
|
|
29382
|
+
...o.sleep ? { sleep: o.sleep } : {},
|
|
29383
|
+
log: progress
|
|
29384
|
+
});
|
|
29385
|
+
let manifest;
|
|
29386
|
+
try {
|
|
29387
|
+
manifest = await exportSleuth({
|
|
29388
|
+
org,
|
|
29389
|
+
out,
|
|
29390
|
+
since,
|
|
29391
|
+
until,
|
|
29392
|
+
pageSize,
|
|
29393
|
+
depth,
|
|
29394
|
+
...only?.length ? { only } : {},
|
|
29395
|
+
fresh: flags.fresh === true,
|
|
29396
|
+
endpoint,
|
|
29397
|
+
...version ? { cliVersion: version } : {},
|
|
29398
|
+
client,
|
|
29399
|
+
now: () => now,
|
|
29400
|
+
log: progress
|
|
29401
|
+
});
|
|
29402
|
+
} catch (e) {
|
|
29403
|
+
error(`sleuth export stopped — ${e instanceof Error ? e.message : String(e)}`);
|
|
29404
|
+
return e instanceof SleuthError && !e.adaptable ? 2 : 1;
|
|
29405
|
+
}
|
|
29406
|
+
if (flags.json === true) {
|
|
29407
|
+
log(JSON.stringify(manifest));
|
|
29408
|
+
return manifest.complete ? 0 : 1;
|
|
29409
|
+
}
|
|
29410
|
+
log(`Sleuth org ${org} → ${out}`);
|
|
29411
|
+
for (const [name, s] of Object.entries(manifest.sections)) {
|
|
29412
|
+
if (!s.found)
|
|
29413
|
+
log(` ${name.padEnd(14)} not in Sleuth's schema — skipped`);
|
|
29414
|
+
else {
|
|
29415
|
+
const months = s.months ? ` across ${s.months.length} months` : "";
|
|
29416
|
+
const dupes = s.duplicates ? `, ${s.duplicates} duplicates dropped` : "";
|
|
29417
|
+
const more = s.truncated ? `, ${s.truncated} nested lists cut at one page` : "";
|
|
29418
|
+
log(` ${name.padEnd(14)} ${String(s.count).padStart(6)}${months}${dupes}${more}${s.error ? ` — FAILED: ${s.error}` : ""}`);
|
|
29419
|
+
}
|
|
29420
|
+
}
|
|
29421
|
+
if (manifest.pruned.length)
|
|
29422
|
+
log(` Sleuth refused ${manifest.pruned.length} fields: ${manifest.pruned.join(", ")}`);
|
|
29423
|
+
for (const m of manifest.mismatches)
|
|
29424
|
+
log(` ${m.section} ${m.month}: exported ${m.exported}, Sleuth's ${m.source} says ${m.reported}`);
|
|
29425
|
+
const deploys = manifest.sections.deploys;
|
|
29426
|
+
if (deploys?.found) {
|
|
29427
|
+
const checked = (deploys.months ?? []).filter((r) => r.sleuthReports !== undefined || r.total !== undefined).length;
|
|
29428
|
+
const off = manifest.mismatches.filter((m) => m.section === "deploys").length;
|
|
29429
|
+
if (!checked)
|
|
29430
|
+
log(" Sleuth gave no deploy count to check against");
|
|
29431
|
+
else if (!off)
|
|
29432
|
+
log(` deploy counts match what Sleuth reports in all ${checked} months checked`);
|
|
29433
|
+
}
|
|
29434
|
+
log(` ${manifest.requests} requests · manifest.json${manifest.complete ? "" : " — INCOMPLETE: re-run to resume"}`);
|
|
29435
|
+
return manifest.complete ? 0 : 1;
|
|
29436
|
+
};
|
|
29437
|
+
|
|
28370
29438
|
// src/commands/specEvent.ts
|
|
28371
29439
|
init_meter();
|
|
28372
|
-
import { readFileSync as
|
|
29440
|
+
import { readFileSync as readFileSync33, statSync as statSync8 } from "node:fs";
|
|
28373
29441
|
import { basename as basename9, isAbsolute as isAbsolute7, relative as relative2 } from "node:path";
|
|
28374
29442
|
var HOOK_STDIN_TIMEOUT_MS3 = 1000;
|
|
28375
29443
|
var ESTIMATE_TIMEOUT_MS = 4000;
|
|
@@ -28425,7 +29493,7 @@ function readFileOrNull(path) {
|
|
|
28425
29493
|
try {
|
|
28426
29494
|
if (statSync8(path).size > 2000000)
|
|
28427
29495
|
return null;
|
|
28428
|
-
return
|
|
29496
|
+
return readFileSync33(path, "utf8");
|
|
28429
29497
|
} catch {
|
|
28430
29498
|
return null;
|
|
28431
29499
|
}
|
|
@@ -28489,7 +29557,7 @@ var specEvent = async (o) => {
|
|
|
28489
29557
|
};
|
|
28490
29558
|
|
|
28491
29559
|
// src/commands/status.ts
|
|
28492
|
-
import { existsSync as
|
|
29560
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
28493
29561
|
init_config();
|
|
28494
29562
|
init_settings();
|
|
28495
29563
|
var HEALTH_TIMEOUT_MS2 = 4000;
|
|
@@ -28577,7 +29645,7 @@ var status3 = async (o) => {
|
|
|
28577
29645
|
const log = o.log ?? console.log;
|
|
28578
29646
|
const nowMs = (o.now ?? Date.now)();
|
|
28579
29647
|
const cfg = readConfig(o.home);
|
|
28580
|
-
const settings =
|
|
29648
|
+
const settings = existsSync32(settingsPath(o.home)) ? readSettings(o.home) : {};
|
|
28581
29649
|
const inSettings = readProxy(settings);
|
|
28582
29650
|
const picked = selectOrg(cfg, undefined, o.env ?? process.env);
|
|
28583
29651
|
const viaEnv = picked.from === "env";
|
|
@@ -28770,7 +29838,7 @@ var announce = (o, events) => {
|
|
|
28770
29838
|
|
|
28771
29839
|
// src/commands/stopEvent.ts
|
|
28772
29840
|
init_meter();
|
|
28773
|
-
import { existsSync as
|
|
29841
|
+
import { existsSync as existsSync33, readFileSync as readFileSync34 } from "node:fs";
|
|
28774
29842
|
|
|
28775
29843
|
// src/transcript.ts
|
|
28776
29844
|
var isObject16 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -28871,7 +29939,7 @@ async function postTranscript(o, payload, now) {
|
|
|
28871
29939
|
}
|
|
28872
29940
|
if (!src)
|
|
28873
29941
|
return false;
|
|
28874
|
-
const read = o.readFile ?? ((p) =>
|
|
29942
|
+
const read = o.readFile ?? ((p) => existsSync33(p) ? readFileSync34(p, "utf8") : null);
|
|
28875
29943
|
let text;
|
|
28876
29944
|
try {
|
|
28877
29945
|
text = read(path);
|
|
@@ -28949,7 +30017,7 @@ var stopEvent = async (o) => {
|
|
|
28949
30017
|
|
|
28950
30018
|
// src/commands/uninstall.ts
|
|
28951
30019
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
28952
|
-
import { existsSync as
|
|
30020
|
+
import { existsSync as existsSync34, rmSync as rmSync22 } from "node:fs";
|
|
28953
30021
|
init_config();
|
|
28954
30022
|
init_meter();
|
|
28955
30023
|
init_settings();
|
|
@@ -28962,7 +30030,7 @@ var uninstall2 = (o) => {
|
|
|
28962
30030
|
(o.log ?? console.log)(` ${codexLine}`);
|
|
28963
30031
|
const log = o.log ?? console.log;
|
|
28964
30032
|
const removed = [];
|
|
28965
|
-
if (
|
|
30033
|
+
if (existsSync34(settingsPath(o.home))) {
|
|
28966
30034
|
let settings = readSettings(o.home);
|
|
28967
30035
|
let changed = false;
|
|
28968
30036
|
const cfg = readConfig(o.home);
|
|
@@ -29015,18 +30083,18 @@ var uninstall2 = (o) => {
|
|
|
29015
30083
|
writeSettings(o.home, settings);
|
|
29016
30084
|
}
|
|
29017
30085
|
for (const file of [meterCachePath(o.home), ...allSeatStatePaths(o.home)]) {
|
|
29018
|
-
if (
|
|
30086
|
+
if (existsSync34(file)) {
|
|
29019
30087
|
rmSync22(file, { force: true });
|
|
29020
30088
|
removed.push(file);
|
|
29021
30089
|
}
|
|
29022
30090
|
}
|
|
29023
30091
|
const skill = skillDir(o.home);
|
|
29024
|
-
if (
|
|
30092
|
+
if (existsSync34(skill)) {
|
|
29025
30093
|
rmSync22(skill, { recursive: true, force: true });
|
|
29026
30094
|
removed.push(skill);
|
|
29027
30095
|
}
|
|
29028
30096
|
const plist = musherPlistPath(o.home);
|
|
29029
|
-
if (
|
|
30097
|
+
if (existsSync34(plist)) {
|
|
29030
30098
|
(o.unloadAgent ?? defaultUnload)(plist);
|
|
29031
30099
|
rmSync22(plist, { force: true });
|
|
29032
30100
|
removed.push(`launchd agent ${musherLabel()}`);
|
|
@@ -29044,8 +30112,8 @@ var uninstall2 = (o) => {
|
|
|
29044
30112
|
|
|
29045
30113
|
// src/commands/update.ts
|
|
29046
30114
|
init_server();
|
|
29047
|
-
import { existsSync as
|
|
29048
|
-
import { homedir as
|
|
30115
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
30116
|
+
import { homedir as homedir9, userInfo as userInfo4 } from "node:os";
|
|
29049
30117
|
import { basename as basename10 } from "node:path";
|
|
29050
30118
|
init_config();
|
|
29051
30119
|
init_state();
|
|
@@ -29111,7 +30179,7 @@ var realPidAlive4 = (pid) => {
|
|
|
29111
30179
|
};
|
|
29112
30180
|
function machineClient(home, env) {
|
|
29113
30181
|
const src = meterSource(home, undefined, { env });
|
|
29114
|
-
return src ? fetchClient({ ...src, version: "0.1.
|
|
30182
|
+
return src ? fetchClient({ ...src, version: "0.1.65" }) : null;
|
|
29115
30183
|
}
|
|
29116
30184
|
async function noteAnswer2(home, a, now) {
|
|
29117
30185
|
try {
|
|
@@ -29133,7 +30201,7 @@ function busyLine(busy) {
|
|
|
29133
30201
|
return `${n} session${n === 1 ? " is" : "s are"} running on ${on} — sleds update --now releases ${n === 1 ? "it" : "them"}`;
|
|
29134
30202
|
}
|
|
29135
30203
|
async function updateCmd(o) {
|
|
29136
|
-
const home = o.home ??
|
|
30204
|
+
const home = o.home ?? homedir9();
|
|
29137
30205
|
const env = o.env ?? process.env;
|
|
29138
30206
|
const log = o.log ?? console.log;
|
|
29139
30207
|
const error = o.error ?? console.error;
|
|
@@ -29259,7 +30327,7 @@ async function updateCmd(o) {
|
|
|
29259
30327
|
error(`sleds ${to} could not be downloaded: ${got.why}`);
|
|
29260
30328
|
return 1;
|
|
29261
30329
|
}
|
|
29262
|
-
} else if (!
|
|
30330
|
+
} else if (!existsSync35(versionBin(home, to, platform))) {
|
|
29263
30331
|
error(`sleds ${to} is not on this machine to move back to`);
|
|
29264
30332
|
return 1;
|
|
29265
30333
|
}
|
|
@@ -29353,7 +30421,7 @@ var verify2 = async (o) => {
|
|
|
29353
30421
|
|
|
29354
30422
|
// src/commands/webhooks.ts
|
|
29355
30423
|
var USAGE3 = "usage: sleds webhooks [--json] | sleds webhooks deliveries <id or name> [--json]";
|
|
29356
|
-
var
|
|
30424
|
+
var str16 = (x) => typeof x === "string" ? x : "";
|
|
29357
30425
|
function ago3(at, now) {
|
|
29358
30426
|
const m = Math.max(0, Math.floor((now - at) / 60000));
|
|
29359
30427
|
if (m < 1)
|
|
@@ -29369,7 +30437,7 @@ function webhookLines(raw, now) {
|
|
|
29369
30437
|
if (!rows.length)
|
|
29370
30438
|
return ["no webhooks — add one in the app: Settings › Integrations › Webhooks"];
|
|
29371
30439
|
return rows.map((h) => {
|
|
29372
|
-
let host =
|
|
30440
|
+
let host = str16(h.url);
|
|
29373
30441
|
try {
|
|
29374
30442
|
host = new URL(host.replace(/\{[a-z.]+\}/g, "x")).host;
|
|
29375
30443
|
} catch {}
|
|
@@ -29377,21 +30445,21 @@ function webhookLines(raw, now) {
|
|
|
29377
30445
|
const last = h.lastDelivery;
|
|
29378
30446
|
const lastText = last?.at ? `last ${last.status ?? "—"} ${last.state ?? ""} ${ago3(last.at, now)}`.replace(/\s+/g, " ") : "nothing sent yet";
|
|
29379
30447
|
const off = h.enabled ? "on " : "off";
|
|
29380
|
-
const why = !h.enabled && h.disabledWhy ? ` (${
|
|
29381
|
-
return `${off} ${
|
|
30448
|
+
const why = !h.enabled && h.disabledWhy ? ` (${str16(h.disabledWhy)})` : "";
|
|
30449
|
+
return `${off} ${str16(h.name)} ${host} ${events.includes("*") ? "all events" : events.join(",")} ${lastText}${why} [${str16(h.id)}]`;
|
|
29382
30450
|
});
|
|
29383
30451
|
}
|
|
29384
30452
|
function deliveryLines(raw, now) {
|
|
29385
30453
|
const j = clean(raw);
|
|
29386
30454
|
const rows = Array.isArray(j.deliveries) ? j.deliveries : [];
|
|
29387
|
-
const name =
|
|
30455
|
+
const name = str16(j.webhook?.name);
|
|
29388
30456
|
if (!rows.length)
|
|
29389
30457
|
return [`${name}: nothing sent yet`];
|
|
29390
30458
|
return [
|
|
29391
30459
|
`${name} — the last ${rows.length}`,
|
|
29392
30460
|
...rows.map((d) => {
|
|
29393
|
-
const result = d.status !== null && d.status !== undefined ? String(d.status) :
|
|
29394
|
-
return `${
|
|
30461
|
+
const result = d.status !== null && d.status !== undefined ? String(d.status) : str16(d.error) || "—";
|
|
30462
|
+
return `${str16(d.state).padEnd(9)} ${result.padEnd(5)} ${str16(d.event).padEnd(20)} ${str16(d.key) || "—"} ${ago3(Number(d.at), now)} tries ${d.attempts} ${str16(d.deliveryId)}`;
|
|
29395
30463
|
})
|
|
29396
30464
|
];
|
|
29397
30465
|
}
|
|
@@ -29422,7 +30490,7 @@ var webhooksCmd = async (o) => {
|
|
|
29422
30490
|
return 1;
|
|
29423
30491
|
}
|
|
29424
30492
|
if (r.status !== 200 || !r.json) {
|
|
29425
|
-
const why =
|
|
30493
|
+
const why = str16(r.json?.error) || `HTTP ${r.status}`;
|
|
29426
30494
|
error(r.status === 404 && !r.json ? "SLEDS does not know this seat — run `sleds init`" : why);
|
|
29427
30495
|
return 1;
|
|
29428
30496
|
}
|
|
@@ -29436,13 +30504,13 @@ var webhooksCmd = async (o) => {
|
|
|
29436
30504
|
|
|
29437
30505
|
// src/config.ts
|
|
29438
30506
|
init_settings();
|
|
29439
|
-
import { chmodSync as chmodSync8, existsSync as
|
|
29440
|
-
import { join as
|
|
30507
|
+
import { chmodSync as chmodSync8, existsSync as existsSync36, mkdirSync as mkdirSync29, readFileSync as readFileSync35, renameSync as renameSync14, writeFileSync as writeFileSync29 } from "node:fs";
|
|
30508
|
+
import { join as join36, resolve as resolve13 } from "node:path";
|
|
29441
30509
|
var configDir2 = (home, env = process.env) => {
|
|
29442
30510
|
const o = env.SLEDS_HOME?.trim();
|
|
29443
|
-
return o ?
|
|
30511
|
+
return o ? resolve13(home, o) : join36(home, ".config", "sleds");
|
|
29444
30512
|
};
|
|
29445
|
-
var configPath2 = (home) =>
|
|
30513
|
+
var configPath2 = (home) => join36(configDir2(home), "config.json");
|
|
29446
30514
|
var isObject17 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
29447
30515
|
var optString2 = (v) => typeof v === "string" ? v : undefined;
|
|
29448
30516
|
var parsePriorEnv2 = (v) => {
|
|
@@ -29577,9 +30645,9 @@ var parseOrgInfo2 = (raw) => {
|
|
|
29577
30645
|
};
|
|
29578
30646
|
var readConfig2 = (home) => {
|
|
29579
30647
|
const p = configPath2(home);
|
|
29580
|
-
if (!
|
|
30648
|
+
if (!existsSync36(p))
|
|
29581
30649
|
return {};
|
|
29582
|
-
return parseConfig2(JSON.parse(
|
|
30650
|
+
return parseConfig2(JSON.parse(readFileSync35(p, "utf8")));
|
|
29583
30651
|
};
|
|
29584
30652
|
|
|
29585
30653
|
// src/runners/exec.ts
|
|
@@ -29636,10 +30704,10 @@ function runnerFor3(name, exec = defaultExec, o = {}) {
|
|
|
29636
30704
|
}
|
|
29637
30705
|
|
|
29638
30706
|
// src/runtime.ts
|
|
29639
|
-
import { homedir as
|
|
29640
|
-
import { join as
|
|
30707
|
+
import { homedir as homedir10 } from "node:os";
|
|
30708
|
+
import { join as join37, win32 as win323 } from "node:path";
|
|
29641
30709
|
var isCompiled2 = (argv1 = process.argv[1]) => Boolean(argv1 && (argv1.startsWith("/$bunfs/") || argv1.startsWith("B:\\~BUN\\")));
|
|
29642
|
-
var sledsLink2 = (home =
|
|
30710
|
+
var sledsLink2 = (home = homedir10(), platform = process.platform) => platform === "win32" ? win323.join(process.env.LOCALAPPDATA ?? win323.join(home, "AppData", "Local"), "sleds", "bin", "sleds.exe") : join37(home, ".sleds", "bin", "sleds");
|
|
29643
30711
|
|
|
29644
30712
|
// src/schedule.ts
|
|
29645
30713
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
@@ -29651,15 +30719,15 @@ var defaultExec5 = (cmd, args) => {
|
|
|
29651
30719
|
};
|
|
29652
30720
|
|
|
29653
30721
|
// src/settings.ts
|
|
29654
|
-
import { dirname as
|
|
30722
|
+
import { dirname as dirname26, join as join38, resolve as resolve14 } from "node:path";
|
|
29655
30723
|
var withSeatEnv2 = (command, home, env = process.env, org) => {
|
|
29656
30724
|
const parts = [];
|
|
29657
30725
|
const sh = env.SLEDS_HOME?.trim();
|
|
29658
30726
|
const cc = env.CLAUDE_CONFIG_DIR?.trim();
|
|
29659
30727
|
if (sh)
|
|
29660
|
-
parts.push(`SLEDS_HOME=${shellWord2(
|
|
30728
|
+
parts.push(`SLEDS_HOME=${shellWord2(resolve14(home, sh))}`);
|
|
29661
30729
|
if (cc)
|
|
29662
|
-
parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(
|
|
30730
|
+
parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve14(home, cc))}`);
|
|
29663
30731
|
parts.push(command);
|
|
29664
30732
|
return pinOrg4(parts.join(" "), org);
|
|
29665
30733
|
};
|
|
@@ -29745,7 +30813,11 @@ commands:
|
|
|
29745
30813
|
--run start Claude Code with the pack as the first prompt
|
|
29746
30814
|
webhooks The org's outbound webhooks (admins); deliveries <id or name> lists one hook's
|
|
29747
30815
|
last hundred deliveries. Registering and Redeliver stay in the app.
|
|
29748
|
-
|
|
30816
|
+
sleuth export --org <slug> --out <dir>: archive a Sleuth.io org before it closes \u2014
|
|
30817
|
+
every deploy as JSONL by month, its projects, environments, teams, incidents and
|
|
30818
|
+
flag events, and a manifest whose counts sit beside Sleuth's own. Key from
|
|
30819
|
+
SLEUTH_API_KEY; re-run to resume. \`sleds sleuth\` alone lists every option.
|
|
30820
|
+
deploy A release step's one line: tell SLEDS this commit is now running in an environment.
|
|
29749
30821
|
--env <slug> the environment (required); --service <name> for a monorepo
|
|
29750
30822
|
--sha / --repo else the CI's own variables, then git
|
|
29751
30823
|
Token from SLEDS_DEPLOY_TOKEN (Settings \u203A Deploys). SLEDS down never fails a release.
|
|
@@ -29951,17 +31023,17 @@ var hookStdin = async () => {
|
|
|
29951
31023
|
var sledsArgv = (execPath = process.execPath, script = process.argv[1], realpath, o = {}) => {
|
|
29952
31024
|
if (isCompiled2(script)) {
|
|
29953
31025
|
const link = sledsLink2(o.home);
|
|
29954
|
-
return stableArgv2([(o.exists ??
|
|
31026
|
+
return stableArgv2([(o.exists ?? existsSync37)(link) ? link : execPath], realpath, o.home);
|
|
29955
31027
|
}
|
|
29956
31028
|
return stableArgv2(script ? [execPath, script] : [execPath], realpath, o.home);
|
|
29957
31029
|
};
|
|
29958
31030
|
var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
29959
|
-
var statusLineCommand = (argv, home =
|
|
29960
|
-
var seatEventCommand = (argv, home =
|
|
29961
|
-
var promptEventCommand = (argv, home =
|
|
29962
|
-
var sessionEventCommand = (argv, home =
|
|
29963
|
-
var stopEventCommand = (argv, home =
|
|
29964
|
-
var specEventCommand = (argv, home =
|
|
31031
|
+
var statusLineCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
|
|
31032
|
+
var seatEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
|
|
31033
|
+
var promptEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "prompt-event"].map(shellQuote).join(" "), home);
|
|
31034
|
+
var sessionEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "session-event"].map(shellQuote).join(" "), home);
|
|
31035
|
+
var stopEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "stop-event"].map(shellQuote).join(" "), home);
|
|
31036
|
+
var specEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
|
|
29965
31037
|
var mcpFlag = (flags) => flags["no-mcp"] === true ? false : flags.mcp === true ? true : undefined;
|
|
29966
31038
|
var main = async (argv) => {
|
|
29967
31039
|
const { cmd, flags } = parseArgs(argv);
|
|
@@ -29969,7 +31041,7 @@ var main = async (argv) => {
|
|
|
29969
31041
|
console.log(commandUsage(cmd));
|
|
29970
31042
|
return 0;
|
|
29971
31043
|
}
|
|
29972
|
-
const home =
|
|
31044
|
+
const home = homedir11();
|
|
29973
31045
|
const scheduler = {
|
|
29974
31046
|
platform: platform3(),
|
|
29975
31047
|
home,
|
|
@@ -29981,7 +31053,7 @@ var main = async (argv) => {
|
|
|
29981
31053
|
now: Date.now(),
|
|
29982
31054
|
stderr: (line) => console.error(line),
|
|
29983
31055
|
isTTY: !!process.stderr.isTTY,
|
|
29984
|
-
version: "0.1.
|
|
31056
|
+
version: "0.1.65"
|
|
29985
31057
|
});
|
|
29986
31058
|
switch (cmd) {
|
|
29987
31059
|
case "update":
|
|
@@ -29996,6 +31068,8 @@ var main = async (argv) => {
|
|
|
29996
31068
|
return packCmd({ home, argv: argv.slice(1) });
|
|
29997
31069
|
case "webhooks":
|
|
29998
31070
|
return webhooksCmd({ home, argv: argv.slice(1) });
|
|
31071
|
+
case "sleuth":
|
|
31072
|
+
return sleuthCmd({ argv: argv.slice(1), home });
|
|
29999
31073
|
case "deploy":
|
|
30000
31074
|
return deployCmd({ argv: argv.slice(1) });
|
|
30001
31075
|
case "sled":
|
|
@@ -30214,7 +31288,7 @@ ${renderHelpText(argv[1])}`);
|
|
|
30214
31288
|
case "version":
|
|
30215
31289
|
case "--version":
|
|
30216
31290
|
case "-v":
|
|
30217
|
-
console.log("sleds 0.1.
|
|
31291
|
+
console.log("sleds 0.1.65");
|
|
30218
31292
|
return 0;
|
|
30219
31293
|
default:
|
|
30220
31294
|
console.error(`unknown command: ${cmd}
|