@quatronic/qore 0.1.3 → 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 +52 -0
- package/dist/chunk-QYBDINUC.js +121 -0
- package/dist/cli.js +851 -206
- package/dist/sentry-B3MDCAOI.js +11262 -0
- package/package.json +3 -2
- package/dist/cli.js.map +0 -1
package/README.md
CHANGED
|
@@ -428,6 +428,26 @@ qore dev --kill-others # the first module to exit stops the rest
|
|
|
428
428
|
module that would start is already trusted and has an env file on disk;
|
|
429
429
|
one exception fails the whole run with the original error.
|
|
430
430
|
|
|
431
|
+
**Feedback.** Before anything starts, a module not yet cloned is offered as
|
|
432
|
+
`Clone <module>, <module> now with qore sync? [Y/n]` (a TTY only, never
|
|
433
|
+
with `--json`/`--no-input`; `--yes` does not accept it). A module sync
|
|
434
|
+
cannot clone (a conflicting directory, no repository yet, an unsupported
|
|
435
|
+
repository URL, GitHub not linked, no repository access) gets its own
|
|
436
|
+
warning naming the real next step, and no prompt. While preflight runs, one progress line per
|
|
437
|
+
module per step (package manager, install, env, ports) is printed as it
|
|
438
|
+
completes. Once every module with a port is either listening or has been
|
|
439
|
+
reported as not running, a one-line summary follows: `✔ All modules
|
|
440
|
+
running:` (or `✔ Running:` / `! Not running:` when some were skipped) lists
|
|
441
|
+
each module with its URL or its reason. A module that crashes while others
|
|
442
|
+
keep running gets a restart hint
|
|
443
|
+
(`Restart it on its own: qore dev --only <module>`, in another terminal;
|
|
444
|
+
the others keep running); on shutdown, a final
|
|
445
|
+
line lists what stopped cleanly and what crashed, with how long it ran.
|
|
446
|
+
When the failure is about the developer's machine rather than the app
|
|
447
|
+
(Docker, Node version, a port busy with something other than a previous
|
|
448
|
+
`qore dev`, a package manager), the error is followed by a pointer to
|
|
449
|
+
`qore doctor` for a full check.
|
|
450
|
+
|
|
431
451
|
**Output.** Every line of a dev server's own stdout/stderr is prefixed per
|
|
432
452
|
module and column-aligned:
|
|
433
453
|
|
|
@@ -491,6 +511,38 @@ and never in an environment variable.
|
|
|
491
511
|
- `NODE_EXTRA_CA_CERTS` can point at an additional CA bundle, for portals or
|
|
492
512
|
identity providers behind a corporate proxy with a private root CA.
|
|
493
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
|
+
|
|
494
546
|
## Releasing
|
|
495
547
|
|
|
496
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
|