@quatronic/qore 0.1.4 → 0.1.5
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 +32 -0
- package/dist/chunk-QYBDINUC.js +121 -0
- package/dist/cli.js +230 -24
- package/dist/sentry-B3MDCAOI.js +11262 -0
- package/package.json +3 -2
- package/dist/cli.js.map +0 -1
package/README.md
CHANGED
|
@@ -511,6 +511,38 @@ and never in an environment variable.
|
|
|
511
511
|
- `NODE_EXTRA_CA_CERTS` can point at an additional CA bundle, for portals or
|
|
512
512
|
identity providers behind a corporate proxy with a private root CA.
|
|
513
513
|
|
|
514
|
+
## Privacy
|
|
515
|
+
|
|
516
|
+
`qore` sends crash reports to Quatronic so we can find and fix bugs. It is on by
|
|
517
|
+
default; the first interactive run says so once, on stderr.
|
|
518
|
+
|
|
519
|
+
**What is sent**, to Sentry (EU region, project `qore-cli`):
|
|
520
|
+
|
|
521
|
+
- errors qore did not anticipate — a bug in qore, not an expected error such as
|
|
522
|
+
"not logged in" or "port in use";
|
|
523
|
+
- one health record per run: whether it crashed or not;
|
|
524
|
+
- with each: the qore version, the Node version, the OS, whether it ran in CI, the
|
|
525
|
+
command name (e.g. `env pull`), the stack trace, and the error message;
|
|
526
|
+
- the method, host and status code of the HTTP requests qore made before a crash.
|
|
527
|
+
|
|
528
|
+
**Removed before sending:** tokens, keys and other secrets, environment variable
|
|
529
|
+
values (including the local env qore fetched), argument values, e-mail addresses,
|
|
530
|
+
URL query strings, and your home directory (paths show as `~`). This is pattern-based:
|
|
531
|
+
an error message about a file qore failed to parse can still quote a fragment of it.
|
|
532
|
+
|
|
533
|
+
**Never attached:** your hostname, your user account, your tenant, or an IP address.
|
|
534
|
+
qore sends no field that carries an IP; the address a report arrives from is dropped
|
|
535
|
+
by the Sentry project's "Prevent Storing of IP Addresses" setting, part of its infra setup.
|
|
536
|
+
|
|
537
|
+
**Turning it off** (the first match wins):
|
|
538
|
+
|
|
539
|
+
1. `DO_NOT_TRACK=1`
|
|
540
|
+
2. `QORE_TELEMETRY=0` (or `QORE_TELEMETRY=1` to turn it on)
|
|
541
|
+
3. `qore config telemetry off` (or `on`)
|
|
542
|
+
|
|
543
|
+
`qore config telemetry` and `qore doctor` show the current state and where it
|
|
544
|
+
comes from. A build from source or a fork carries no DSN and sends nothing.
|
|
545
|
+
|
|
514
546
|
## Releasing
|
|
515
547
|
|
|
516
548
|
See [RELEASING.md](./RELEASING.md).
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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]="a8210538-dc3e-507d-ab33-bdf6a49d6d16")}catch(e){}}();
|
|
4
|
+
import { createRequire as __qoreCreateRequire } from "node:module"; const require = __qoreCreateRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// src/core/telemetry/scrub.ts
|
|
7
|
+
var SECRET_ENV_KEY = /TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|API_?KEY|ACCESS_?KEY|DSN|AUTH|CREDENTIAL|COOKIE|SESSION/i;
|
|
8
|
+
var KEEP_CONTEXTS = /* @__PURE__ */ new Set(["runtime", "os"]);
|
|
9
|
+
var PATTERNS = [
|
|
10
|
+
// an unterminated block runs to the end, so a truncated key is still dropped
|
|
11
|
+
[/-----BEGIN [A-Z0-9 ]{1,40}-----[\s\S]*?(?:-----END [A-Z0-9 ]{1,40}-----|$)/g, "[pem]"],
|
|
12
|
+
// query strings and fragments carry OAuth codes, states and signed-URL tokens
|
|
13
|
+
[/\b([a-z][a-z0-9+.-]{0,30}:\/\/[^\s?#"'<>]*)[?#][^\s"'<>]*/gi, "$1"],
|
|
14
|
+
[/\b(authorization["']?\s*[=:]\s*)(\[redacted\]|"[^"]*"|'[^']*'|[A-Za-z][A-Za-z0-9_-]{0,30}\s+[^\s,;&]+|[^\s,;&]+)/gi, "$1[redacted]"],
|
|
15
|
+
[/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]"],
|
|
16
|
+
[/\bBasic\s+[A-Za-z0-9+/=]{8,}/gi, "Basic [redacted]"],
|
|
17
|
+
[/\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]*/g, "[jwt]"],
|
|
18
|
+
[/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|sntr[ysu]_[A-Za-z0-9+/=_-]{20,}|npm_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|xox[abpr]-[A-Za-z0-9-]{10,}|[sr]k_(?:live|test)_[A-Za-z0-9]{10,}|(?:AKIA|ASIA)[0-9A-Z]{16})/g, "[token]"],
|
|
19
|
+
// URL credentials, which is also what a DSN's public key looks like
|
|
20
|
+
[/\b([a-z][a-z0-9+.-]{0,30}:\/\/)[^\s/:@]+(?::[^\s/@]*)?@/gi, "$1[redacted]@"],
|
|
21
|
+
[/[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63}){0,8}\.[A-Za-z]{2,24}\b/g, "[email]"],
|
|
22
|
+
// affixes are bounded so a long run of key-like characters cannot backtrack quadratically
|
|
23
|
+
[/\b([A-Za-z0-9_.-]{0,40}(?:(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|private[_-]?key|auth|credential|dsn|session|cookie)[A-Za-z0-9_.-]{0,40}|key))(["']?\s*[=:]\s*)(\[redacted\]|"[^"]*"|'[^']*'|[^\s&,;]+)/gi, "$1$2[redacted]"]
|
|
24
|
+
];
|
|
25
|
+
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26
|
+
var longestFirst = (xs, min) => [...new Set(xs.filter((x) => x.length >= min))].sort((a, b) => b.length - a.length);
|
|
27
|
+
function buildScrubContext(o) {
|
|
28
|
+
const words = new Set(o.commandWords);
|
|
29
|
+
const argvValues = o.argv.flatMap((a) => {
|
|
30
|
+
if (a.startsWith("-")) {
|
|
31
|
+
const i = a.indexOf("=");
|
|
32
|
+
return i > 0 ? [a.slice(i + 1)] : [];
|
|
33
|
+
}
|
|
34
|
+
return words.has(a) ? [] : [a];
|
|
35
|
+
});
|
|
36
|
+
const secretEnvValues = Object.entries(o.env).flatMap(([k, v]) => v && SECRET_ENV_KEY.test(k) ? [v] : []);
|
|
37
|
+
return { home: o.home, platform: o.platform, argvValues: longestFirst(argvValues, 3), secretEnvValues: longestFirst(secretEnvValues, 8) };
|
|
38
|
+
}
|
|
39
|
+
function addSecretValues(ctx, values) {
|
|
40
|
+
ctx.secretEnvValues = longestFirst([...ctx.secretEnvValues, ...values], 8);
|
|
41
|
+
}
|
|
42
|
+
function replaceLiteral(s, needle, repl, flags) {
|
|
43
|
+
return s.replace(new RegExp(escapeRe(needle), flags), () => repl);
|
|
44
|
+
}
|
|
45
|
+
function replaceHome(s, ctx) {
|
|
46
|
+
const win = ctx.platform === "win32";
|
|
47
|
+
const plain = [ctx.home, ...win ? [ctx.home.replace(/\\/g, "/")] : []];
|
|
48
|
+
const variants = /* @__PURE__ */ new Set([...plain, ...plain.map((h) => encodeURI(h).replace(/[?#]/g, encodeURIComponent))]);
|
|
49
|
+
let out = s;
|
|
50
|
+
for (const h of [...variants].filter((v) => v.length > 1).sort((a, b) => b.length - a.length)) {
|
|
51
|
+
out = out.replace(new RegExp(`${escapeRe(h)}(?![A-Za-z0-9._-])`, win ? "gi" : "g"), () => "~");
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function scrubText(s, ctx) {
|
|
56
|
+
let out = s;
|
|
57
|
+
for (const v of ctx.secretEnvValues) out = replaceLiteral(out, v, "[env]", "g");
|
|
58
|
+
for (const v of ctx.argvValues) out = replaceLiteral(out, v, "[arg]", "g");
|
|
59
|
+
out = replaceHome(out, ctx);
|
|
60
|
+
for (const [re, repl] of PATTERNS) out = out.replace(re, repl);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function hostOf(url) {
|
|
64
|
+
if (typeof url !== "string") return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return new URL(url).host;
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function scrubBreadcrumb(b, ctx) {
|
|
72
|
+
if (b.category === "console") return null;
|
|
73
|
+
if (b.type === "http" || b.category === "http" || b.category === "fetch") {
|
|
74
|
+
const d = b.data ?? {};
|
|
75
|
+
return {
|
|
76
|
+
type: b.type,
|
|
77
|
+
category: b.category,
|
|
78
|
+
level: b.level,
|
|
79
|
+
timestamp: b.timestamp,
|
|
80
|
+
data: { method: d.method ?? d["http.method"], host: hostOf(d.url) ?? d.host, status_code: d.status_code }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { ...b, message: b.message === void 0 ? void 0 : scrubText(b.message, ctx), data: void 0 };
|
|
84
|
+
}
|
|
85
|
+
function scrubEvent(e, ctx) {
|
|
86
|
+
delete e.server_name;
|
|
87
|
+
delete e.user;
|
|
88
|
+
delete e.request;
|
|
89
|
+
delete e.extra;
|
|
90
|
+
delete e.modules;
|
|
91
|
+
for (const k of Object.keys(e.contexts ?? {})) if (!KEEP_CONTEXTS.has(k)) delete e.contexts[k];
|
|
92
|
+
if (e.message !== void 0) e.message = scrubText(e.message, ctx);
|
|
93
|
+
if (e.logentry?.message !== void 0) {
|
|
94
|
+
e.logentry.message = scrubText(e.logentry.message, ctx);
|
|
95
|
+
delete e.logentry.params;
|
|
96
|
+
}
|
|
97
|
+
for (const v of e.exception?.values ?? []) {
|
|
98
|
+
if (v.value !== void 0) v.value = scrubText(v.value, ctx);
|
|
99
|
+
for (const f of v.stacktrace?.frames ?? []) {
|
|
100
|
+
if (f.filename) f.filename = replaceHome(f.filename, ctx);
|
|
101
|
+
if (f.abs_path) f.abs_path = replaceHome(f.abs_path, ctx);
|
|
102
|
+
if (f.module) f.module = replaceHome(f.module, ctx);
|
|
103
|
+
delete f.vars;
|
|
104
|
+
delete f.pre_context;
|
|
105
|
+
delete f.context_line;
|
|
106
|
+
delete f.post_context;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const img of e.debug_meta?.images ?? []) if (img.code_file) img.code_file = replaceHome(img.code_file, ctx);
|
|
110
|
+
if (e.breadcrumbs) e.breadcrumbs = e.breadcrumbs.map((b) => scrubBreadcrumb(b, ctx)).filter((b) => b !== null);
|
|
111
|
+
return e;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
buildScrubContext,
|
|
116
|
+
addSecretValues,
|
|
117
|
+
scrubBreadcrumb,
|
|
118
|
+
scrubEvent
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=chunk-QYBDINUC.js.map
|
|
121
|
+
//# debugId=a8210538-dc3e-507d-ab33-bdf6a49d6d16
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
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]="a70250c5-daf4-5ff3-b490-5556ac377549")}catch(e){}}();
|
|
4
|
+
import { createRequire as __qoreCreateRequire } from "node:module"; const require = __qoreCreateRequire(import.meta.url);
|
|
5
|
+
import {
|
|
6
|
+
addSecretValues,
|
|
7
|
+
buildScrubContext
|
|
8
|
+
} from "./chunk-QYBDINUC.js";
|
|
9
|
+
|
|
3
10
|
// src/cli.ts
|
|
4
11
|
import { homedir as homedir2 } from "os";
|
|
5
12
|
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
|
|
@@ -320,7 +327,7 @@ function renderDoctorJson(io2, report) {
|
|
|
320
327
|
return true;
|
|
321
328
|
}
|
|
322
329
|
var SYMBOL = { ok: "\u2713", warn: "!", fail: "\u2717" };
|
|
323
|
-
var TOOL_IDS = ["qore", "node", "git", "github-ssh", "docker"];
|
|
330
|
+
var TOOL_IDS = ["qore", "node", "git", "github-ssh", "docker", "telemetry"];
|
|
324
331
|
var ROW_WIDTH = 80;
|
|
325
332
|
var COL_B = 36;
|
|
326
333
|
function rawLine(r) {
|
|
@@ -435,7 +442,16 @@ function buildProgram(deps2, io2, signal, register, setExitCode) {
|
|
|
435
442
|
for (const r of register) r(program, ctx);
|
|
436
443
|
return program;
|
|
437
444
|
}
|
|
438
|
-
|
|
445
|
+
function commandPath(action) {
|
|
446
|
+
const names = [];
|
|
447
|
+
for (let c = action; c?.parent; c = c.parent) names.unshift(c.name());
|
|
448
|
+
return names.join(" ");
|
|
449
|
+
}
|
|
450
|
+
function isCrash(e) {
|
|
451
|
+
if (e instanceof QoreError || e instanceof CancelledError || e instanceof CommanderError) return false;
|
|
452
|
+
return !(e instanceof Error && (e.name === "CancelledError" || e.name === "QoreError"));
|
|
453
|
+
}
|
|
454
|
+
async function run(argv2, deps2, io2, signal, register, hooks = {}) {
|
|
439
455
|
const json2 = jsonFromArgv(argv2);
|
|
440
456
|
const parseIo = json2 ? { ...io2, err: () => {
|
|
441
457
|
} } : io2;
|
|
@@ -443,8 +459,12 @@ async function run(argv2, deps2, io2, signal, register) {
|
|
|
443
459
|
const program = buildProgram(deps2, parseIo, signal, register, (code) => {
|
|
444
460
|
state.exitCode = code;
|
|
445
461
|
});
|
|
446
|
-
program.hook("preAction", (_program, action) => {
|
|
462
|
+
program.hook("preAction", async (_program, action) => {
|
|
447
463
|
state.command = action.name();
|
|
464
|
+
try {
|
|
465
|
+
await hooks.onCommand?.(commandPath(action));
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
448
468
|
});
|
|
449
469
|
try {
|
|
450
470
|
await program.parseAsync(argv2, { from: "user" });
|
|
@@ -456,6 +476,12 @@ async function run(argv2, deps2, io2, signal, register) {
|
|
|
456
476
|
if (json2) io2.out(JSON.stringify({ ok: false, error: { code: "QORE_CONTEXT_INVALID", message: e.message } }));
|
|
457
477
|
return 2;
|
|
458
478
|
}
|
|
479
|
+
if (isCrash(e)) {
|
|
480
|
+
try {
|
|
481
|
+
hooks.onCrash?.(e);
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
459
485
|
const opts = program.opts();
|
|
460
486
|
renderError(io2, e, Boolean(opts.json), pc2.isColorSupported && opts.color !== false);
|
|
461
487
|
if (!opts.json && needsDoctor(e) && state.command !== "doctor") io2.err("Run qore doctor for a full check of your machine.");
|
|
@@ -1261,8 +1287,8 @@ async function readAt(dir2) {
|
|
|
1261
1287
|
async function readWorkspace(dir2) {
|
|
1262
1288
|
return readAt(dir2);
|
|
1263
1289
|
}
|
|
1264
|
-
async function findWorkspace(
|
|
1265
|
-
let dir2 = resolve(
|
|
1290
|
+
async function findWorkspace(start2, home) {
|
|
1291
|
+
let dir2 = resolve(start2);
|
|
1266
1292
|
const stop = resolve(home);
|
|
1267
1293
|
for (; ; ) {
|
|
1268
1294
|
const ws = await readAt(dir2);
|
|
@@ -1506,6 +1532,48 @@ function registerClone(program, ctx) {
|
|
|
1506
1532
|
});
|
|
1507
1533
|
}
|
|
1508
1534
|
|
|
1535
|
+
// src/core/telemetry/consent.ts
|
|
1536
|
+
var ON = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
|
|
1537
|
+
var DO_NOT_TRACK_EXEMPT = /* @__PURE__ */ new Set(["0", "false"]);
|
|
1538
|
+
var norm = (v) => (v ?? "").trim().toLowerCase();
|
|
1539
|
+
function resolveConsent(env, cfg) {
|
|
1540
|
+
const dnt = norm(env.DO_NOT_TRACK);
|
|
1541
|
+
if (dnt !== "" && !DO_NOT_TRACK_EXEMPT.has(dnt)) return { enabled: false, source: "DO_NOT_TRACK" };
|
|
1542
|
+
const q = norm(env.QORE_TELEMETRY);
|
|
1543
|
+
if (q !== "") return { enabled: ON.has(q), source: "QORE_TELEMETRY" };
|
|
1544
|
+
if (typeof cfg.telemetry?.enabled === "boolean") return { enabled: cfg.telemetry.enabled, source: "config" };
|
|
1545
|
+
return { enabled: true, source: "default" };
|
|
1546
|
+
}
|
|
1547
|
+
function needsNotice(cfg, consent) {
|
|
1548
|
+
return consent.enabled && consent.source === "default" && !cfg.telemetry?.noticeShownAt;
|
|
1549
|
+
}
|
|
1550
|
+
function describeConsent(consent, built) {
|
|
1551
|
+
const state = `crash reports ${consent.enabled ? "on" : "off"} (${consent.source})`;
|
|
1552
|
+
return consent.enabled && !built ? `${state} \xB7 this build has no DSN, nothing is sent` : state;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/cli/commands/config.ts
|
|
1556
|
+
function registerConfig(program, ctx) {
|
|
1557
|
+
const config = program.command("config").description("Change qore's own settings");
|
|
1558
|
+
config.command("telemetry").description("Show or set crash reporting").argument("[state]", "on or off").action(async (state) => {
|
|
1559
|
+
const { deps: deps2, io: io2 } = ctx;
|
|
1560
|
+
if (state !== void 0 && state !== "on" && state !== "off") {
|
|
1561
|
+
throw new QoreError("QORE_CONTEXT_INVALID", `Unknown telemetry state: ${state}`, { hint: "Use: qore config telemetry on|off" });
|
|
1562
|
+
}
|
|
1563
|
+
if (state) await deps2.config.update((c) => {
|
|
1564
|
+
c.telemetry = { ...c.telemetry, enabled: state === "on" };
|
|
1565
|
+
});
|
|
1566
|
+
const consent = resolveConsent(deps2.env, await deps2.config.read());
|
|
1567
|
+
const built = deps2.sentryDsn !== "";
|
|
1568
|
+
if (ctx.opts().json) {
|
|
1569
|
+
renderJson(io2, { telemetry: { ...consent, built } });
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
io2.out(describeConsent(consent, built));
|
|
1573
|
+
if (state && consent.source !== "config") io2.err(`${consent.source} in your environment overrides this setting.`);
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1509
1577
|
// src/cli/commands/dev.ts
|
|
1510
1578
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
1511
1579
|
import { realpath as realpath5 } from "fs/promises";
|
|
@@ -1583,8 +1651,8 @@ async function readRepoConfig(root) {
|
|
|
1583
1651
|
// src/core/context/repoRoot.ts
|
|
1584
1652
|
import { stat } from "fs/promises";
|
|
1585
1653
|
import { dirname as dirname2, join as join8, resolve as resolve3 } from "path";
|
|
1586
|
-
async function findRepoRoot(
|
|
1587
|
-
let dir2 = resolve3(
|
|
1654
|
+
async function findRepoRoot(start2) {
|
|
1655
|
+
let dir2 = resolve3(start2);
|
|
1588
1656
|
for (; ; ) {
|
|
1589
1657
|
try {
|
|
1590
1658
|
await stat(join8(dir2, ".git"));
|
|
@@ -1968,9 +2036,11 @@ import { dirname as dirname5, join as join12 } from "path";
|
|
|
1968
2036
|
async function fetchLocalEnv(deps2, mc, app) {
|
|
1969
2037
|
await ensureCapability(deps2, mc.portal.apiOrigin, "module.local-env");
|
|
1970
2038
|
try {
|
|
1971
|
-
|
|
2039
|
+
const local = await mc.session.api.get(
|
|
1972
2040
|
`/api/cli/v1/modules/${encodeURIComponent(mc.module.id)}/local-env?app_id=${encodeURIComponent(app.id)}`
|
|
1973
2041
|
);
|
|
2042
|
+
deps2.registerSecrets(Object.values(local.env));
|
|
2043
|
+
return local;
|
|
1974
2044
|
} catch (e) {
|
|
1975
2045
|
if (!(e instanceof QoreError) || e.code !== "QORE_ACCESS_DENIED") throw e;
|
|
1976
2046
|
if (e.serverCode === "app.access.denied") {
|
|
@@ -3182,8 +3252,8 @@ function planDev(modules, sel) {
|
|
|
3182
3252
|
}
|
|
3183
3253
|
const order = sccs.map((_, i) => i).sort((a, b) => minSlug[a].localeCompare(minSlug[b]));
|
|
3184
3254
|
for (const i of order) visit(i);
|
|
3185
|
-
const
|
|
3186
|
-
for (const i of orderedSccIndices)
|
|
3255
|
+
const start2 = [];
|
|
3256
|
+
for (const i of orderedSccIndices) start2.push(...sccSlugs[i]);
|
|
3187
3257
|
const waitFor = {};
|
|
3188
3258
|
for (const m of starting) {
|
|
3189
3259
|
const mySccId = sccOf.get(m.id);
|
|
@@ -3198,7 +3268,7 @@ function planDev(modules, sel) {
|
|
|
3198
3268
|
missingProviders[m.slug] = [...new Set(missing)].sort();
|
|
3199
3269
|
}
|
|
3200
3270
|
}
|
|
3201
|
-
return { start, waitFor, cycles, skipped, missingProviders };
|
|
3271
|
+
return { start: start2, waitFor, cycles, skipped, missingProviders };
|
|
3202
3272
|
}
|
|
3203
3273
|
|
|
3204
3274
|
// src/core/dev/supervisor.ts
|
|
@@ -3306,7 +3376,7 @@ function supervise(o) {
|
|
|
3306
3376
|
checkDone();
|
|
3307
3377
|
});
|
|
3308
3378
|
};
|
|
3309
|
-
const
|
|
3379
|
+
const start2 = async (m) => {
|
|
3310
3380
|
const providers = m.waitFor.filter((p) => readiness.has(p));
|
|
3311
3381
|
const results = await Promise.all(providers.map((p) => readiness.get(p).promise));
|
|
3312
3382
|
if (stopCode !== null) return;
|
|
@@ -3316,7 +3386,7 @@ function supervise(o) {
|
|
|
3316
3386
|
});
|
|
3317
3387
|
launch(m);
|
|
3318
3388
|
};
|
|
3319
|
-
for (const m of o.modules) void
|
|
3389
|
+
for (const m of o.modules) void start2(m);
|
|
3320
3390
|
checkDone();
|
|
3321
3391
|
return {
|
|
3322
3392
|
done,
|
|
@@ -4136,6 +4206,14 @@ async function prepareModule(ctx, f, waitTimeout, p) {
|
|
|
4136
4206
|
return { root, slug, recipe, env, port: dev.port, pkg, devEnv: dev.env };
|
|
4137
4207
|
}
|
|
4138
4208
|
|
|
4209
|
+
// src/cli/commands/debugTelemetry.ts
|
|
4210
|
+
function registerDebugTelemetry(program, ctx) {
|
|
4211
|
+
program.command("debug-telemetry", { hidden: true }).description("Send a test crash report").option("--crash", "throw an unexpected error").action((o) => {
|
|
4212
|
+
if (o.crash) throw new Error(`qore debug-telemetry crash in ${ctx.deps.home} token=debug-telemetry-secret`);
|
|
4213
|
+
if (ctx.opts().json) renderJson(ctx.io, { crash: false });
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4139
4217
|
// src/cli/commands/doctor.ts
|
|
4140
4218
|
import pc3 from "picocolors";
|
|
4141
4219
|
|
|
@@ -4421,6 +4499,12 @@ async function resolveRepoModule(deps2, session, portal, meta, root) {
|
|
|
4421
4499
|
}
|
|
4422
4500
|
return session.api.get(`/api/cli/v1/modules/${encodeURIComponent(ids[0])}`);
|
|
4423
4501
|
}
|
|
4502
|
+
function checkTelemetry(deps2, cfg) {
|
|
4503
|
+
const built = deps2.sentryDsn !== "";
|
|
4504
|
+
if (!cfg) return ok("crash reports off (config unreadable)", { enabled: false, source: "config", reason: "unreadable", built });
|
|
4505
|
+
const consent = resolveConsent(deps2.env, cfg);
|
|
4506
|
+
return ok(describeConsent(consent, built), { enabled: consent.enabled, source: consent.source, built });
|
|
4507
|
+
}
|
|
4424
4508
|
async function runDoctor(deps2, o = {}) {
|
|
4425
4509
|
const signal = o.signal ?? new AbortController().signal;
|
|
4426
4510
|
const toolMs = o.toolTimeoutMs ?? TOOL_TIMEOUT_MS;
|
|
@@ -4440,12 +4524,14 @@ async function runDoctor(deps2, o = {}) {
|
|
|
4440
4524
|
return r;
|
|
4441
4525
|
};
|
|
4442
4526
|
let cfg;
|
|
4527
|
+
let cfgReadable = true;
|
|
4443
4528
|
try {
|
|
4444
4529
|
cfg = await deps2.config.read();
|
|
4445
4530
|
} catch (e) {
|
|
4446
4531
|
if (isCancel(e, signal)) throw e;
|
|
4447
4532
|
await push("config", "tools", () => withError("fail", errorLabel(e), e));
|
|
4448
4533
|
cfg = { schemaVersion: 1, profiles: {} };
|
|
4534
|
+
cfgReadable = false;
|
|
4449
4535
|
}
|
|
4450
4536
|
let ws = null;
|
|
4451
4537
|
let wsError = null;
|
|
@@ -4467,6 +4553,7 @@ async function runDoctor(deps2, o = {}) {
|
|
|
4467
4553
|
await push("git", "tools", () => checkGit(deps2, signal, toolMs));
|
|
4468
4554
|
await push("github-ssh", "tools", () => checkGithubSsh(deps2, signal, toolMs));
|
|
4469
4555
|
await push("docker", "tools", () => checkDocker(deps2, signal, toolMs));
|
|
4556
|
+
await push("telemetry", "tools", () => checkTelemetry(deps2, cfgReadable ? cfg : null));
|
|
4470
4557
|
const sessions = /* @__PURE__ */ new Map();
|
|
4471
4558
|
let loggedIn = [];
|
|
4472
4559
|
try {
|
|
@@ -4745,9 +4832,9 @@ async function createOidc(opts) {
|
|
|
4745
4832
|
throw mapOidcError(e, host);
|
|
4746
4833
|
}
|
|
4747
4834
|
},
|
|
4748
|
-
async pollDevice(
|
|
4835
|
+
async pollDevice(start2, signal) {
|
|
4749
4836
|
try {
|
|
4750
|
-
const t = await oc.pollDeviceAuthorizationGrant(config,
|
|
4837
|
+
const t = await oc.pollDeviceAuthorizationGrant(config, start2.handle, void 0, { signal });
|
|
4751
4838
|
return toTokenSet(t);
|
|
4752
4839
|
} catch (e) {
|
|
4753
4840
|
throw mapOidcError(e, host);
|
|
@@ -4774,14 +4861,14 @@ async function createOidc(opts) {
|
|
|
4774
4861
|
// src/core/auth/login.ts
|
|
4775
4862
|
async function deviceLogin(opts) {
|
|
4776
4863
|
await opts.store.getRefresh();
|
|
4777
|
-
const
|
|
4864
|
+
const start2 = await opts.oidc.startDevice(LOGIN_SCOPE);
|
|
4778
4865
|
const stopPoll = new AbortController();
|
|
4779
4866
|
const settled = new AbortController();
|
|
4780
|
-
const poll = opts.oidc.pollDevice(
|
|
4867
|
+
const poll = opts.oidc.pollDevice(start2, AbortSignal.any([opts.signal, stopPoll.signal])).finally(() => settled.abort());
|
|
4781
4868
|
poll.catch(() => {
|
|
4782
4869
|
});
|
|
4783
4870
|
try {
|
|
4784
|
-
await opts.onCode(
|
|
4871
|
+
await opts.onCode(start2, settled.signal);
|
|
4785
4872
|
} catch (e) {
|
|
4786
4873
|
stopPoll.abort();
|
|
4787
4874
|
const late = await poll.catch(() => null);
|
|
@@ -5179,8 +5266,110 @@ var COMMANDS = [
|
|
|
5179
5266
|
registerClone,
|
|
5180
5267
|
registerSync,
|
|
5181
5268
|
registerVersion,
|
|
5182
|
-
|
|
5269
|
+
registerConfig,
|
|
5270
|
+
registerDoctor,
|
|
5271
|
+
registerDebugTelemetry
|
|
5272
|
+
];
|
|
5273
|
+
|
|
5274
|
+
// src/cli/telemetry/notice.ts
|
|
5275
|
+
var NOTICE = [
|
|
5276
|
+
"qore sends anonymous crash reports and a per-run health record to Quatronic, to find and fix bugs.",
|
|
5277
|
+
"Tokens, env values and your home path are removed; no user or tenant is attached.",
|
|
5278
|
+
"Turn it off with `qore config telemetry off`, QORE_TELEMETRY=0 or DO_NOT_TRACK=1. See the Privacy section of the README."
|
|
5183
5279
|
];
|
|
5280
|
+
async function showNoticeOnce(deps2, io2, o) {
|
|
5281
|
+
if (o.json || !io2.interactive || !deps2.sentryDsn) return;
|
|
5282
|
+
try {
|
|
5283
|
+
const cfg = await deps2.config.read();
|
|
5284
|
+
if (!needsNotice(cfg, resolveConsent(deps2.env, cfg))) return;
|
|
5285
|
+
await deps2.config.update((c) => {
|
|
5286
|
+
c.telemetry = { ...c.telemetry, noticeShownAt: deps2.now().toISOString() };
|
|
5287
|
+
});
|
|
5288
|
+
} catch {
|
|
5289
|
+
return;
|
|
5290
|
+
}
|
|
5291
|
+
for (const line2 of NOTICE) io2.err(line2);
|
|
5292
|
+
}
|
|
5293
|
+
|
|
5294
|
+
// src/cli/telemetry/index.ts
|
|
5295
|
+
var CRASH_FLUSH_MS = 2e3;
|
|
5296
|
+
var CLEAN_FLUSH_MS = 200;
|
|
5297
|
+
var loadSentry = async () => (await import("./sentry-B3MDCAOI.js")).createSdk;
|
|
5298
|
+
function createTelemetry(deps2, o) {
|
|
5299
|
+
const load = o.load ?? loadSentry;
|
|
5300
|
+
const flushMs = o.flushMs ?? { crash: CRASH_FLUSH_MS, clean: CLEAN_FLUSH_MS };
|
|
5301
|
+
let sdk = null;
|
|
5302
|
+
let crashed = null;
|
|
5303
|
+
const secrets = [];
|
|
5304
|
+
let scrub = null;
|
|
5305
|
+
return {
|
|
5306
|
+
begin(command) {
|
|
5307
|
+
if (sdk || !deps2.sentryDsn) return;
|
|
5308
|
+
sdk = start(deps2, o.argv, command, load, (ctx) => {
|
|
5309
|
+
scrub = ctx;
|
|
5310
|
+
addSecretValues(ctx, secrets);
|
|
5311
|
+
}).catch(() => null);
|
|
5312
|
+
},
|
|
5313
|
+
crash(e) {
|
|
5314
|
+
crashed ??= { e };
|
|
5315
|
+
},
|
|
5316
|
+
registerSecrets(values) {
|
|
5317
|
+
secrets.push(...values);
|
|
5318
|
+
if (scrub) addSecretValues(scrub, values);
|
|
5319
|
+
},
|
|
5320
|
+
async end(signal) {
|
|
5321
|
+
if (!sdk) return { drained: true };
|
|
5322
|
+
const ms = crashed ? flushMs.crash : flushMs.clean;
|
|
5323
|
+
const work = sdk.then(async (s) => {
|
|
5324
|
+
if (!s) return true;
|
|
5325
|
+
if (crashed) s.crash(crashed.e);
|
|
5326
|
+
return s.end(ms);
|
|
5327
|
+
}).catch(() => false);
|
|
5328
|
+
return { drained: await withDeadline(work, ms, signal) };
|
|
5329
|
+
}
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
5332
|
+
async function start(deps2, argv2, command, load, onScrub) {
|
|
5333
|
+
const cfg = await deps2.config.read().catch(() => null);
|
|
5334
|
+
if (!cfg) return null;
|
|
5335
|
+
if (!resolveConsent(deps2.env, cfg).enabled) return null;
|
|
5336
|
+
const create = await load();
|
|
5337
|
+
const scrub = buildScrubContext({ home: deps2.home, platform: deps2.platform, env: deps2.env, argv: argv2, commandWords: command.split(" ") });
|
|
5338
|
+
onScrub(scrub);
|
|
5339
|
+
const sdk = create({
|
|
5340
|
+
dsn: deps2.sentryDsn,
|
|
5341
|
+
environment: deps2.sentryEnvironment,
|
|
5342
|
+
release: deps2.version,
|
|
5343
|
+
tags: { command, "cli.version": deps2.version, "node.version": deps2.nodeVersion, os: deps2.platform, ci: deps2.env.CI ? "true" : "false" },
|
|
5344
|
+
scrub
|
|
5345
|
+
});
|
|
5346
|
+
sdk.start();
|
|
5347
|
+
return sdk;
|
|
5348
|
+
}
|
|
5349
|
+
function withDeadline(work, ms, signal) {
|
|
5350
|
+
if (signal?.aborted) return Promise.resolve(false);
|
|
5351
|
+
return new Promise((resolve5) => {
|
|
5352
|
+
const done = (v) => {
|
|
5353
|
+
clearTimeout(timer);
|
|
5354
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5355
|
+
resolve5(v);
|
|
5356
|
+
};
|
|
5357
|
+
const onAbort = () => done(false);
|
|
5358
|
+
const timer = setTimeout(() => done(false), ms);
|
|
5359
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5360
|
+
work.then(done, () => done(false));
|
|
5361
|
+
});
|
|
5362
|
+
}
|
|
5363
|
+
function telemetryHooks(deps2, io2, t, o) {
|
|
5364
|
+
return {
|
|
5365
|
+
onCommand: async (command) => {
|
|
5366
|
+
if (command === "config telemetry") return;
|
|
5367
|
+
await showNoticeOnce(deps2, io2, o);
|
|
5368
|
+
t.begin(command);
|
|
5369
|
+
},
|
|
5370
|
+
onCrash: (e) => t.crash(e)
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5184
5373
|
|
|
5185
5374
|
// src/cli/updateCheck.ts
|
|
5186
5375
|
var REGISTRY_URL = "https://registry.npmjs.org/@quatronic%2Fqore/latest";
|
|
@@ -5728,6 +5917,10 @@ var systemPorts = {
|
|
|
5728
5917
|
}
|
|
5729
5918
|
};
|
|
5730
5919
|
|
|
5920
|
+
// src/buildInfo.ts
|
|
5921
|
+
var SENTRY_DSN = true ? "https://50b592efb53dbc1a31cb10d3945c6d52@o4512129906049024.ingest.de.sentry.io/4512174341750864" : "";
|
|
5922
|
+
var SENTRY_ENVIRONMENT = true ? "production" : "development";
|
|
5923
|
+
|
|
5731
5924
|
// src/platform/proxy.ts
|
|
5732
5925
|
import { execFile as execFile3 } from "child_process";
|
|
5733
5926
|
|
|
@@ -5790,7 +5983,7 @@ function systemProxy(platform = process.platform) {
|
|
|
5790
5983
|
}
|
|
5791
5984
|
|
|
5792
5985
|
// src/version.ts
|
|
5793
|
-
var VERSION = true ? "0.1.
|
|
5986
|
+
var VERSION = true ? "0.1.5" : "0.0.0-dev";
|
|
5794
5987
|
|
|
5795
5988
|
// src/cli.ts
|
|
5796
5989
|
if (process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY) setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
@@ -5833,6 +6026,11 @@ var deps = {
|
|
|
5833
6026
|
process.kill(process.pid, signal);
|
|
5834
6027
|
},
|
|
5835
6028
|
pid: process.pid,
|
|
6029
|
+
registerSecrets: (values) => {
|
|
6030
|
+
telemetry.registerSecrets(values);
|
|
6031
|
+
},
|
|
6032
|
+
sentryDsn: SENTRY_DSN,
|
|
6033
|
+
sentryEnvironment: SENTRY_ENVIRONMENT,
|
|
5836
6034
|
systemProxy: () => systemProxy(process.platform)
|
|
5837
6035
|
};
|
|
5838
6036
|
var termination = preflightTermination(() => ac.abort(), deps.raise);
|
|
@@ -5843,16 +6041,24 @@ var io = processIo(isInteractive({
|
|
|
5843
6041
|
noInput: preSeparatorArgv.includes("--no-input"),
|
|
5844
6042
|
env: process.env
|
|
5845
6043
|
}));
|
|
5846
|
-
|
|
5847
|
-
await termination.reRaise(async () => {
|
|
6044
|
+
var flushStd = async () => {
|
|
5848
6045
|
await Promise.all([process.stdout, process.stderr].map((s) => new Promise((r) => {
|
|
5849
6046
|
s.write("", () => r());
|
|
5850
6047
|
})));
|
|
5851
|
-
}
|
|
6048
|
+
};
|
|
6049
|
+
var telemetry = createTelemetry(deps, { argv });
|
|
6050
|
+
process.exitCode = await run(argv, deps, io, ac.signal, COMMANDS, telemetryHooks(deps, io, telemetry, { json }));
|
|
6051
|
+
var { drained } = await telemetry.end(ac.signal);
|
|
6052
|
+
await termination.reRaise(flushStd);
|
|
5852
6053
|
if (!ac.signal.aborted) {
|
|
5853
6054
|
try {
|
|
5854
6055
|
await maybeNotifyUpdate(deps, io, { json });
|
|
5855
6056
|
} catch {
|
|
5856
6057
|
}
|
|
5857
6058
|
}
|
|
5858
|
-
|
|
6059
|
+
if (!drained) {
|
|
6060
|
+
await flushStd();
|
|
6061
|
+
process.exit();
|
|
6062
|
+
}
|
|
6063
|
+
//# sourceMappingURL=cli.js.map
|
|
6064
|
+
//# debugId=a70250c5-daf4-5ff3-b490-5556ac377549
|