@quatronic/qore 0.1.6 → 0.1.7
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 +11 -0
- package/dist/cli.js +694 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,11 @@ qore version # CLI + portal version info
|
|
|
23
23
|
|
|
24
24
|
qore env pull # write this module's local secrets into an env file
|
|
25
25
|
qore run -- <command> # run a command with those secrets in its environment
|
|
26
|
+
|
|
27
|
+
qore errors --env stag # recent unresolved errors
|
|
28
|
+
qore errors FRONTEND-1A --env prod --with trace,logs # one error, its trace and logs
|
|
29
|
+
qore traces --env stag --slower-than 1s # slow requests
|
|
30
|
+
qore logs --env stag --level error # error logs
|
|
26
31
|
```
|
|
27
32
|
|
|
28
33
|
### Portals
|
|
@@ -57,6 +62,12 @@ current directory. `qore env pull --project` writes into the located module,
|
|
|
57
62
|
and a relative `--target` then resolves against that project directory, not
|
|
58
63
|
the directory you ran it from.
|
|
59
64
|
|
|
65
|
+
A repository that is no module counts as outside one: standing in some other
|
|
66
|
+
codebase, `qore dev` asks the portal about its remotes and, when they match no
|
|
67
|
+
module (or it has no remote Qore can match), offers your projects just the
|
|
68
|
+
same. A repository inside a known project is never asked about, and a portal
|
|
69
|
+
that cannot answer keeps today's error.
|
|
70
|
+
|
|
60
71
|
Logged out with a projects folder set, the local lookup still runs first: a
|
|
61
72
|
folder that holds no projects gives `QORE_PROJECT_NOT_FOUND`; otherwise the
|
|
62
73
|
project is located exactly as when logged in (a match, a picker, or
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bf813c0e-4054-532d-9e74-62fbe8673876")}catch(e){}}();
|
|
4
4
|
import { createRequire as __qoreCreateRequire } from "node:module"; const require = __qoreCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
6
|
addSecretValues,
|
|
@@ -538,8 +538,8 @@ async function fetchMetaWithDate(fetch3, apiOrigin) {
|
|
|
538
538
|
}
|
|
539
539
|
if (!res.ok) throw await problemToError(res);
|
|
540
540
|
const header = res.headers.get("date");
|
|
541
|
-
const
|
|
542
|
-
return { meta: await res.json(), date: Number.isNaN(
|
|
541
|
+
const ms2 = header ? Date.parse(header) : NaN;
|
|
542
|
+
return { meta: await res.json(), date: Number.isNaN(ms2) ? null : new Date(ms2) };
|
|
543
543
|
}
|
|
544
544
|
function negotiate(meta, portal, cliVersion) {
|
|
545
545
|
if (portal.pinnedIssuer && meta.auth.issuer !== portal.pinnedIssuer) {
|
|
@@ -678,7 +678,7 @@ var QoreApiClient = class {
|
|
|
678
678
|
this.o.onRequest?.(`${method} ${path} \u2192 ${res.status}${rid ? ` (${rid})` : ""}`);
|
|
679
679
|
return res;
|
|
680
680
|
}
|
|
681
|
-
async request(method, path, body) {
|
|
681
|
+
async request(method, path, body, parse2 = (r) => r.json()) {
|
|
682
682
|
let res = await this.once(method, path, body, false);
|
|
683
683
|
if (res.status === 401) {
|
|
684
684
|
res = await this.once(method, path, body, true);
|
|
@@ -700,11 +700,14 @@ var QoreApiClient = class {
|
|
|
700
700
|
}
|
|
701
701
|
}
|
|
702
702
|
if (!res.ok) throw await problemToError(res);
|
|
703
|
-
return
|
|
703
|
+
return parse2(res);
|
|
704
704
|
}
|
|
705
705
|
get(path) {
|
|
706
706
|
return this.request("GET", path);
|
|
707
707
|
}
|
|
708
|
+
getText(path) {
|
|
709
|
+
return this.request("GET", path, void 0, (r) => r.text());
|
|
710
|
+
}
|
|
708
711
|
post(path, body) {
|
|
709
712
|
return this.request("POST", path, body);
|
|
710
713
|
}
|
|
@@ -720,13 +723,13 @@ var RefreshCoordinator = class {
|
|
|
720
723
|
fresh(expiresAt) {
|
|
721
724
|
return new Date(expiresAt).getTime() - this.o.now().getTime() > SKEW_MS;
|
|
722
725
|
}
|
|
723
|
-
async rotate(
|
|
726
|
+
async rotate(scope2) {
|
|
724
727
|
let stored = await this.o.store.getRefresh();
|
|
725
728
|
if (!stored) throw new QoreError("QORE_NOT_LOGGED_IN", "Not logged in", { hint: "Run `qore login`." });
|
|
726
729
|
let t;
|
|
727
730
|
for (let attempt = 0; ; attempt++) {
|
|
728
731
|
try {
|
|
729
|
-
t = await (await this.o.oidc()).refresh(stored.rt,
|
|
732
|
+
t = await (await this.o.oidc()).refresh(stored.rt, scope2);
|
|
730
733
|
break;
|
|
731
734
|
} catch (e) {
|
|
732
735
|
if (!(e instanceof QoreError && e.code === "QORE_SESSION_EXPIRED")) throw e;
|
|
@@ -742,7 +745,7 @@ var RefreshCoordinator = class {
|
|
|
742
745
|
throw e;
|
|
743
746
|
}
|
|
744
747
|
}
|
|
745
|
-
await this.o.store.setRefresh({ rt: t.refreshToken, sub: t.sub, scope: t.scope || (
|
|
748
|
+
await this.o.store.setRefresh({ rt: t.refreshToken, sub: t.sub, scope: t.scope || (scope2 ?? stored.scope), obtainedAt: this.o.now().toISOString() });
|
|
746
749
|
return t;
|
|
747
750
|
}
|
|
748
751
|
async accessToken(opts = {}) {
|
|
@@ -2125,6 +2128,7 @@ async function anyLoggedIn(deps2, cfg) {
|
|
|
2125
2128
|
}
|
|
2126
2129
|
return false;
|
|
2127
2130
|
}
|
|
2131
|
+
var isForeignRepo = (e) => e instanceof QoreError && e.code === "QORE_CONTEXT_REQUIRED" && e.details?.foreignRepo === true;
|
|
2128
2132
|
async function resolveModuleContext(deps2, o) {
|
|
2129
2133
|
const cache = new ResolutionCache(join11(deps2.config.dir, "cache"), deps2.now);
|
|
2130
2134
|
const ws = await findWorkspace(deps2.cwd, deps2.home);
|
|
@@ -2143,7 +2147,7 @@ async function resolveModuleContext(deps2, o) {
|
|
|
2143
2147
|
const said = gitError?.hint;
|
|
2144
2148
|
throw new QoreError("QORE_CONTEXT_REQUIRED", "This repository has no remote Qore can match", {
|
|
2145
2149
|
hint: said ? `${HINT_FLAG} git said: ${said}` : HINT_FLAG,
|
|
2146
|
-
details: gitError ? { git: said ?? gitError.message } :
|
|
2150
|
+
details: gitError ? { git: said ?? gitError.message } : { foreignRepo: true },
|
|
2147
2151
|
cause: gitError
|
|
2148
2152
|
});
|
|
2149
2153
|
}
|
|
@@ -2172,6 +2176,7 @@ async function resolveModuleContext(deps2, o) {
|
|
|
2172
2176
|
}
|
|
2173
2177
|
const details = { triedProfiles: tried };
|
|
2174
2178
|
if (skipped.length > 0) details.skippedProfiles = skipped;
|
|
2179
|
+
if (answered.length > 0) details.foreignRepo = true;
|
|
2175
2180
|
if (answered.length === 0) {
|
|
2176
2181
|
const cause = skipErrors[0] ?? unsupported2;
|
|
2177
2182
|
if (cause) throw withDetails(cause, details);
|
|
@@ -2579,7 +2584,7 @@ function notClonedReason(m, am) {
|
|
|
2579
2584
|
}
|
|
2580
2585
|
|
|
2581
2586
|
// src/core/dev/findings.ts
|
|
2582
|
-
var plural = (n,
|
|
2587
|
+
var plural = (n, one2, many) => n === 1 ? one2 : many;
|
|
2583
2588
|
function modulesMissing(o) {
|
|
2584
2589
|
const actionFor = new Map(o.actions.map((a) => [a.module.slug, a]));
|
|
2585
2590
|
const findings = [];
|
|
@@ -2869,7 +2874,7 @@ function nodeVersionError(found, range, slug) {
|
|
|
2869
2874
|
// src/core/dev/preflight.ts
|
|
2870
2875
|
var pmMajor = (v) => v ? v.split(".")[0] : "latest";
|
|
2871
2876
|
var GRACE_MS = 1e4;
|
|
2872
|
-
var secs = (
|
|
2877
|
+
var secs = (ms2) => `${Math.round(ms2 / 1e3)} s`;
|
|
2873
2878
|
async function runTool(deps2, cmd, args, o) {
|
|
2874
2879
|
if (o.signal.aborted) throw new CancelledError();
|
|
2875
2880
|
const tail = [];
|
|
@@ -3057,13 +3062,13 @@ async function checkPort(deps2, o) {
|
|
|
3057
3062
|
throw await portInUseError(deps2, { slug: o.slug, port: o.port, key: o.key, runs: o.runs });
|
|
3058
3063
|
}
|
|
3059
3064
|
function abortableDelay(signal) {
|
|
3060
|
-
return (
|
|
3065
|
+
return (ms2) => new Promise((resolve6) => {
|
|
3061
3066
|
const done = () => {
|
|
3062
3067
|
clearTimeout(t);
|
|
3063
3068
|
signal.removeEventListener("abort", done);
|
|
3064
3069
|
resolve6();
|
|
3065
3070
|
};
|
|
3066
|
-
const t = setTimeout(done,
|
|
3071
|
+
const t = setTimeout(done, ms2);
|
|
3067
3072
|
signal.addEventListener("abort", done, { once: true });
|
|
3068
3073
|
});
|
|
3069
3074
|
}
|
|
@@ -3210,10 +3215,20 @@ var isDir = async (p) => (await stat4(p).catch(() => null))?.isDirectory() ?? fa
|
|
|
3210
3215
|
var label2 = (p) => `${p.tenantSlug} / ${p.appSlug}${p.kind === "module" ? ` / ${p.moduleSlug}` : ""}${p.source === "registry" ? ` (${p.path})` : ""}`;
|
|
3211
3216
|
var candidate = (p) => p.kind === "module" ? { kind: "module", id: p.moduleId, slug: p.moduleSlug, tenant: p.tenantSlug, name: p.path } : { kind: "app", slug: p.appSlug, tenant: p.tenantSlug, name: p.path };
|
|
3212
3217
|
var withCwd = (ctx, cwd) => ({ ...ctx, deps: { ...ctx.deps, cwd } });
|
|
3213
|
-
async function
|
|
3214
|
-
if (await findWorkspace(ctx.deps.cwd, ctx.deps.home)) return
|
|
3218
|
+
async function standingIn(ctx) {
|
|
3219
|
+
if (await findWorkspace(ctx.deps.cwd, ctx.deps.home)) return "workspace";
|
|
3215
3220
|
const repo = await findRepoRoot(ctx.deps.cwd);
|
|
3216
|
-
return repo !== null && !isInside(repo, ctx.deps.home);
|
|
3221
|
+
return repo !== null && !isInside(repo, ctx.deps.home) ? { repo } : null;
|
|
3222
|
+
}
|
|
3223
|
+
async function isForeign(ctx, command) {
|
|
3224
|
+
const g = ctx.opts();
|
|
3225
|
+
try {
|
|
3226
|
+
await resolveModuleContext(ctx.deps, { portal: g.portal, command, onRequest: g.verbose ? (l) => ctx.io.err(l) : void 0 });
|
|
3227
|
+
return false;
|
|
3228
|
+
} catch (e) {
|
|
3229
|
+
if (e instanceof CancelledError) throw e;
|
|
3230
|
+
return isForeignRepo(e);
|
|
3231
|
+
}
|
|
3217
3232
|
}
|
|
3218
3233
|
async function moduleDirs(wsPath) {
|
|
3219
3234
|
const out = [];
|
|
@@ -3288,8 +3303,11 @@ async function projectCtx(ctx, o) {
|
|
|
3288
3303
|
const m2 = await choose(ctx, await liveMatches(matches), `"${o.ref}" matches several projects`, true);
|
|
3289
3304
|
return relocate(ctx, await pathFor(ctx, m2, o.needModule));
|
|
3290
3305
|
}
|
|
3291
|
-
if (o.module || o.app
|
|
3306
|
+
if (o.module || o.app) return ctx;
|
|
3307
|
+
const here = await standingIn(ctx);
|
|
3308
|
+
if (here === "workspace") return ctx;
|
|
3292
3309
|
const known = await knownProjects(ctx.deps, { portal });
|
|
3310
|
+
if (here && (known.length === 0 || known.some((p) => isInside(p.path, here.repo)) || !await isForeign(ctx, o.command ?? "project"))) return ctx;
|
|
3293
3311
|
if (known.length === 0) {
|
|
3294
3312
|
if (!cfg.projectsRoot) {
|
|
3295
3313
|
if (!await loggedInAs(ctx)) return ctx;
|
|
@@ -3301,7 +3319,8 @@ async function projectCtx(ctx, o) {
|
|
|
3301
3319
|
hint: "Run `qore clone <app>`."
|
|
3302
3320
|
});
|
|
3303
3321
|
}
|
|
3304
|
-
const
|
|
3322
|
+
const what = here ? "This repository is not a Qore module \u2014 which project?" : "Not inside a project \u2014 which one?";
|
|
3323
|
+
const m = await choose(ctx, known.map((project) => ({ project })), what, false);
|
|
3305
3324
|
return relocate(ctx, await pathFor(ctx, m, o.needModule));
|
|
3306
3325
|
}
|
|
3307
3326
|
function relocate(ctx, path) {
|
|
@@ -3342,6 +3361,10 @@ async function rememberWorkspace(ctx, ws, app) {
|
|
|
3342
3361
|
|
|
3343
3362
|
// src/cli/context.ts
|
|
3344
3363
|
var allOfKind = (c, kind) => !!c && c.length > 0 && c.every((x) => x.kind === kind && typeof x.id === "string");
|
|
3364
|
+
async function pick(ctx, err, message, choices) {
|
|
3365
|
+
if (!ctx.io.interactive) throw err;
|
|
3366
|
+
return ctx.io.select(message, choices);
|
|
3367
|
+
}
|
|
3345
3368
|
async function moduleContextFor(ctx, flags, command) {
|
|
3346
3369
|
const g = ctx.opts();
|
|
3347
3370
|
const onRequest = g.verbose ? (l) => ctx.io.err(l) : void 0;
|
|
@@ -3351,14 +3374,16 @@ async function moduleContextFor(ctx, flags, command) {
|
|
|
3351
3374
|
} catch (e) {
|
|
3352
3375
|
if (!(e instanceof QoreError) || e.code !== "QORE_CONTEXT_REQUIRED" || !ctx.io.interactive) throw e;
|
|
3353
3376
|
if (allOfKind(e.candidates, "module")) {
|
|
3354
|
-
const choose2 = await
|
|
3377
|
+
const choose2 = await pick(
|
|
3378
|
+
ctx,
|
|
3379
|
+
e,
|
|
3355
3380
|
"Select a module",
|
|
3356
3381
|
e.candidates.map((c) => ({ name: `${c.tenant ? `${c.tenant} / ` : ""}${c.slug} (${c.remote})`, value: c.id }))
|
|
3357
3382
|
);
|
|
3358
3383
|
return resolveModuleContext(ctx.deps, { ...base, choose: choose2 });
|
|
3359
3384
|
}
|
|
3360
3385
|
if (allOfKind(e.candidates, "tenant")) {
|
|
3361
|
-
const tenant = await ctx
|
|
3386
|
+
const tenant = await pick(ctx, e, "Select a tenant", e.candidates.map((c) => ({ name: c.name ?? c.slug, value: c.id })));
|
|
3362
3387
|
return resolveModuleContext(ctx.deps, { ...base, tenant });
|
|
3363
3388
|
}
|
|
3364
3389
|
throw e;
|
|
@@ -3369,7 +3394,7 @@ async function appFor(ctx, mc, app) {
|
|
|
3369
3394
|
return pickApp2(mc, app);
|
|
3370
3395
|
} catch (e) {
|
|
3371
3396
|
if (!(e instanceof QoreError) || e.code !== "QORE_APP_AMBIGUOUS" || !ctx.io.interactive) throw e;
|
|
3372
|
-
const id = await ctx
|
|
3397
|
+
const id = await pick(ctx, e, "Select an app", mc.module.apps.map((a) => ({ name: a.name, value: a.id })));
|
|
3373
3398
|
return pickApp2(mc, id);
|
|
3374
3399
|
}
|
|
3375
3400
|
}
|
|
@@ -3701,8 +3726,8 @@ function exitCode(e) {
|
|
|
3701
3726
|
}
|
|
3702
3727
|
function supervise(o) {
|
|
3703
3728
|
const now = o.now ?? Date.now;
|
|
3704
|
-
const delay = o.delay ?? ((
|
|
3705
|
-
setTimeout(r,
|
|
3729
|
+
const delay = o.delay ?? ((ms2) => new Promise((r) => {
|
|
3730
|
+
setTimeout(r, ms2).unref();
|
|
3706
3731
|
}));
|
|
3707
3732
|
const abort = new AbortController();
|
|
3708
3733
|
const emit = (e) => {
|
|
@@ -3880,7 +3905,7 @@ function registerRun(program, ctx) {
|
|
|
3880
3905
|
hint: "Drop --json; the child's own output passes through."
|
|
3881
3906
|
});
|
|
3882
3907
|
}
|
|
3883
|
-
const c = await projectCtx(ctx, { ref: f.project, module: f.module, app: f.app, needModule: true });
|
|
3908
|
+
const c = await projectCtx(ctx, { ref: f.project, module: f.module, app: f.app, needModule: true, command: "run" });
|
|
3884
3909
|
const { deps: deps2 } = c;
|
|
3885
3910
|
const mc = await moduleContextFor(c, f, "run");
|
|
3886
3911
|
const app = await appFor(c, mc, f.app);
|
|
@@ -4410,7 +4435,7 @@ function registerDev(program, ctx) {
|
|
|
4410
4435
|
hint: "qore dev <project> | qore dev --project <project>"
|
|
4411
4436
|
});
|
|
4412
4437
|
}
|
|
4413
|
-
const c = await projectCtx(ctx, { ref: project ?? f.project, module: f.module, app: f.app, needModule: false });
|
|
4438
|
+
const c = await projectCtx(ctx, { ref: project ?? f.project, module: f.module, app: f.app, needModule: false, command: "dev" });
|
|
4414
4439
|
const ws = await workspaceRoot(c.deps);
|
|
4415
4440
|
if (ws && f.module === void 0) return workspaceDev(c, ws, f, waitTimeout);
|
|
4416
4441
|
return moduleDev(c, f, waitTimeout);
|
|
@@ -4515,9 +4540,9 @@ async function moduleDev(ctx, f, waitTimeout) {
|
|
|
4515
4540
|
childRunning.current = false;
|
|
4516
4541
|
await removeRunFile(deps2, root);
|
|
4517
4542
|
}
|
|
4518
|
-
const
|
|
4543
|
+
const ms2 = deps2.now().getTime() - startedAt.getTime();
|
|
4519
4544
|
if (exit.code !== 0 && exit.signal === null) {
|
|
4520
|
-
io2.err(
|
|
4545
|
+
io2.err(ms2 < EARLY_EXIT_MS ? `\u2717 ${slug} exited after ${Math.round(ms2 / 1e3)} s with code ${exit.code ?? 1} \u2014 its output above says why.` : `\u2717 ${slug} exited with code ${exit.code ?? 1}.`);
|
|
4521
4546
|
const hint = exitHint(tee.lines(), hintContext(recipe, pkg, devEnv2));
|
|
4522
4547
|
if (hint) io2.err(` \u2192 ${hint.hint}`);
|
|
4523
4548
|
}
|
|
@@ -4698,19 +4723,19 @@ var withError = (status2, label3, e) => ({ status: status2, label: label3, error
|
|
|
4698
4723
|
var isCancel = (e, signal) => e instanceof CancelledError || signal.aborted;
|
|
4699
4724
|
var portalLabel = (p) => p.builtin ?? p.apiOrigin;
|
|
4700
4725
|
var ToolTimeout = class extends Error {
|
|
4701
|
-
constructor(tool,
|
|
4702
|
-
super(`${tool} did not answer within ${Math.round(
|
|
4726
|
+
constructor(tool, ms2) {
|
|
4727
|
+
super(`${tool} did not answer within ${Math.round(ms2 / 1e3)} s`);
|
|
4703
4728
|
this.tool = tool;
|
|
4704
|
-
this.ms =
|
|
4729
|
+
this.ms = ms2;
|
|
4705
4730
|
}
|
|
4706
4731
|
tool;
|
|
4707
4732
|
ms;
|
|
4708
4733
|
};
|
|
4709
|
-
async function timed(tool, signal,
|
|
4734
|
+
async function timed(tool, signal, ms2, fn) {
|
|
4710
4735
|
try {
|
|
4711
|
-
return await fn(AbortSignal.any([signal, AbortSignal.timeout(
|
|
4736
|
+
return await fn(AbortSignal.any([signal, AbortSignal.timeout(ms2)]));
|
|
4712
4737
|
} catch (e) {
|
|
4713
|
-
if (e instanceof CancelledError && !signal.aborted) throw new ToolTimeout(tool,
|
|
4738
|
+
if (e instanceof CancelledError && !signal.aborted) throw new ToolTimeout(tool, ms2);
|
|
4714
4739
|
throw e;
|
|
4715
4740
|
}
|
|
4716
4741
|
}
|
|
@@ -4741,8 +4766,8 @@ function checkOwnNode(deps2) {
|
|
|
4741
4766
|
}
|
|
4742
4767
|
return ok(`Node ${v} (\u226522 required)`);
|
|
4743
4768
|
}
|
|
4744
|
-
async function checkGit(deps2, signal,
|
|
4745
|
-
const res = await timed("git", signal,
|
|
4769
|
+
async function checkGit(deps2, signal, ms2) {
|
|
4770
|
+
const res = await timed("git", signal, ms2, (s) => runTool(deps2, "git", ["--version"], { cwd: deps2.home, env: deps2.env, onLine: noop, signal: s }));
|
|
4746
4771
|
if (res.spawnError === "ENOENT") {
|
|
4747
4772
|
const e = new QoreError("QORE_GIT_UNAVAILABLE", "git is not installed or not on PATH", {
|
|
4748
4773
|
hint: "Install git (https://git-scm.com/downloads) and open a new terminal."
|
|
@@ -4756,14 +4781,14 @@ async function checkGit(deps2, signal, ms) {
|
|
|
4756
4781
|
const m = /git version (\d+)\.(\d+)/.exec(res.tail.join("\n"));
|
|
4757
4782
|
return ok(m ? `git ${m[1]}.${m[2]}` : "git");
|
|
4758
4783
|
}
|
|
4759
|
-
async function checkGithubSsh(deps2, signal,
|
|
4760
|
-
const res = await timed("ssh", signal,
|
|
4784
|
+
async function checkGithubSsh(deps2, signal, ms2) {
|
|
4785
|
+
const res = await timed("ssh", signal, ms2, (s) => runTool(deps2, "ssh", SSH_TEST_ARGS, { cwd: deps2.home, env: deps2.env, onLine: noop, signal: s }));
|
|
4761
4786
|
const r = classifySshTest(res);
|
|
4762
4787
|
if (r.ok) return ok(`GitHub SSH ok (${r.user})`, { user: r.user });
|
|
4763
4788
|
return withError("warn", `GitHub SSH: ${r.error.message} (https still works)`, r.error);
|
|
4764
4789
|
}
|
|
4765
|
-
async function checkDocker(deps2, signal,
|
|
4766
|
-
return timed("docker", signal,
|
|
4790
|
+
async function checkDocker(deps2, signal, ms2) {
|
|
4791
|
+
return timed("docker", signal, ms2, async (s) => {
|
|
4767
4792
|
const o = { cwd: deps2.home, env: deps2.env, onLine: noop, signal: s };
|
|
4768
4793
|
const info = await runTool(deps2, "docker", ["info", "--format", "{{.ServerVersion}}"], o);
|
|
4769
4794
|
const infoErr = classifyDockerInfo(info, deps2.platform);
|
|
@@ -4895,16 +4920,16 @@ function runsAt(deps2, root) {
|
|
|
4895
4920
|
return () => p ??= leftoverRun(deps2, root, { prune: false }).then((r) => r ? [r] : []);
|
|
4896
4921
|
}
|
|
4897
4922
|
var UNTRUSTED = { status: "warn", label: "not checked (repository not trusted)" };
|
|
4898
|
-
async function checkRepoPm(deps2, m, recipe, signal,
|
|
4923
|
+
async function checkRepoPm(deps2, m, recipe, signal, ms2) {
|
|
4899
4924
|
if (!await isTrusted(deps2, m.root, m.id)) return UNTRUSTED;
|
|
4900
|
-
const found = await timed(recipe.pm.name, signal,
|
|
4925
|
+
const found = await timed(recipe.pm.name, signal, ms2, (s) => checkPackageManager(deps2, recipe, noop, s));
|
|
4901
4926
|
const { name, version } = recipe.pm;
|
|
4902
4927
|
if (found === null) return ok(`${name} (version unknown)`);
|
|
4903
4928
|
return ok(version ? `${name} ${found} (matches)` : `${name} ${found}`, { found, ...version ? { pinned: version } : {} });
|
|
4904
4929
|
}
|
|
4905
|
-
async function checkRepoNode(deps2, m, recipe, pkg, signal,
|
|
4930
|
+
async function checkRepoNode(deps2, m, recipe, pkg, signal, ms2) {
|
|
4906
4931
|
if (!await isTrusted(deps2, m.root, m.id)) return UNTRUSTED;
|
|
4907
|
-
const found = await timed("node", signal,
|
|
4932
|
+
const found = await timed("node", signal, ms2, (s) => checkNode(deps2, recipe, pkg, m.slug, s));
|
|
4908
4933
|
return ok(`Node ${found ?? "?"} (engines ${pkg.engines.node})`);
|
|
4909
4934
|
}
|
|
4910
4935
|
async function loggedInPortals(deps2, profiles) {
|
|
@@ -5122,7 +5147,7 @@ var maskLen = (v) => v === void 0 ? "(unset)" : `\u2022\u2022\u2022\u2022 (${v.l
|
|
|
5122
5147
|
function registerEnv(program, ctx) {
|
|
5123
5148
|
const env = program.command("env").description("Local environment");
|
|
5124
5149
|
env.command("pull").description("Write this module's local secrets into its env file").option("--module <slug|id>", "the module (default: from this repository's git remote)").option("--app <slug|id>", "the app, when the module is part of several").option("--project <ref>", "the project to pull for, from anywhere (see qore init)").option("--target <path>", "env file to write (inside the repository)").option("--use-portal", "take the portal's value for keys that differ locally").option("--yes", "trust this repository without asking").action(async (f) => {
|
|
5125
|
-
const c = await projectCtx(ctx, { ref: f.project, module: f.module, app: f.app, needModule: true });
|
|
5150
|
+
const c = await projectCtx(ctx, { ref: f.project, module: f.module, app: f.app, needModule: true, command: "env pull" });
|
|
5126
5151
|
const { deps: deps2, io: io2 } = c;
|
|
5127
5152
|
const g = c.opts();
|
|
5128
5153
|
const mc = await moduleContextFor(c, f, "env pull");
|
|
@@ -5157,7 +5182,7 @@ function registerEnv(program, ctx) {
|
|
|
5157
5182
|
choices[key] = bulk;
|
|
5158
5183
|
continue;
|
|
5159
5184
|
}
|
|
5160
|
-
const
|
|
5185
|
+
const pick2 = await io2.select(
|
|
5161
5186
|
`${key} differs (local ${maskLen(current.get(key))}, portal ${maskLen(local.env[key])})`,
|
|
5162
5187
|
[
|
|
5163
5188
|
{ name: "keep local", value: "keep" },
|
|
@@ -5166,9 +5191,9 @@ function registerEnv(program, ctx) {
|
|
|
5166
5191
|
{ name: "keep local for all", value: "skip" }
|
|
5167
5192
|
]
|
|
5168
5193
|
);
|
|
5169
|
-
if (
|
|
5170
|
-
if (
|
|
5171
|
-
choices[key] =
|
|
5194
|
+
if (pick2 === "all") bulk = "use-portal";
|
|
5195
|
+
if (pick2 === "skip") bulk = "keep";
|
|
5196
|
+
choices[key] = pick2 === "portal" || pick2 === "all" ? "use-portal" : "keep";
|
|
5172
5197
|
}
|
|
5173
5198
|
plan = planPull({ ...inputs, choices });
|
|
5174
5199
|
}
|
|
@@ -5206,12 +5231,486 @@ function registerEnv(program, ctx) {
|
|
|
5206
5231
|
});
|
|
5207
5232
|
}
|
|
5208
5233
|
|
|
5234
|
+
// src/core/observability/compose.ts
|
|
5235
|
+
function pageWarnings(...pages) {
|
|
5236
|
+
return pages.some((p) => p.ingestPaused) ? ["ingest_paused"] : [];
|
|
5237
|
+
}
|
|
5238
|
+
function scope(q) {
|
|
5239
|
+
const s = { env: q.env };
|
|
5240
|
+
if (q.since !== void 0) s.since = q.since;
|
|
5241
|
+
if (q.start !== void 0) s.start = q.start;
|
|
5242
|
+
if (q.end !== void 0) s.end = q.end;
|
|
5243
|
+
return s;
|
|
5244
|
+
}
|
|
5245
|
+
async function errorDetail(c, id, q, withs) {
|
|
5246
|
+
const w = scope(q);
|
|
5247
|
+
const markdown = await c.issueMarkdown(id, w);
|
|
5248
|
+
const event = await c.issueEvent(id, w);
|
|
5249
|
+
const out = { issue: { markdown, event }, warnings: [] };
|
|
5250
|
+
if (withs.size === 0) return out;
|
|
5251
|
+
if (!event.traceId) {
|
|
5252
|
+
out.warnings.push("no_trace_id");
|
|
5253
|
+
return out;
|
|
5254
|
+
}
|
|
5255
|
+
if (withs.has("trace")) out.trace = await c.trace(event.traceId, w);
|
|
5256
|
+
if (withs.has("logs")) {
|
|
5257
|
+
const logs = await c.logs({ ...w, trace: event.traceId, limit: 100 });
|
|
5258
|
+
out.logs = logs.items;
|
|
5259
|
+
out.warnings.push(...pageWarnings(logs));
|
|
5260
|
+
}
|
|
5261
|
+
return out;
|
|
5262
|
+
}
|
|
5263
|
+
async function traceDetail(c, id, q, withs) {
|
|
5264
|
+
const w = scope(q);
|
|
5265
|
+
const out = { trace: await c.trace(id, w), warnings: [] };
|
|
5266
|
+
const pages = [];
|
|
5267
|
+
if (withs.has("logs")) {
|
|
5268
|
+
const p = await c.logs({ ...w, trace: id, limit: 100 });
|
|
5269
|
+
out.logs = p.items;
|
|
5270
|
+
pages.push(p);
|
|
5271
|
+
}
|
|
5272
|
+
if (withs.has("errors")) {
|
|
5273
|
+
const p = await c.issues({ ...w, trace: id, status: "all" });
|
|
5274
|
+
out.errors = p.items;
|
|
5275
|
+
pages.push(p);
|
|
5276
|
+
}
|
|
5277
|
+
out.warnings.push(...pageWarnings(...pages));
|
|
5278
|
+
return out;
|
|
5279
|
+
}
|
|
5280
|
+
|
|
5281
|
+
// src/core/observability/client.ts
|
|
5282
|
+
var MAX_RETRY_WAIT_S = 30;
|
|
5283
|
+
var NOT_PROVISIONED = "observability_not_provisioned";
|
|
5284
|
+
var wait = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
5285
|
+
var ObsClient = class {
|
|
5286
|
+
constructor(api, moduleId, sleep = wait, slug = moduleId) {
|
|
5287
|
+
this.api = api;
|
|
5288
|
+
this.moduleId = moduleId;
|
|
5289
|
+
this.sleep = sleep;
|
|
5290
|
+
this.slug = slug;
|
|
5291
|
+
}
|
|
5292
|
+
api;
|
|
5293
|
+
moduleId;
|
|
5294
|
+
sleep;
|
|
5295
|
+
slug;
|
|
5296
|
+
path(resource, o, extra = {}) {
|
|
5297
|
+
const p = new URLSearchParams();
|
|
5298
|
+
const all = { env: o.env, since: o.since, start: o.start, end: o.end, q: o.q, limit: o.limit, ...extra };
|
|
5299
|
+
for (const [k, v] of Object.entries(all)) if (v !== void 0 && v !== "") p.set(k, String(v));
|
|
5300
|
+
return `/api/cli/v1/modules/${encodeURIComponent(this.moduleId)}/observability/${resource}?${p}`;
|
|
5301
|
+
}
|
|
5302
|
+
async call(fn) {
|
|
5303
|
+
try {
|
|
5304
|
+
return await this.retryOnce(fn);
|
|
5305
|
+
} catch (e) {
|
|
5306
|
+
if (e instanceof QoreError && e.code === "QORE_ACCESS_DENIED" && e.serverCode === "auth.scope.insufficient") {
|
|
5307
|
+
throw new QoreError("QORE_ACCESS_DENIED", "This login predates `qore errors/traces/logs`", {
|
|
5308
|
+
hint: "Run `qore login` again.",
|
|
5309
|
+
status: e.status,
|
|
5310
|
+
requestId: e.requestId,
|
|
5311
|
+
serverCode: e.serverCode
|
|
5312
|
+
});
|
|
5313
|
+
}
|
|
5314
|
+
if (e instanceof QoreError && e.serverCode === NOT_PROVISIONED) {
|
|
5315
|
+
throw new QoreError("QORE_RESOURCE_NOT_FOUND", `Module "${this.slug}" does not report to Sentry yet`, {
|
|
5316
|
+
hint: "Its Sentry project is not provisioned; pick another module with --module.",
|
|
5317
|
+
status: e.status,
|
|
5318
|
+
requestId: e.requestId,
|
|
5319
|
+
serverCode: e.serverCode,
|
|
5320
|
+
details: e.details
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
5323
|
+
throw e;
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
async retryOnce(fn) {
|
|
5327
|
+
try {
|
|
5328
|
+
return await fn();
|
|
5329
|
+
} catch (e) {
|
|
5330
|
+
if (!(e instanceof QoreError) || e.code !== "QORE_RATE_LIMITED") throw e;
|
|
5331
|
+
const secs2 = typeof e.details?.retryAfter === "number" ? e.details.retryAfter : 1;
|
|
5332
|
+
await this.sleep(Math.min(secs2, MAX_RETRY_WAIT_S) * 1e3);
|
|
5333
|
+
return fn();
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
issues(o) {
|
|
5337
|
+
return this.call(() => this.api.get(this.path("issues", o, { status: o.status, sort: o.sort, trace: o.trace })));
|
|
5338
|
+
}
|
|
5339
|
+
issueMarkdown(id, o) {
|
|
5340
|
+
return this.call(() => this.api.getText(this.path(`issues/${encodeURIComponent(id)}/markdown`, { env: o.env, since: o.since, start: o.start, end: o.end }, { event: "recommended" })));
|
|
5341
|
+
}
|
|
5342
|
+
issueEvent(id, o) {
|
|
5343
|
+
return this.call(() => this.api.get(this.path(`issues/${encodeURIComponent(id)}/events/recommended`, { env: o.env, since: o.since, start: o.start, end: o.end })));
|
|
5344
|
+
}
|
|
5345
|
+
logs(o) {
|
|
5346
|
+
return this.call(() => this.api.get(this.path("logs", o, { level: o.level, trace: o.trace })));
|
|
5347
|
+
}
|
|
5348
|
+
spans(o) {
|
|
5349
|
+
return this.call(() => this.api.get(this.path("spans", o, { sort: o.sort })));
|
|
5350
|
+
}
|
|
5351
|
+
trace(id, o) {
|
|
5352
|
+
return this.call(() => this.api.get(this.path(`traces/${encodeURIComponent(id)}`, { env: o.env, since: o.since, start: o.start, end: o.end })));
|
|
5353
|
+
}
|
|
5354
|
+
};
|
|
5355
|
+
|
|
5356
|
+
// src/core/observability/query.ts
|
|
5357
|
+
var ROOT_SPAN_FILTER = "is_transaction:true";
|
|
5358
|
+
var DURATION = /^\d{1,6}(ms|s|m)$/;
|
|
5359
|
+
var SINCE = /^(\d{1,4})([mhdw])$/;
|
|
5360
|
+
var SECONDS = { m: 60, h: 3600, d: 86400, w: 604800 };
|
|
5361
|
+
function parseDuration(s) {
|
|
5362
|
+
if (!DURATION.test(s)) throw new QoreError("QORE_CONTEXT_INVALID", `"${s}" is not a duration`, { hint: "Use e.g. 500ms, 1s or 2m." });
|
|
5363
|
+
return s;
|
|
5364
|
+
}
|
|
5365
|
+
function parseSince(s) {
|
|
5366
|
+
const m = SINCE.exec(s);
|
|
5367
|
+
if (!m || Number(m[1]) * SECONDS[m[2]] > 90 * 86400) {
|
|
5368
|
+
throw new QoreError("QORE_CONTEXT_INVALID", `"${s}" is not a time window`, { hint: "Use a count plus m, h, d or w, at most 90 days (e.g. 24h, 7d)." });
|
|
5369
|
+
}
|
|
5370
|
+
return s;
|
|
5371
|
+
}
|
|
5372
|
+
function joinQuery(...parts) {
|
|
5373
|
+
const q = parts.filter((p) => typeof p === "string" && p.length > 0).join(" ");
|
|
5374
|
+
return q || void 0;
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
// src/cli/observability/context.ts
|
|
5378
|
+
var ALL = { kind: "module", slug: "all", name: "All modules" };
|
|
5379
|
+
var modCandidates = (mods, withAll) => [...mods.map((m) => ({ kind: "module", id: m.id, slug: m.slug, name: m.name })), ...withAll ? [ALL] : []];
|
|
5380
|
+
function withRepo(mods) {
|
|
5381
|
+
const r = mods.filter((m) => m.repoUrl !== null);
|
|
5382
|
+
if (r.length === 0) throw new QoreError("QORE_RESOURCE_NOT_FOUND", "No modules with a repository in this app");
|
|
5383
|
+
return r;
|
|
5384
|
+
}
|
|
5385
|
+
function windowOf(f) {
|
|
5386
|
+
if (Boolean(f.start) !== Boolean(f.end)) throw new QoreError("QORE_CONTEXT_INVALID", "--start and --end go together; pass both");
|
|
5387
|
+
if (f.start || f.end) return { start: f.start, end: f.end };
|
|
5388
|
+
return { since: parseSince(f.since ?? "24h") };
|
|
5389
|
+
}
|
|
5390
|
+
var windowText = (w) => w.since ? `last ${w.since}` : `${w.start ?? "\u2026"} \u2013 ${w.end ?? "now"}`;
|
|
5391
|
+
async function chooseModule(ctx, mods, flag, single) {
|
|
5392
|
+
if (flag === "all" && !single) return mods;
|
|
5393
|
+
if (flag && flag !== "all") {
|
|
5394
|
+
const m = mods.find((x) => x.slug === flag || x.id === flag);
|
|
5395
|
+
if (m) return [m];
|
|
5396
|
+
throw new QoreError("QORE_CONTEXT_REQUIRED", `No module "${flag}" in this app`, { candidates: modCandidates(mods, !single) });
|
|
5397
|
+
}
|
|
5398
|
+
const err = new QoreError(
|
|
5399
|
+
"QORE_CONTEXT_REQUIRED",
|
|
5400
|
+
single && flag === "all" ? "A detail view needs one module" : "Which module?",
|
|
5401
|
+
{ candidates: modCandidates(mods, !single), hint: "Pass --module <slug>" + (single ? "." : " or --module all.") }
|
|
5402
|
+
);
|
|
5403
|
+
if (flag === "all") throw err;
|
|
5404
|
+
const choice = await pick(
|
|
5405
|
+
ctx,
|
|
5406
|
+
err,
|
|
5407
|
+
"Select a module",
|
|
5408
|
+
[...mods.map((m) => ({ name: m.slug, value: m.id })), ...single ? [] : [{ name: "All modules", value: "all" }]]
|
|
5409
|
+
);
|
|
5410
|
+
return choice === "all" ? mods : mods.filter((m) => m.id === choice);
|
|
5411
|
+
}
|
|
5412
|
+
async function chooseEnv(ctx, api, moduleId, appId, flag) {
|
|
5413
|
+
const st = await api.get(`/api/cli/v1/modules/${encodeURIComponent(moduleId)}/status?app_id=${encodeURIComponent(appId)}`);
|
|
5414
|
+
const names = st.environments.map((e) => e.name);
|
|
5415
|
+
if (names.length === 0) throw new QoreError("QORE_RESOURCE_NOT_FOUND", "No environments yet");
|
|
5416
|
+
const candidates = names.map((n) => ({ kind: "env", slug: n }));
|
|
5417
|
+
if (flag) {
|
|
5418
|
+
if (names.includes(flag)) return flag;
|
|
5419
|
+
throw new QoreError("QORE_CONTEXT_REQUIRED", `Unknown environment "${flag}"`, { candidates, hint: `Pass --env ${names.join("|")}.` });
|
|
5420
|
+
}
|
|
5421
|
+
const err = new QoreError("QORE_CONTEXT_REQUIRED", "Which environment?", { candidates, hint: `Pass --env ${names.join("|")}.` });
|
|
5422
|
+
return pick(ctx, err, "Select an environment", names.map((n) => ({ name: n, value: n })));
|
|
5423
|
+
}
|
|
5424
|
+
async function obsContext(ctx, flags, command, o) {
|
|
5425
|
+
const { deps: deps2, io: io2 } = ctx;
|
|
5426
|
+
const g = ctx.opts();
|
|
5427
|
+
const window = windowOf(flags);
|
|
5428
|
+
const onRequest = g.verbose ? (l) => io2.err(l) : void 0;
|
|
5429
|
+
const ws = flags.module === void 0 || flags.module === "all" ? await workspaceRoot(deps2) : null;
|
|
5430
|
+
let api, tenant, app, mods, base, apiOrigin;
|
|
5431
|
+
if (ws) {
|
|
5432
|
+
const portal = await flagPortal(deps2, g.portal, ws);
|
|
5433
|
+
apiOrigin = portal.apiOrigin;
|
|
5434
|
+
const session = await openSession(deps2, portal, command, onRequest);
|
|
5435
|
+
await ensureCapability(deps2, apiOrigin, "module.observability");
|
|
5436
|
+
await ensureCapability(deps2, apiOrigin, "app.modules");
|
|
5437
|
+
const am = await workspaceAppModules(session, portal, ws);
|
|
5438
|
+
api = session.api;
|
|
5439
|
+
({ tenant } = am.app);
|
|
5440
|
+
app = { id: am.app.id, slug: am.app.slug, name: am.app.name };
|
|
5441
|
+
mods = await chooseModule(ctx, withRepo(am.modules), flags.module, o.single);
|
|
5442
|
+
base = { portal: portal.builtin ?? portal.apiOrigin, tenant, app, source: WORKSPACE_FILE };
|
|
5443
|
+
} else {
|
|
5444
|
+
const mc = await moduleContextFor(ctx, { module: flags.module === "all" ? void 0 : flags.module }, command);
|
|
5445
|
+
apiOrigin = mc.portal.apiOrigin;
|
|
5446
|
+
await ensureCapability(deps2, apiOrigin, "module.observability");
|
|
5447
|
+
app = await appFor(ctx, mc, flags.app);
|
|
5448
|
+
api = mc.session.api;
|
|
5449
|
+
tenant = mc.module.tenant;
|
|
5450
|
+
if (flags.module === "all") {
|
|
5451
|
+
if (o.single) throw new QoreError(
|
|
5452
|
+
"QORE_CONTEXT_REQUIRED",
|
|
5453
|
+
"A detail view needs one module",
|
|
5454
|
+
{ candidates: [{ kind: "module", id: mc.module.id, slug: mc.module.slug, name: mc.module.name }] }
|
|
5455
|
+
);
|
|
5456
|
+
await ensureCapability(deps2, apiOrigin, "app.modules");
|
|
5457
|
+
const am = await api.get(`/api/cli/v1/apps/${encodeURIComponent(app.id)}/modules`);
|
|
5458
|
+
mods = withRepo(am.modules);
|
|
5459
|
+
} else {
|
|
5460
|
+
mods = [mc.module];
|
|
5461
|
+
}
|
|
5462
|
+
base = contextJson(mc, app);
|
|
5463
|
+
delete base.module;
|
|
5464
|
+
}
|
|
5465
|
+
await ensureCapability(deps2, apiOrigin, "module.status");
|
|
5466
|
+
const env = await chooseEnv(ctx, api, mods[0].id, app.id, flags.env);
|
|
5467
|
+
const fanOut2 = mods.length > 1 || flags.module === "all";
|
|
5468
|
+
const targets = mods.map((m) => ({ module: { id: m.id, slug: m.slug, name: m.name, type: m.type, access: m.access }, client: new ObsClient(api, m.id, void 0, m.slug) }));
|
|
5469
|
+
const where = fanOut2 ? "all modules" : mods[0].slug;
|
|
5470
|
+
return {
|
|
5471
|
+
targets,
|
|
5472
|
+
fanOut: fanOut2,
|
|
5473
|
+
env,
|
|
5474
|
+
window,
|
|
5475
|
+
header: `\u2192 ${tenant.name} / ${app.name} / ${where} \xB7 ${env} \xB7 ${windowText(window)}`,
|
|
5476
|
+
json: { ...base, ...fanOut2 ? { modules: targets.map((t) => t.module) } : { module: targets[0].module }, env, window }
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
|
|
5480
|
+
// src/cli/format.ts
|
|
5481
|
+
function ago(iso, now) {
|
|
5482
|
+
if (!iso) return "\u2013";
|
|
5483
|
+
const s = Math.max(0, Math.round((now.getTime() - new Date(iso).getTime()) / 1e3));
|
|
5484
|
+
if (s < 60) return "just now";
|
|
5485
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
5486
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
5487
|
+
return `${Math.floor(s / 86400)}d ago`;
|
|
5488
|
+
}
|
|
5489
|
+
var pct = (f) => String(Number((f * 100).toFixed(2)));
|
|
5490
|
+
function uptime(h) {
|
|
5491
|
+
if (!h) return "\u2013";
|
|
5492
|
+
const kind = h.cause && h.kind !== "up" ? `${h.kind}: ${h.cause}` : h.kind;
|
|
5493
|
+
return h.uptime24h === null ? kind : `${kind} (${pct(h.uptime24h)}% 24h)`;
|
|
5494
|
+
}
|
|
5495
|
+
function table(rows) {
|
|
5496
|
+
const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length)));
|
|
5497
|
+
return rows.map((r) => " " + r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i])).join(" "));
|
|
5498
|
+
}
|
|
5499
|
+
|
|
5500
|
+
// src/cli/observability/render.ts
|
|
5501
|
+
var CONTROL = /[\x00-\x08\x0b-\x1f\x7f\x80-\x9f]/g;
|
|
5502
|
+
var clean = (s) => s.replace(CONTROL, "");
|
|
5503
|
+
function fence(content) {
|
|
5504
|
+
const lines = content.map(clean);
|
|
5505
|
+
const longest = Math.max(0, ...lines.flatMap((l) => (l.match(/`+/g) ?? []).map((r) => r.length)));
|
|
5506
|
+
const ticks = "`".repeat(Math.max(3, longest + 1));
|
|
5507
|
+
return [`${ticks}untrusted-data`, ...lines, ticks];
|
|
5508
|
+
}
|
|
5509
|
+
function ms(n) {
|
|
5510
|
+
return n < 1e3 ? `${Math.round(n)}ms` : `${(n / 1e3).toFixed(1)}s`;
|
|
5511
|
+
}
|
|
5512
|
+
var oneLine = (s) => clean((s ?? "").replace(/\s+/g, " ")).trim();
|
|
5513
|
+
function issuesTable(items, now, withModule) {
|
|
5514
|
+
const head = ["ID", "LEVEL", "EVENTS", "USERS", "LAST SEEN", "TITLE"];
|
|
5515
|
+
const rows = items.map((i) => [oneLine(i.shortId), oneLine(i.level), String(i.count), String(i.userCount), ago(i.lastSeen, now), oneLine(i.title)]);
|
|
5516
|
+
return table(withModule ? [["MODULE", ...head], ...rows.map((r, n) => [oneLine(items[n].module.slug), ...r])] : [head, ...rows]);
|
|
5517
|
+
}
|
|
5518
|
+
function spansTable(items, now, withModule) {
|
|
5519
|
+
const head = ["TRACE", "DURATION", "ROOT", "WHEN"];
|
|
5520
|
+
const rows = items.map((s) => [oneLine(s.traceId), ms(s.durationMs), oneLine(s.transaction ?? s.description ?? s.name), ago(s.timestamp, now)]);
|
|
5521
|
+
return table(withModule ? [["MODULE", ...head], ...rows.map((r, n) => [oneLine(items[n].module.slug), ...r])] : [head, ...rows]);
|
|
5522
|
+
}
|
|
5523
|
+
function logLines(items, withModule) {
|
|
5524
|
+
return items.map((l) => [oneLine(l.timestamp), oneLine(l.severity).toUpperCase(), ...withModule ? [oneLine(l.module.slug)] : [], oneLine(l.message)].join(" "));
|
|
5525
|
+
}
|
|
5526
|
+
function spanTree(t) {
|
|
5527
|
+
const all = [];
|
|
5528
|
+
const walk = (n) => {
|
|
5529
|
+
all.push(n);
|
|
5530
|
+
n.children.forEach(walk);
|
|
5531
|
+
};
|
|
5532
|
+
t.roots.forEach(walk);
|
|
5533
|
+
const slowest = all.reduce((a, n) => !a || n.end - n.start > a.end - a.start ? n : a, null);
|
|
5534
|
+
const out = [];
|
|
5535
|
+
const line2 = (n, depth) => {
|
|
5536
|
+
const label3 = [oneLine(n.op), oneLine(n.description ?? n.transaction)].filter(Boolean).join(" ");
|
|
5537
|
+
const errs = n.errors.map((e) => `\u2716 ${oneLine(e.shortId ?? e.eventId)}`).join(" ");
|
|
5538
|
+
out.push([
|
|
5539
|
+
`${" ".repeat(depth)}${label3}`,
|
|
5540
|
+
ms((n.end - n.start) * 1e3),
|
|
5541
|
+
`[${oneLine(n.module.slug)}]`,
|
|
5542
|
+
...n === slowest ? ["\u25C0 slowest"] : [],
|
|
5543
|
+
...errs ? [errs] : []
|
|
5544
|
+
].join(" "));
|
|
5545
|
+
n.children.forEach((c) => line2(c, depth + 1));
|
|
5546
|
+
};
|
|
5547
|
+
t.roots.forEach((r) => line2(r, 0));
|
|
5548
|
+
return out;
|
|
5549
|
+
}
|
|
5550
|
+
var WARN = {
|
|
5551
|
+
ingest_paused: "! This tenant's Sentry quota is reached; new events are not being recorded.",
|
|
5552
|
+
no_trace_id: "! This error has no trace id, so there is no trace or trace logs to show.",
|
|
5553
|
+
sort_unavailable: "! Sorting by duration is not available here; showing the slowest of the most recent matches.",
|
|
5554
|
+
module_not_provisioned: "! Skipped, not reporting to Sentry yet:"
|
|
5555
|
+
};
|
|
5556
|
+
function warningLines(w, skipped = []) {
|
|
5557
|
+
return [...new Set(w)].map((k) => k === "module_not_provisioned" ? `${WARN[k]} ${skipped.join(", ")}` : WARN[k]);
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5560
|
+
// src/cli/observability/flags.ts
|
|
5561
|
+
function addObsFlags(cmd) {
|
|
5562
|
+
return cmd.option("--env <name>", "the environment (required; prompted when interactive)").option("--module <slug|all>", "the module (default: this repository); all = every module of the app").option("--app <slug|id>", "the app, when the module is part of several").option("--since <window>", "time window, e.g. 1h, 24h, 7d (default 24h)").option("--start <iso>", "window start (use with --end, instead of --since)").option("--end <iso>", "window end (use with --start, instead of --since)").option("--query <search>", "Sentry search syntax, appended to the filter").option("--limit <n>", "rows per module, 1-100 (default 25)");
|
|
5563
|
+
}
|
|
5564
|
+
function limitOf(s) {
|
|
5565
|
+
if (s === void 0) return 25;
|
|
5566
|
+
const n = Number(s);
|
|
5567
|
+
if (!Number.isInteger(n) || n < 1 || n > 100) throw new QoreError("QORE_CONTEXT_INVALID", `--limit must be 1-100, not "${s}"`);
|
|
5568
|
+
return n;
|
|
5569
|
+
}
|
|
5570
|
+
function withList(s, allowed) {
|
|
5571
|
+
if (!s) return /* @__PURE__ */ new Set();
|
|
5572
|
+
const parts = s.split(",").map((p) => p.trim()).filter(Boolean);
|
|
5573
|
+
const bad = parts.filter((p) => !allowed.includes(p));
|
|
5574
|
+
if (bad.length) throw new QoreError("QORE_CONTEXT_INVALID", `--with accepts ${allowed.join(", ")}; not ${bad.join(", ")}`);
|
|
5575
|
+
return new Set(parts);
|
|
5576
|
+
}
|
|
5577
|
+
function one(flag, value, allowed) {
|
|
5578
|
+
if (!allowed.includes(value)) {
|
|
5579
|
+
throw new QoreError("QORE_CONTEXT_INVALID", `${flag} must be ${allowed.join("|")}, not "${value}"`);
|
|
5580
|
+
}
|
|
5581
|
+
return value;
|
|
5582
|
+
}
|
|
5583
|
+
async function fanOut(oc2, fn) {
|
|
5584
|
+
const out = { results: [], skipped: [] };
|
|
5585
|
+
let last;
|
|
5586
|
+
for (const target of oc2.targets) {
|
|
5587
|
+
try {
|
|
5588
|
+
out.results.push({ target, result: await fn(target) });
|
|
5589
|
+
} catch (e) {
|
|
5590
|
+
if (oc2.targets.length < 2 || !(e instanceof QoreError) || e.serverCode !== NOT_PROVISIONED) throw e;
|
|
5591
|
+
out.skipped.push({ id: target.module.id, slug: target.module.slug });
|
|
5592
|
+
last = e;
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
if (out.results.length === 0 && last) {
|
|
5596
|
+
throw new QoreError("QORE_RESOURCE_NOT_FOUND", "No module of this app reports to Sentry yet", {
|
|
5597
|
+
serverCode: NOT_PROVISIONED,
|
|
5598
|
+
details: { modules: out.skipped.map((m) => m.slug) }
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
return out;
|
|
5602
|
+
}
|
|
5603
|
+
var skipWarnings = (f) => f.skipped.length ? ["module_not_provisioned"] : [];
|
|
5604
|
+
var skipJson = (f) => f.skipped.length ? { unprovisionedModules: f.skipped } : {};
|
|
5605
|
+
function printWarnings(io2, w, json2, skipped = []) {
|
|
5606
|
+
if (!json2) for (const l of warningLines(w, skipped.map((m) => m.slug))) io2.err(l);
|
|
5607
|
+
}
|
|
5608
|
+
|
|
5609
|
+
// src/cli/commands/errors.ts
|
|
5610
|
+
var WITHS = ["trace", "logs"];
|
|
5611
|
+
var STATUSES = ["unresolved", "resolved", "all"];
|
|
5612
|
+
var SORTS = ["date", "freq", "new"];
|
|
5613
|
+
var MERGE = {
|
|
5614
|
+
date: (a, b) => b.lastSeen.localeCompare(a.lastSeen),
|
|
5615
|
+
freq: (a, b) => b.count - a.count,
|
|
5616
|
+
new: (a, b) => b.firstSeen.localeCompare(a.firstSeen)
|
|
5617
|
+
};
|
|
5618
|
+
function printDetail(ctx, oc2, d, showIssue) {
|
|
5619
|
+
const { io: io2 } = ctx;
|
|
5620
|
+
printWarnings(io2, d.warnings, false);
|
|
5621
|
+
if (showIssue) for (const l of fence(d.issue.markdown.split("\n"))) io2.out(l);
|
|
5622
|
+
if (d.trace) {
|
|
5623
|
+
io2.out("");
|
|
5624
|
+
io2.out("Trace");
|
|
5625
|
+
for (const l of fence(spanTree(d.trace))) io2.out(l);
|
|
5626
|
+
}
|
|
5627
|
+
if (d.logs) {
|
|
5628
|
+
io2.out("");
|
|
5629
|
+
io2.out(`Logs (${d.logs.length})`);
|
|
5630
|
+
for (const l of fence(logLines(d.logs, oc2.fanOut))) io2.out(l);
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
async function interactiveDetail(ctx, oc2, c, id) {
|
|
5634
|
+
const first = await errorDetail(c, id, { env: oc2.env, ...oc2.window }, /* @__PURE__ */ new Set());
|
|
5635
|
+
printDetail(ctx, oc2, first, true);
|
|
5636
|
+
const traceId = first.issue.event.traceId;
|
|
5637
|
+
if (!traceId) return;
|
|
5638
|
+
const scope2 = { env: oc2.env, ...oc2.window };
|
|
5639
|
+
for (; ; ) {
|
|
5640
|
+
const next = await ctx.io.select("Next?", [
|
|
5641
|
+
{ name: `Show trace ${oneLine(traceId).slice(0, 8)}`, value: "trace" },
|
|
5642
|
+
{ name: "Logs of this trace", value: "logs" },
|
|
5643
|
+
{ name: "Done", value: "done" }
|
|
5644
|
+
]);
|
|
5645
|
+
if (next === "done") return;
|
|
5646
|
+
const extra = { issue: first.issue, warnings: [] };
|
|
5647
|
+
if (next === "trace") extra.trace = await c.trace(traceId, scope2);
|
|
5648
|
+
else {
|
|
5649
|
+
const p = await c.logs({ ...scope2, trace: traceId, limit: 100 });
|
|
5650
|
+
extra.logs = p.items;
|
|
5651
|
+
extra.warnings.push(...pageWarnings(p));
|
|
5652
|
+
}
|
|
5653
|
+
printDetail(ctx, oc2, extra, false);
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
function registerErrors(program, ctx) {
|
|
5657
|
+
addObsFlags(program.command("errors [id]").description("Errors of a deployed environment, or one error with its stack trace (requires --env)")).option("--with <list>", `with an id: also show ${WITHS.join(",")}`).option("--status <status>", `${STATUSES.join("|")} (default unresolved)`).option("--sort <sort>", `${SORTS.join("|")} (default date)`).action(async (id, flags) => {
|
|
5658
|
+
const { io: io2, deps: deps2 } = ctx;
|
|
5659
|
+
const g = ctx.opts();
|
|
5660
|
+
const withs = withList(flags.with, WITHS);
|
|
5661
|
+
const status2 = one("--status", flags.status ?? "unresolved", STATUSES);
|
|
5662
|
+
const sort = one("--sort", flags.sort ?? "date", SORTS);
|
|
5663
|
+
const limit = limitOf(flags.limit);
|
|
5664
|
+
const oc2 = await obsContext(ctx, flags, "errors", { single: id !== void 0 });
|
|
5665
|
+
const q = { env: oc2.env, ...oc2.window, q: flags.query, limit };
|
|
5666
|
+
if (id) {
|
|
5667
|
+
const c2 = oc2.targets[0].client;
|
|
5668
|
+
if (g.json) {
|
|
5669
|
+
renderJson(io2, { context: oc2.json, ...await errorDetail(c2, id, q, withs) });
|
|
5670
|
+
return;
|
|
5671
|
+
}
|
|
5672
|
+
io2.err(oc2.header);
|
|
5673
|
+
if (io2.interactive && withs.size === 0) {
|
|
5674
|
+
await interactiveDetail(ctx, oc2, c2, id);
|
|
5675
|
+
return;
|
|
5676
|
+
}
|
|
5677
|
+
printDetail(ctx, oc2, await errorDetail(c2, id, q, withs), true);
|
|
5678
|
+
return;
|
|
5679
|
+
}
|
|
5680
|
+
const f = await fanOut(oc2, (t) => t.client.issues({ ...q, status: status2, sort }));
|
|
5681
|
+
const pages = f.results.map((r) => r.result);
|
|
5682
|
+
const items = pages.flatMap((p) => p.items);
|
|
5683
|
+
if (pages.length > 1) items.sort(MERGE[sort]);
|
|
5684
|
+
const warnings = [...pageWarnings(...pages), ...skipWarnings(f)];
|
|
5685
|
+
if (g.json) {
|
|
5686
|
+
renderJson(io2, { context: oc2.json, items, warnings, ...skipJson(f) });
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5689
|
+
io2.err(oc2.header);
|
|
5690
|
+
printWarnings(io2, warnings, false, f.skipped);
|
|
5691
|
+
if (items.length === 0) {
|
|
5692
|
+
io2.out(`No ${status2 === "all" ? "" : `${status2} `}errors in ${oc2.env} (${windowText(oc2.window)}).`);
|
|
5693
|
+
return;
|
|
5694
|
+
}
|
|
5695
|
+
for (const l of fence(issuesTable(items, deps2.now(), oc2.fanOut))) io2.out(l);
|
|
5696
|
+
if (!io2.interactive) return;
|
|
5697
|
+
const picked = await io2.select(
|
|
5698
|
+
"Which error to open?",
|
|
5699
|
+
[...items.map((i) => ({ name: `${oneLine(i.shortId)} ${oneLine(i.title)}`.slice(0, 100), value: i })), { name: "(none)", value: null }]
|
|
5700
|
+
);
|
|
5701
|
+
if (!picked) return;
|
|
5702
|
+
const c = oc2.targets.find((t) => t.module.id === picked.module.id)?.client;
|
|
5703
|
+
if (!c) throw new QoreError("QORE_RESOURCE_NOT_FOUND", `module ${picked.module.slug} is not among the queried modules`);
|
|
5704
|
+
await interactiveDetail(ctx, oc2, c, picked.shortId);
|
|
5705
|
+
});
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5209
5708
|
// src/cli/commands/init.ts
|
|
5210
5709
|
import { realpath as realpath11 } from "fs/promises";
|
|
5211
5710
|
|
|
5212
5711
|
// src/core/auth/oidc.ts
|
|
5213
5712
|
import * as oc from "openid-client";
|
|
5214
|
-
var LOGIN_SCOPE = "openid profile email offline_access qore:read qore:local-env";
|
|
5713
|
+
var LOGIN_SCOPE = "openid profile email offline_access qore:read qore:local-env qore:observability";
|
|
5215
5714
|
function decodeSub(accessToken) {
|
|
5216
5715
|
const part = accessToken.split(".")[1] ?? "";
|
|
5217
5716
|
const claims = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
|
|
@@ -5262,9 +5761,9 @@ async function createOidc(opts) {
|
|
|
5262
5761
|
throw mapOidcError(e, host);
|
|
5263
5762
|
}
|
|
5264
5763
|
return {
|
|
5265
|
-
async startDevice(
|
|
5764
|
+
async startDevice(scope2) {
|
|
5266
5765
|
try {
|
|
5267
|
-
const r = await oc.initiateDeviceAuthorization(config, { scope });
|
|
5766
|
+
const r = await oc.initiateDeviceAuthorization(config, { scope: scope2 });
|
|
5268
5767
|
return {
|
|
5269
5768
|
userCode: r.user_code,
|
|
5270
5769
|
verificationUri: r.verification_uri,
|
|
@@ -5284,9 +5783,9 @@ async function createOidc(opts) {
|
|
|
5284
5783
|
throw mapOidcError(e, host);
|
|
5285
5784
|
}
|
|
5286
5785
|
},
|
|
5287
|
-
async refresh(rt,
|
|
5786
|
+
async refresh(rt, scope2) {
|
|
5288
5787
|
try {
|
|
5289
|
-
const t = await oc.refreshTokenGrant(config, rt,
|
|
5788
|
+
const t = await oc.refreshTokenGrant(config, rt, scope2 ? { scope: scope2 } : void 0);
|
|
5290
5789
|
return toTokenSet(t, rt);
|
|
5291
5790
|
} catch (e) {
|
|
5292
5791
|
throw mapOidcError(e, host);
|
|
@@ -5538,6 +6037,34 @@ function registerInit(program, ctx) {
|
|
|
5538
6037
|
});
|
|
5539
6038
|
}
|
|
5540
6039
|
|
|
6040
|
+
// src/cli/commands/logs.ts
|
|
6041
|
+
var LEVELS = ["error", "warn", "info", "debug"];
|
|
6042
|
+
function registerLogs(program, ctx) {
|
|
6043
|
+
addObsFlags(program.command("logs").description("Logs of a deployed environment (requires --env)")).option("--level <level>", `exactly this severity: ${LEVELS.join("|")}`).option("--trace <id>", "only the logs of one trace").action(async (flags) => {
|
|
6044
|
+
const { io: io2 } = ctx;
|
|
6045
|
+
const g = ctx.opts();
|
|
6046
|
+
const limit = limitOf(flags.limit);
|
|
6047
|
+
const level = flags.level === void 0 ? void 0 : one("--level", flags.level, LEVELS);
|
|
6048
|
+
const oc2 = await obsContext(ctx, flags, "logs", { single: false });
|
|
6049
|
+
const q = { env: oc2.env, ...oc2.window, q: flags.query, limit, level, trace: flags.trace };
|
|
6050
|
+
const f = await fanOut(oc2, (t) => t.client.logs(q));
|
|
6051
|
+
const pages = f.results.map((r) => r.result);
|
|
6052
|
+
const items = pages.flatMap((p) => p.items).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
6053
|
+
const warnings = [...pageWarnings(...pages), ...skipWarnings(f)];
|
|
6054
|
+
if (g.json) {
|
|
6055
|
+
renderJson(io2, { context: oc2.json, items, warnings, ...skipJson(f) });
|
|
6056
|
+
return;
|
|
6057
|
+
}
|
|
6058
|
+
io2.err(oc2.header);
|
|
6059
|
+
printWarnings(io2, warnings, false, f.skipped);
|
|
6060
|
+
if (items.length === 0) {
|
|
6061
|
+
io2.out(`No logs in ${oc2.env} (${windowText(oc2.window)}).`);
|
|
6062
|
+
return;
|
|
6063
|
+
}
|
|
6064
|
+
for (const l of fence(logLines(items, oc2.fanOut))) io2.out(l);
|
|
6065
|
+
});
|
|
6066
|
+
}
|
|
6067
|
+
|
|
5541
6068
|
// src/cli/commands/logout.ts
|
|
5542
6069
|
function registerLogout(program, ctx) {
|
|
5543
6070
|
program.command("logout").description("End the CLI session").option("--all", "log out of every portal").action(async (cmd) => {
|
|
@@ -5570,26 +6097,6 @@ function registerLogout(program, ctx) {
|
|
|
5570
6097
|
});
|
|
5571
6098
|
}
|
|
5572
6099
|
|
|
5573
|
-
// src/cli/format.ts
|
|
5574
|
-
function ago(iso, now) {
|
|
5575
|
-
if (!iso) return "\u2013";
|
|
5576
|
-
const s = Math.max(0, Math.round((now.getTime() - new Date(iso).getTime()) / 1e3));
|
|
5577
|
-
if (s < 60) return "just now";
|
|
5578
|
-
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
5579
|
-
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
5580
|
-
return `${Math.floor(s / 86400)}d ago`;
|
|
5581
|
-
}
|
|
5582
|
-
var pct = (f) => String(Number((f * 100).toFixed(2)));
|
|
5583
|
-
function uptime(h) {
|
|
5584
|
-
if (!h) return "\u2013";
|
|
5585
|
-
const kind = h.cause && h.kind !== "up" ? `${h.kind}: ${h.cause}` : h.kind;
|
|
5586
|
-
return h.uptime24h === null ? kind : `${kind} (${pct(h.uptime24h)}% 24h)`;
|
|
5587
|
-
}
|
|
5588
|
-
function table(rows) {
|
|
5589
|
-
const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length)));
|
|
5590
|
-
return rows.map((r) => " " + r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i])).join(" "));
|
|
5591
|
-
}
|
|
5592
|
-
|
|
5593
6100
|
// src/cli/commands/status.ts
|
|
5594
6101
|
var HEAD = ["ENV", "DEPLOY", "COMMIT", "DEPLOYED", "APP HEALTH"];
|
|
5595
6102
|
var envRow = (e, now) => [e.name, e.paused ? "paused" : e.status, e.commit?.sha ?? "\u2013", ago(e.commit?.deployedAt ?? null, now), uptime(e.health)];
|
|
@@ -5631,7 +6138,7 @@ async function appStatus(ctx, ws, appFlag) {
|
|
|
5631
6138
|
}
|
|
5632
6139
|
function registerStatus(program, ctx) {
|
|
5633
6140
|
program.command("status").description("Deployment and health per environment for this module (or, in a workspace root, the app)").option("--module <slug|id>", "the module (default: from this repository's git remote)").option("--app <slug|id>", "the app, when the module is part of several").option("--project <ref>", "the app or module, from anywhere (see qore init)").action(async (flags) => {
|
|
5634
|
-
const c = await projectCtx(ctx, { ref: flags.project, module: flags.module, app: flags.app, needModule: false });
|
|
6141
|
+
const c = await projectCtx(ctx, { ref: flags.project, module: flags.module, app: flags.app, needModule: false, command: "status" });
|
|
5635
6142
|
const { deps: deps2, io: io2 } = c;
|
|
5636
6143
|
const g = c.opts();
|
|
5637
6144
|
if (!flags.module) {
|
|
@@ -5717,6 +6224,109 @@ function registerSync(program, ctx) {
|
|
|
5717
6224
|
});
|
|
5718
6225
|
}
|
|
5719
6226
|
|
|
6227
|
+
// src/cli/commands/traces.ts
|
|
6228
|
+
var WITHS2 = ["logs", "errors"];
|
|
6229
|
+
async function spansOf(c, q, sort, warnings) {
|
|
6230
|
+
try {
|
|
6231
|
+
return await c.spans({ ...q, sort });
|
|
6232
|
+
} catch (e) {
|
|
6233
|
+
if (sort === "-duration" && e instanceof QoreError && e.serverCode === "observability.sort_unsupported") {
|
|
6234
|
+
warnings.push("sort_unavailable");
|
|
6235
|
+
return c.spans(q);
|
|
6236
|
+
}
|
|
6237
|
+
throw e;
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
function registerTraces(program, ctx) {
|
|
6241
|
+
addObsFlags(program.command("traces [id]").description("Slow or recent requests, or one trace as a span tree (requires --env)")).option("--slower-than <duration>", "only requests slower than this, slowest first (e.g. 500ms, 1s)").option("--with <list>", `with a trace id: also show ${WITHS2.join(",")}`).action(async (id, flags) => {
|
|
6242
|
+
const { io: io2, deps: deps2 } = ctx;
|
|
6243
|
+
const g = ctx.opts();
|
|
6244
|
+
const withs = withList(flags.with, WITHS2);
|
|
6245
|
+
const slower = flags.slowerThan === void 0 ? void 0 : parseDuration(flags.slowerThan);
|
|
6246
|
+
const limit = limitOf(flags.limit);
|
|
6247
|
+
const oc2 = await obsContext(ctx, flags, "traces", { single: id !== void 0 });
|
|
6248
|
+
const q = { env: oc2.env, ...oc2.window, limit };
|
|
6249
|
+
const scope2 = { env: oc2.env, ...oc2.window };
|
|
6250
|
+
const printLogs = (items2) => {
|
|
6251
|
+
io2.out("");
|
|
6252
|
+
io2.out(`Logs (${items2.length})`);
|
|
6253
|
+
for (const l of fence(logLines(items2, oc2.fanOut))) io2.out(l);
|
|
6254
|
+
};
|
|
6255
|
+
const printErrors = (items2) => {
|
|
6256
|
+
io2.out("");
|
|
6257
|
+
io2.out(`Errors (${items2.length})`);
|
|
6258
|
+
for (const l of fence(issuesTable(items2, deps2.now(), oc2.fanOut))) io2.out(l);
|
|
6259
|
+
};
|
|
6260
|
+
const printTree = (trace2) => {
|
|
6261
|
+
for (const l of fence(spanTree(trace2))) io2.out(l);
|
|
6262
|
+
};
|
|
6263
|
+
const more = async (c2, traceId, w) => {
|
|
6264
|
+
const f2 = { ...scope2, trace: traceId };
|
|
6265
|
+
if (w === "logs") {
|
|
6266
|
+
const p = await c2.logs({ ...f2, limit: 100 });
|
|
6267
|
+
printWarnings(io2, pageWarnings(p), false);
|
|
6268
|
+
printLogs(p.items);
|
|
6269
|
+
} else {
|
|
6270
|
+
const p = await c2.issues({ ...f2, status: "all" });
|
|
6271
|
+
printWarnings(io2, pageWarnings(p), false);
|
|
6272
|
+
printErrors(p.items);
|
|
6273
|
+
}
|
|
6274
|
+
};
|
|
6275
|
+
if (id) {
|
|
6276
|
+
const c2 = oc2.targets[0].client;
|
|
6277
|
+
const b = await traceDetail(c2, id, q, withs);
|
|
6278
|
+
if (g.json) {
|
|
6279
|
+
renderJson(io2, { context: oc2.json, ...b });
|
|
6280
|
+
return;
|
|
6281
|
+
}
|
|
6282
|
+
io2.err(oc2.header);
|
|
6283
|
+
printWarnings(io2, b.warnings, false);
|
|
6284
|
+
printTree(b.trace);
|
|
6285
|
+
if (b.logs) printLogs(b.logs);
|
|
6286
|
+
if (b.errors) printErrors(b.errors);
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
const warnings = [];
|
|
6290
|
+
const listQ = { ...q, q: joinQuery(ROOT_SPAN_FILTER, slower && `span.duration:>${slower}`, flags.query) };
|
|
6291
|
+
const sort = slower ? "-duration" : "-timestamp";
|
|
6292
|
+
const f = await fanOut(oc2, (t) => spansOf(t.client, listQ, sort, warnings));
|
|
6293
|
+
const pages = f.results.map((r) => r.result);
|
|
6294
|
+
warnings.push(...pageWarnings(...pages), ...skipWarnings(f));
|
|
6295
|
+
const items = pages.flatMap((p) => p.items).sort((a, b) => slower ? b.durationMs - a.durationMs : b.timestamp.localeCompare(a.timestamp));
|
|
6296
|
+
const uniq = [...new Set(warnings)];
|
|
6297
|
+
if (g.json) {
|
|
6298
|
+
renderJson(io2, { context: oc2.json, items, warnings: uniq, ...skipJson(f) });
|
|
6299
|
+
return;
|
|
6300
|
+
}
|
|
6301
|
+
io2.err(oc2.header);
|
|
6302
|
+
printWarnings(io2, uniq, false, f.skipped);
|
|
6303
|
+
if (items.length === 0) {
|
|
6304
|
+
io2.out(`No ${slower ? `requests slower than ${slower}` : "traces"} in ${oc2.env} (${windowText(oc2.window)}).`);
|
|
6305
|
+
return;
|
|
6306
|
+
}
|
|
6307
|
+
for (const l of fence(spansTable(items, deps2.now(), oc2.fanOut))) io2.out(l);
|
|
6308
|
+
if (!io2.interactive) return;
|
|
6309
|
+
const picked = await io2.select(
|
|
6310
|
+
"Which trace to open?",
|
|
6311
|
+
[...items.map((s) => ({ name: `${oneLine(s.traceId).slice(0, 8)} ${oneLine(s.transaction ?? s.op)}`, value: s })), { name: "(none)", value: null }]
|
|
6312
|
+
);
|
|
6313
|
+
if (!picked) return;
|
|
6314
|
+
const c = oc2.targets.find((t) => t.module.id === picked.module.id)?.client;
|
|
6315
|
+
if (!c) throw new QoreError("QORE_RESOURCE_NOT_FOUND", `module ${picked.module.slug} is not among the queried modules`);
|
|
6316
|
+
const { trace } = await traceDetail(c, picked.traceId, q, /* @__PURE__ */ new Set());
|
|
6317
|
+
printTree(trace);
|
|
6318
|
+
for (; ; ) {
|
|
6319
|
+
const next = await io2.select("Next?", [
|
|
6320
|
+
{ name: "Logs of this trace", value: "logs" },
|
|
6321
|
+
{ name: "Errors in this trace", value: "errors" },
|
|
6322
|
+
{ name: "Done", value: "done" }
|
|
6323
|
+
]);
|
|
6324
|
+
if (next === "done") return;
|
|
6325
|
+
await more(c, picked.traceId, next);
|
|
6326
|
+
}
|
|
6327
|
+
});
|
|
6328
|
+
}
|
|
6329
|
+
|
|
5720
6330
|
// src/cli/commands/version.ts
|
|
5721
6331
|
function registerVersion(program, ctx) {
|
|
5722
6332
|
program.command("version").description("Show the CLI and portal version").action(async () => {
|
|
@@ -5785,6 +6395,9 @@ var COMMANDS = [
|
|
|
5785
6395
|
registerWhoami,
|
|
5786
6396
|
registerTenant,
|
|
5787
6397
|
registerStatus,
|
|
6398
|
+
registerLogs,
|
|
6399
|
+
registerTraces,
|
|
6400
|
+
registerErrors,
|
|
5788
6401
|
registerEnv,
|
|
5789
6402
|
registerRun,
|
|
5790
6403
|
registerDev,
|
|
@@ -5844,13 +6457,13 @@ function createTelemetry(deps2, o) {
|
|
|
5844
6457
|
},
|
|
5845
6458
|
async end(signal) {
|
|
5846
6459
|
if (!sdk) return { drained: true };
|
|
5847
|
-
const
|
|
6460
|
+
const ms2 = crashed ? flushMs.crash : flushMs.clean;
|
|
5848
6461
|
const work = sdk.then(async (s) => {
|
|
5849
6462
|
if (!s) return true;
|
|
5850
6463
|
if (crashed) s.crash(crashed.e);
|
|
5851
|
-
return s.end(
|
|
6464
|
+
return s.end(ms2);
|
|
5852
6465
|
}).catch(() => false);
|
|
5853
|
-
return { drained: await withDeadline(work,
|
|
6466
|
+
return { drained: await withDeadline(work, ms2, signal) };
|
|
5854
6467
|
}
|
|
5855
6468
|
};
|
|
5856
6469
|
}
|
|
@@ -5871,7 +6484,7 @@ async function start(deps2, argv2, command, load, onScrub) {
|
|
|
5871
6484
|
sdk.start();
|
|
5872
6485
|
return sdk;
|
|
5873
6486
|
}
|
|
5874
|
-
function withDeadline(work,
|
|
6487
|
+
function withDeadline(work, ms2, signal) {
|
|
5875
6488
|
if (signal?.aborted) return Promise.resolve(false);
|
|
5876
6489
|
return new Promise((resolve6) => {
|
|
5877
6490
|
const done = (v) => {
|
|
@@ -5880,7 +6493,7 @@ function withDeadline(work, ms, signal) {
|
|
|
5880
6493
|
resolve6(v);
|
|
5881
6494
|
};
|
|
5882
6495
|
const onAbort = () => done(false);
|
|
5883
|
-
const timer = setTimeout(() => done(false),
|
|
6496
|
+
const timer = setTimeout(() => done(false), ms2);
|
|
5884
6497
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5885
6498
|
work.then(done, () => done(false));
|
|
5886
6499
|
});
|
|
@@ -6508,7 +7121,7 @@ function systemProxy(platform = process.platform) {
|
|
|
6508
7121
|
}
|
|
6509
7122
|
|
|
6510
7123
|
// src/version.ts
|
|
6511
|
-
var VERSION = true ? "0.1.
|
|
7124
|
+
var VERSION = true ? "0.1.7" : "0.0.0-dev";
|
|
6512
7125
|
|
|
6513
7126
|
// src/cli.ts
|
|
6514
7127
|
if (process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY) setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
@@ -6586,4 +7199,4 @@ if (!drained) {
|
|
|
6586
7199
|
process.exit();
|
|
6587
7200
|
}
|
|
6588
7201
|
//# sourceMappingURL=cli.js.map
|
|
6589
|
-
//# debugId=
|
|
7202
|
+
//# debugId=bf813c0e-4054-532d-9e74-62fbe8673876
|