brakit 0.10.0 → 0.10.2
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/dist/api.d.ts +47 -19
- package/dist/api.js +127 -31
- package/dist/bin/brakit.js +425 -60
- package/dist/dashboard-client.global.js +524 -465
- package/dist/dashboard.html +623 -513
- package/dist/mcp/server.js +175 -22
- package/dist/runtime/index.js +1758 -1458
- package/package.json +1 -1
package/dist/runtime/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/constants/config.ts
|
|
18
|
-
var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH, MAX_INGEST_BYTES,
|
|
18
|
+
var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH, MAX_INGEST_BYTES, SENSITIVE_MASK_MIN_LENGTH, SENSITIVE_MASK_VISIBLE_CHARS, MAX_JSON_BODY_BYTES, ANALYSIS_DEBOUNCE_MS, ISSUE_ID_HASH_LENGTH, ISSUES_DATA_VERSION, SENSITIVE_MASK_PLACEHOLDER, PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_API_LIMIT, MAX_OBJECT_SCAN_DEPTH, MAX_UNIQUE_ENDPOINTS, MAX_ACCUMULATOR_ENTRIES, ISSUE_PRUNE_TTL_MS, FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS, INSIGHT_WINDOW_PER_ENDPOINT, CLEAN_HITS_FOR_RESOLUTION, STALE_ISSUE_TTL_MS, STRICT_MODE_MAX_GAP_MS, BASELINE_MIN_SESSIONS, BASELINE_MIN_REQUESTS_PER_SESSION, BASELINE_PENDING_POINTS_MIN, METRICS_DIR, METRICS_FILE, PORT_FILE, ISSUES_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS, ISSUES_FLUSH_INTERVAL_MS, SSE_HEARTBEAT_INTERVAL_MS, NOISE_HOSTS, NOISE_PATH_PATTERNS, VALID_ISSUE_STATES, VALID_ISSUE_CATEGORIES, VALID_AI_FIX_STATUSES, DETAIL_PREVIEW_LENGTH;
|
|
19
19
|
var init_config = __esm({
|
|
20
20
|
"src/constants/config.ts"() {
|
|
21
21
|
"use strict";
|
|
@@ -25,7 +25,6 @@ var init_config = __esm({
|
|
|
25
25
|
MAX_TELEMETRY_ENTRIES = 1e3;
|
|
26
26
|
MAX_TAB_NAME_LENGTH = 32;
|
|
27
27
|
MAX_INGEST_BYTES = 10485760;
|
|
28
|
-
TERMINAL_TRUNCATE_LENGTH = 80;
|
|
29
28
|
SENSITIVE_MASK_MIN_LENGTH = 8;
|
|
30
29
|
SENSITIVE_MASK_VISIBLE_CHARS = 4;
|
|
31
30
|
MAX_JSON_BODY_BYTES = 65536;
|
|
@@ -90,36 +89,117 @@ var init_config = __esm({
|
|
|
90
89
|
VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
91
90
|
VALID_ISSUE_CATEGORIES = /* @__PURE__ */ new Set(["security", "performance", "reliability"]);
|
|
92
91
|
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
93
|
-
TELEMETRY_EVENT_SETUP_COMPLETED = "setup_completed";
|
|
94
|
-
TELEMETRY_EVENT_FIRST_REQUEST = "first_request";
|
|
95
|
-
TELEMETRY_EVENT_DASHBOARD_VIEWED = "dashboard_viewed";
|
|
96
|
-
TELEMETRY_EVENT_SESSION = "session";
|
|
97
|
-
TELEMETRY_EVENT_GRAPH_FEATURE = "graph_feature";
|
|
98
|
-
EXIT_REASON_CLEAN = "clean";
|
|
99
|
-
EXIT_REASON_SIGINT = "sigint";
|
|
100
|
-
EXIT_REASON_SIGTERM = "sigterm";
|
|
101
92
|
DETAIL_PREVIEW_LENGTH = 120;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/constants/detection.ts
|
|
97
|
+
var NODE_MODULES_SEGMENT, INSTALLED_HOOKS, KNOWN_DEPENDENCY_NAMES, KNOWN_CONFIG_FILES, KNOWN_DEPENDENCY_SET;
|
|
98
|
+
var init_detection = __esm({
|
|
99
|
+
"src/constants/detection.ts"() {
|
|
100
|
+
"use strict";
|
|
101
|
+
NODE_MODULES_SEGMENT = "/node_modules/";
|
|
102
|
+
INSTALLED_HOOKS = ["fetch", "console", "error"];
|
|
102
103
|
KNOWN_DEPENDENCY_NAMES = [
|
|
104
|
+
// -- Frameworks (meta) --
|
|
103
105
|
"next",
|
|
104
106
|
"@remix-run/dev",
|
|
105
107
|
"nuxt",
|
|
106
|
-
"vite",
|
|
107
108
|
"astro",
|
|
109
|
+
// -- Frameworks (backend) --
|
|
110
|
+
"@nestjs/core",
|
|
111
|
+
"@adonisjs/core",
|
|
112
|
+
"sails",
|
|
108
113
|
"express",
|
|
109
114
|
"fastify",
|
|
110
115
|
"hono",
|
|
111
116
|
"koa",
|
|
112
|
-
"
|
|
117
|
+
"@hapi/hapi",
|
|
118
|
+
"elysia",
|
|
119
|
+
"h3",
|
|
120
|
+
"nitro",
|
|
121
|
+
"@trpc/server",
|
|
122
|
+
// -- Bundlers --
|
|
123
|
+
"vite",
|
|
124
|
+
// -- ORM / query builders --
|
|
113
125
|
"prisma",
|
|
126
|
+
"@prisma/client",
|
|
114
127
|
"drizzle-orm",
|
|
115
128
|
"typeorm",
|
|
116
|
-
"sequelize"
|
|
129
|
+
"sequelize",
|
|
130
|
+
"mongoose",
|
|
131
|
+
"kysely",
|
|
132
|
+
"knex",
|
|
133
|
+
"@mikro-orm/core",
|
|
134
|
+
"objection",
|
|
135
|
+
// -- DB drivers --
|
|
136
|
+
"pg",
|
|
137
|
+
"mysql2",
|
|
138
|
+
"mongodb",
|
|
139
|
+
"better-sqlite3",
|
|
140
|
+
"@libsql/client",
|
|
141
|
+
"@planetscale/database",
|
|
142
|
+
"ioredis",
|
|
143
|
+
"redis",
|
|
144
|
+
// -- Auth --
|
|
145
|
+
"lucia",
|
|
146
|
+
"next-auth",
|
|
147
|
+
"@auth/core",
|
|
148
|
+
"passport",
|
|
149
|
+
// -- Queues / messaging --
|
|
150
|
+
"bullmq",
|
|
151
|
+
"amqplib",
|
|
152
|
+
"kafkajs",
|
|
153
|
+
// -- Validation --
|
|
154
|
+
"zod",
|
|
155
|
+
"joi",
|
|
156
|
+
"yup",
|
|
157
|
+
"arktype",
|
|
158
|
+
"valibot",
|
|
159
|
+
// -- HTTP clients --
|
|
160
|
+
"axios",
|
|
161
|
+
"got",
|
|
162
|
+
"ky",
|
|
163
|
+
"undici",
|
|
164
|
+
// -- Realtime --
|
|
165
|
+
"socket.io",
|
|
166
|
+
"ws",
|
|
167
|
+
// -- CSS / styling --
|
|
168
|
+
"tailwindcss",
|
|
169
|
+
// -- Testing --
|
|
170
|
+
"vitest",
|
|
171
|
+
"jest",
|
|
172
|
+
"mocha",
|
|
173
|
+
// -- Runtime indicators --
|
|
174
|
+
"bun-types",
|
|
175
|
+
"@types/bun"
|
|
117
176
|
];
|
|
177
|
+
KNOWN_CONFIG_FILES = {
|
|
178
|
+
"next.config.js": "nextjs",
|
|
179
|
+
"next.config.mjs": "nextjs",
|
|
180
|
+
"next.config.ts": "nextjs",
|
|
181
|
+
"nuxt.config.ts": "nuxt",
|
|
182
|
+
"nuxt.config.js": "nuxt",
|
|
183
|
+
"astro.config.mjs": "astro",
|
|
184
|
+
"astro.config.ts": "astro",
|
|
185
|
+
"vite.config.ts": "vite",
|
|
186
|
+
"vite.config.js": "vite",
|
|
187
|
+
"drizzle.config.ts": "drizzle-orm",
|
|
188
|
+
"drizzle.config.js": "drizzle-orm",
|
|
189
|
+
"prisma/schema.prisma": "prisma",
|
|
190
|
+
"knexfile.js": "knex",
|
|
191
|
+
"knexfile.ts": "knex",
|
|
192
|
+
"mikro-orm.config.ts": "@mikro-orm/core",
|
|
193
|
+
"nest-cli.json": "@nestjs/core",
|
|
194
|
+
"tailwind.config.js": "tailwindcss",
|
|
195
|
+
"tailwind.config.ts": "tailwindcss"
|
|
196
|
+
};
|
|
197
|
+
KNOWN_DEPENDENCY_SET = new Set(KNOWN_DEPENDENCY_NAMES);
|
|
118
198
|
}
|
|
119
199
|
});
|
|
120
200
|
|
|
121
201
|
// src/constants/labels.ts
|
|
122
|
-
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, DASHBOARD_API_GRAPH, VALID_TABS_TUPLE, VALID_TABS, BRAKIT_REQUEST_ID_HEADER, BRAKIT_FETCH_ID_HEADER, SENSITIVE_HEADER_NAMES, HTTP_OK, HTTP_NO_CONTENT, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_PAYLOAD_TOO_LARGE, HTTP_INTERNAL_ERROR, SECURITY_HEADERS, CONTENT_ENCODING_GZIP, CONTENT_ENCODING_BR, CONTENT_ENCODING_DEFLATE,
|
|
202
|
+
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, DASHBOARD_API_GRAPH, VALID_TABS_TUPLE, VALID_TABS, BRAKIT_REQUEST_ID_HEADER, BRAKIT_FETCH_ID_HEADER, SENSITIVE_HEADER_NAMES, HTTP_OK, HTTP_NO_CONTENT, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_PAYLOAD_TOO_LARGE, HTTP_INTERNAL_ERROR, SECURITY_HEADERS, CONTENT_ENCODING_GZIP, CONTENT_ENCODING_BR, CONTENT_ENCODING_DEFLATE, SSE_EVENT_FETCH, SSE_EVENT_LOG, SSE_EVENT_ERROR, SSE_EVENT_QUERY, SSE_EVENT_ISSUES, SDK_EVENT_REQUEST, SDK_EVENT_DB_QUERY, SDK_EVENT_FETCH, SDK_EVENT_LOG, SDK_EVENT_ERROR, SDK_EVENT_AUTH_CHECK, SDK_EVENT_HELLO, TIMELINE_FETCH, TIMELINE_LOG, TIMELINE_ERROR, TIMELINE_QUERY, UNICODE_ARROW, UNICODE_EM_DASH, UNICODE_CHECK_MARK;
|
|
123
203
|
var init_labels = __esm({
|
|
124
204
|
"src/constants/labels.ts"() {
|
|
125
205
|
"use strict";
|
|
@@ -145,14 +225,10 @@ var init_labels = __esm({
|
|
|
145
225
|
VALID_TABS_TUPLE = [
|
|
146
226
|
"overview",
|
|
147
227
|
"actions",
|
|
148
|
-
"
|
|
149
|
-
"fetches",
|
|
150
|
-
"queries",
|
|
151
|
-
"errors",
|
|
152
|
-
"logs",
|
|
228
|
+
"insights",
|
|
153
229
|
"performance",
|
|
154
|
-
"
|
|
155
|
-
"
|
|
230
|
+
"graph",
|
|
231
|
+
"explorer"
|
|
156
232
|
];
|
|
157
233
|
VALID_TABS = new Set(VALID_TABS_TUPLE);
|
|
158
234
|
BRAKIT_REQUEST_ID_HEADER = "x-brakit-request-id";
|
|
@@ -181,11 +257,6 @@ var init_labels = __esm({
|
|
|
181
257
|
CONTENT_ENCODING_GZIP = "gzip";
|
|
182
258
|
CONTENT_ENCODING_BR = "br";
|
|
183
259
|
CONTENT_ENCODING_DEFLATE = "deflate";
|
|
184
|
-
SEVERITY_ICON = {
|
|
185
|
-
critical: "\u2717",
|
|
186
|
-
warning: "\u26A0",
|
|
187
|
-
info: "\u2139"
|
|
188
|
-
};
|
|
189
260
|
SSE_EVENT_FETCH = "fetch";
|
|
190
261
|
SSE_EVENT_LOG = "log";
|
|
191
262
|
SSE_EVENT_ERROR = "error_event";
|
|
@@ -197,14 +268,14 @@ var init_labels = __esm({
|
|
|
197
268
|
SDK_EVENT_LOG = "log";
|
|
198
269
|
SDK_EVENT_ERROR = "error";
|
|
199
270
|
SDK_EVENT_AUTH_CHECK = "auth.check";
|
|
200
|
-
|
|
201
|
-
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
202
|
-
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
203
|
-
SPEED_BUCKET_THRESHOLDS = [200, 500, 1e3, 2e3, 5e3];
|
|
271
|
+
SDK_EVENT_HELLO = "sdk.hello";
|
|
204
272
|
TIMELINE_FETCH = "fetch";
|
|
205
273
|
TIMELINE_LOG = "log";
|
|
206
274
|
TIMELINE_ERROR = "error";
|
|
207
275
|
TIMELINE_QUERY = "query";
|
|
276
|
+
UNICODE_ARROW = "\u2192";
|
|
277
|
+
UNICODE_EM_DASH = "\u2014";
|
|
278
|
+
UNICODE_CHECK_MARK = "\u2713";
|
|
208
279
|
}
|
|
209
280
|
});
|
|
210
281
|
|
|
@@ -244,13 +315,36 @@ var init_features = __esm({
|
|
|
244
315
|
}
|
|
245
316
|
});
|
|
246
317
|
|
|
318
|
+
// src/constants/telemetry.ts
|
|
319
|
+
var POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS, TELEMETRY_EVENT_SETUP_COMPLETED, TELEMETRY_EVENT_FIRST_REQUEST, TELEMETRY_EVENT_DASHBOARD_VIEWED, TELEMETRY_EVENT_SESSION, TELEMETRY_EVENT_GRAPH_FEATURE, TELEMETRY_SDK_NAME, EXIT_REASON_CLEAN, EXIT_REASON_SIGINT, EXIT_REASON_SIGTERM, SPEED_BUCKET_THRESHOLDS;
|
|
320
|
+
var init_telemetry = __esm({
|
|
321
|
+
"src/constants/telemetry.ts"() {
|
|
322
|
+
"use strict";
|
|
323
|
+
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
324
|
+
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
325
|
+
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
326
|
+
TELEMETRY_EVENT_SETUP_COMPLETED = "setup_completed";
|
|
327
|
+
TELEMETRY_EVENT_FIRST_REQUEST = "first_request";
|
|
328
|
+
TELEMETRY_EVENT_DASHBOARD_VIEWED = "dashboard_viewed";
|
|
329
|
+
TELEMETRY_EVENT_SESSION = "session";
|
|
330
|
+
TELEMETRY_EVENT_GRAPH_FEATURE = "graph_feature";
|
|
331
|
+
TELEMETRY_SDK_NAME = "node";
|
|
332
|
+
EXIT_REASON_CLEAN = "clean";
|
|
333
|
+
EXIT_REASON_SIGINT = "sigint";
|
|
334
|
+
EXIT_REASON_SIGTERM = "sigterm";
|
|
335
|
+
SPEED_BUCKET_THRESHOLDS = [200, 500, 1e3, 2e3, 5e3];
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
247
339
|
// src/constants/index.ts
|
|
248
340
|
var init_constants = __esm({
|
|
249
341
|
"src/constants/index.ts"() {
|
|
250
342
|
"use strict";
|
|
251
343
|
init_config();
|
|
344
|
+
init_detection();
|
|
252
345
|
init_labels();
|
|
253
346
|
init_features();
|
|
347
|
+
init_telemetry();
|
|
254
348
|
}
|
|
255
349
|
});
|
|
256
350
|
|
|
@@ -339,6 +433,214 @@ var init_fetch = __esm({
|
|
|
339
433
|
}
|
|
340
434
|
});
|
|
341
435
|
|
|
436
|
+
// src/utils/log.ts
|
|
437
|
+
function brakitWarn(message) {
|
|
438
|
+
process.stderr.write(`${PREFIX} ${message}
|
|
439
|
+
`);
|
|
440
|
+
}
|
|
441
|
+
function brakitDebug(message) {
|
|
442
|
+
if (process.env.DEBUG_BRAKIT) {
|
|
443
|
+
process.stderr.write(`${PREFIX}:debug ${message}
|
|
444
|
+
`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
var PREFIX;
|
|
448
|
+
var init_log = __esm({
|
|
449
|
+
"src/utils/log.ts"() {
|
|
450
|
+
"use strict";
|
|
451
|
+
PREFIX = "[brakit]";
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// src/utils/type-guards.ts
|
|
456
|
+
function isString(val) {
|
|
457
|
+
return typeof val === "string";
|
|
458
|
+
}
|
|
459
|
+
function isNumber(val) {
|
|
460
|
+
return typeof val === "number" && !isNaN(val);
|
|
461
|
+
}
|
|
462
|
+
function isBoolean(val) {
|
|
463
|
+
return typeof val === "boolean";
|
|
464
|
+
}
|
|
465
|
+
function isThenable(value) {
|
|
466
|
+
return value != null && typeof value.then === "function";
|
|
467
|
+
}
|
|
468
|
+
function getErrorMessage(err) {
|
|
469
|
+
if (err instanceof Error) return err.message;
|
|
470
|
+
if (typeof err === "string") return err;
|
|
471
|
+
return String(err);
|
|
472
|
+
}
|
|
473
|
+
function isValidIssueState(val) {
|
|
474
|
+
return typeof val === "string" && VALID_ISSUE_STATES.has(val);
|
|
475
|
+
}
|
|
476
|
+
function isValidIssueCategory(val) {
|
|
477
|
+
return typeof val === "string" && VALID_ISSUE_CATEGORIES.has(val);
|
|
478
|
+
}
|
|
479
|
+
function isValidAiFixStatus(val) {
|
|
480
|
+
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
481
|
+
}
|
|
482
|
+
function validateIssuesData(parsed) {
|
|
483
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
484
|
+
const obj = parsed;
|
|
485
|
+
if (obj.version === ISSUES_DATA_VERSION && Array.isArray(obj.issues)) {
|
|
486
|
+
return parsed;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
function validateMetricsData(parsed) {
|
|
491
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
492
|
+
const obj = parsed;
|
|
493
|
+
if (obj.version === 1 && Array.isArray(obj.endpoints)) {
|
|
494
|
+
return parsed;
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
var init_type_guards = __esm({
|
|
499
|
+
"src/utils/type-guards.ts"() {
|
|
500
|
+
"use strict";
|
|
501
|
+
init_config();
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// src/runtime/capture.ts
|
|
506
|
+
import { gunzip, brotliDecompress, inflate } from "zlib";
|
|
507
|
+
function outgoingToIncoming(headers2) {
|
|
508
|
+
const result = {};
|
|
509
|
+
for (const [key, value] of Object.entries(headers2)) {
|
|
510
|
+
if (value === void 0) continue;
|
|
511
|
+
if (Array.isArray(value)) {
|
|
512
|
+
result[key] = value.map(String);
|
|
513
|
+
} else {
|
|
514
|
+
result[key] = String(value);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
function getDecompressor(encoding) {
|
|
520
|
+
if (encoding === CONTENT_ENCODING_GZIP) return gunzip;
|
|
521
|
+
if (encoding === CONTENT_ENCODING_BR) return brotliDecompress;
|
|
522
|
+
if (encoding === CONTENT_ENCODING_DEFLATE) return inflate;
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
function decompressAsync(body, encoding) {
|
|
526
|
+
const decompressor = getDecompressor(encoding);
|
|
527
|
+
if (!decompressor) return Promise.resolve(body);
|
|
528
|
+
return new Promise((resolve6) => {
|
|
529
|
+
decompressor(body, (err, result) => {
|
|
530
|
+
resolve6(err ? body : result);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
function toBuffer(chunk) {
|
|
535
|
+
if (Buffer.isBuffer(chunk)) return chunk;
|
|
536
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
537
|
+
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
function drainPendingCaptures() {
|
|
541
|
+
if (pendingCaptures === 0) return Promise.resolve();
|
|
542
|
+
return new Promise((resolve6) => {
|
|
543
|
+
drainResolvers.push(resolve6);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function onCaptureSettled() {
|
|
547
|
+
pendingCaptures--;
|
|
548
|
+
if (pendingCaptures === 0 && drainResolvers.length > 0) {
|
|
549
|
+
const resolvers = drainResolvers;
|
|
550
|
+
drainResolvers = [];
|
|
551
|
+
for (const resolve6 of resolvers) resolve6();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function captureInProcess(req, res, requestId, requestStore, isChild = false) {
|
|
555
|
+
const startTime = performance.now();
|
|
556
|
+
const method = req.method ?? "GET";
|
|
557
|
+
const resChunks = [];
|
|
558
|
+
let resSize = 0;
|
|
559
|
+
const originalWrite = res.write;
|
|
560
|
+
const originalEnd = res.end;
|
|
561
|
+
let truncated = false;
|
|
562
|
+
res.write = function(...args) {
|
|
563
|
+
try {
|
|
564
|
+
const chunk = args[0];
|
|
565
|
+
if (chunk != null && typeof chunk !== "function") {
|
|
566
|
+
if (resSize < DEFAULT_MAX_BODY_CAPTURE) {
|
|
567
|
+
const buf = toBuffer(chunk);
|
|
568
|
+
if (buf) {
|
|
569
|
+
resChunks.push(buf);
|
|
570
|
+
resSize += buf.length;
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
truncated = true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
} catch (e) {
|
|
577
|
+
brakitDebug(`capture write: ${getErrorMessage(e)}`);
|
|
578
|
+
}
|
|
579
|
+
return originalWrite.apply(this, args);
|
|
580
|
+
};
|
|
581
|
+
res.end = function(...args) {
|
|
582
|
+
try {
|
|
583
|
+
const chunk = typeof args[0] !== "function" ? args[0] : void 0;
|
|
584
|
+
if (chunk != null && resSize < DEFAULT_MAX_BODY_CAPTURE) {
|
|
585
|
+
const buf = toBuffer(chunk);
|
|
586
|
+
if (buf) {
|
|
587
|
+
resChunks.push(buf);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch (e) {
|
|
591
|
+
brakitDebug(`capture end: ${getErrorMessage(e)}`);
|
|
592
|
+
}
|
|
593
|
+
const result = originalEnd.apply(this, args);
|
|
594
|
+
const endTime = performance.now();
|
|
595
|
+
const encoding = String(res.getHeader("content-encoding") ?? "").toLowerCase();
|
|
596
|
+
const statusCode = res.statusCode;
|
|
597
|
+
const responseHeaders = outgoingToIncoming(res.getHeaders());
|
|
598
|
+
const responseContentType = String(res.getHeader("content-type") ?? "");
|
|
599
|
+
const capturedChunks = resChunks.slice();
|
|
600
|
+
if (!isChild) {
|
|
601
|
+
pendingCaptures++;
|
|
602
|
+
void (async () => {
|
|
603
|
+
try {
|
|
604
|
+
let body = capturedChunks.length > 0 ? Buffer.concat(capturedChunks) : null;
|
|
605
|
+
if (body && encoding && !truncated) {
|
|
606
|
+
body = await decompressAsync(body, encoding);
|
|
607
|
+
}
|
|
608
|
+
requestStore.capture({
|
|
609
|
+
requestId,
|
|
610
|
+
method,
|
|
611
|
+
url: req.url ?? "/",
|
|
612
|
+
requestHeaders: req.headers,
|
|
613
|
+
requestBody: null,
|
|
614
|
+
statusCode,
|
|
615
|
+
responseHeaders,
|
|
616
|
+
responseBody: body,
|
|
617
|
+
responseContentType,
|
|
618
|
+
startTime,
|
|
619
|
+
endTime,
|
|
620
|
+
config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
|
|
621
|
+
});
|
|
622
|
+
} catch (e) {
|
|
623
|
+
brakitDebug(`capture store: ${getErrorMessage(e)}`);
|
|
624
|
+
} finally {
|
|
625
|
+
onCaptureSettled();
|
|
626
|
+
}
|
|
627
|
+
})();
|
|
628
|
+
}
|
|
629
|
+
return result;
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
var pendingCaptures, drainResolvers;
|
|
633
|
+
var init_capture = __esm({
|
|
634
|
+
"src/runtime/capture.ts"() {
|
|
635
|
+
"use strict";
|
|
636
|
+
init_constants();
|
|
637
|
+
init_log();
|
|
638
|
+
init_type_guards();
|
|
639
|
+
pendingCaptures = 0;
|
|
640
|
+
drainResolvers = [];
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
342
644
|
// src/instrument/hooks/console.ts
|
|
343
645
|
import { format } from "util";
|
|
344
646
|
function setupConsoleHook(emit) {
|
|
@@ -539,92 +841,23 @@ var init_normalize = __esm({
|
|
|
539
841
|
}
|
|
540
842
|
});
|
|
541
843
|
|
|
542
|
-
// src/
|
|
543
|
-
|
|
544
|
-
|
|
844
|
+
// src/instrument/adapters/shared.ts
|
|
845
|
+
import { createRequire } from "module";
|
|
846
|
+
function tryRequire(id) {
|
|
847
|
+
try {
|
|
848
|
+
return appRequire(id);
|
|
849
|
+
} catch {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
545
852
|
}
|
|
546
|
-
function
|
|
547
|
-
return
|
|
853
|
+
function getActiveRequestId() {
|
|
854
|
+
return getRequestContext()?.requestId ?? null;
|
|
548
855
|
}
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return
|
|
554
|
-
}
|
|
555
|
-
function getErrorMessage(err) {
|
|
556
|
-
if (err instanceof Error) return err.message;
|
|
557
|
-
if (typeof err === "string") return err;
|
|
558
|
-
return String(err);
|
|
559
|
-
}
|
|
560
|
-
function isValidIssueState(val) {
|
|
561
|
-
return typeof val === "string" && VALID_ISSUE_STATES.has(val);
|
|
562
|
-
}
|
|
563
|
-
function isValidIssueCategory(val) {
|
|
564
|
-
return typeof val === "string" && VALID_ISSUE_CATEGORIES.has(val);
|
|
565
|
-
}
|
|
566
|
-
function isValidAiFixStatus(val) {
|
|
567
|
-
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
568
|
-
}
|
|
569
|
-
function validateIssuesData(parsed) {
|
|
570
|
-
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
571
|
-
const obj = parsed;
|
|
572
|
-
if (obj.version === ISSUES_DATA_VERSION && Array.isArray(obj.issues)) {
|
|
573
|
-
return parsed;
|
|
574
|
-
}
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
577
|
-
function validateMetricsData(parsed) {
|
|
578
|
-
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
579
|
-
const obj = parsed;
|
|
580
|
-
if (obj.version === 1 && Array.isArray(obj.endpoints)) {
|
|
581
|
-
return parsed;
|
|
582
|
-
}
|
|
583
|
-
return null;
|
|
584
|
-
}
|
|
585
|
-
var init_type_guards = __esm({
|
|
586
|
-
"src/utils/type-guards.ts"() {
|
|
587
|
-
"use strict";
|
|
588
|
-
init_config();
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// src/utils/log.ts
|
|
593
|
-
function brakitWarn(message) {
|
|
594
|
-
process.stderr.write(`${PREFIX} ${message}
|
|
595
|
-
`);
|
|
596
|
-
}
|
|
597
|
-
function brakitDebug(message) {
|
|
598
|
-
if (process.env.DEBUG_BRAKIT) {
|
|
599
|
-
process.stderr.write(`${PREFIX}:debug ${message}
|
|
600
|
-
`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
var PREFIX;
|
|
604
|
-
var init_log = __esm({
|
|
605
|
-
"src/utils/log.ts"() {
|
|
606
|
-
"use strict";
|
|
607
|
-
PREFIX = "[brakit]";
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
// src/instrument/adapters/shared.ts
|
|
612
|
-
import { createRequire } from "module";
|
|
613
|
-
function tryRequire(id) {
|
|
614
|
-
try {
|
|
615
|
-
return appRequire(id);
|
|
616
|
-
} catch {
|
|
617
|
-
return null;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
function getActiveRequestId() {
|
|
621
|
-
return getRequestContext()?.requestId ?? null;
|
|
622
|
-
}
|
|
623
|
-
function getPrototype(lib, className) {
|
|
624
|
-
const defaultExport = lib.default;
|
|
625
|
-
const cls = defaultExport?.[className] ?? lib[className];
|
|
626
|
-
if (!cls || typeof cls !== "function") return null;
|
|
627
|
-
return cls.prototype ?? null;
|
|
856
|
+
function getPrototype(lib, className) {
|
|
857
|
+
const defaultExport = lib.default;
|
|
858
|
+
const cls = defaultExport?.[className] ?? lib[className];
|
|
859
|
+
if (!cls || typeof cls !== "function") return null;
|
|
860
|
+
return cls.prototype ?? null;
|
|
628
861
|
}
|
|
629
862
|
function buildQueryEvent(config, sql, op, table, start, requestId, rowCount) {
|
|
630
863
|
return {
|
|
@@ -1834,554 +2067,219 @@ var init_sdk_event_parser = __esm({
|
|
|
1834
2067
|
}
|
|
1835
2068
|
});
|
|
1836
2069
|
|
|
1837
|
-
// src/
|
|
1838
|
-
|
|
1839
|
-
|
|
2070
|
+
// src/telemetry/config.ts
|
|
2071
|
+
import { homedir, platform } from "os";
|
|
2072
|
+
import { join } from "path";
|
|
2073
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2074
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
2075
|
+
function isValidTelemetryConfig(value) {
|
|
2076
|
+
return typeof value === "object" && value !== null && typeof value.telemetry === "boolean" && typeof value.anonymousId === "string" && value.anonymousId.length > 0;
|
|
1840
2077
|
}
|
|
1841
|
-
function
|
|
1842
|
-
|
|
2078
|
+
function readConfig() {
|
|
2079
|
+
try {
|
|
2080
|
+
if (!existsSync(CONFIG_PATH)) return null;
|
|
2081
|
+
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
2082
|
+
return isValidTelemetryConfig(parsed) ? parsed : null;
|
|
2083
|
+
} catch {
|
|
2084
|
+
return null;
|
|
2085
|
+
}
|
|
1843
2086
|
}
|
|
1844
|
-
function
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
services.queryStore.add(event.data);
|
|
1858
|
-
break;
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1861
|
-
const queryStore = services.queryStore;
|
|
1862
|
-
const fetchStore = services.fetchStore;
|
|
1863
|
-
const logStore = services.logStore;
|
|
1864
|
-
const errorStore = services.errorStore;
|
|
1865
|
-
const requestStore = services.requestStore;
|
|
1866
|
-
const stores = {
|
|
1867
|
-
addQuery: (data) => queryStore.add(data),
|
|
1868
|
-
addFetch: (data) => fetchStore.add(data),
|
|
1869
|
-
addLog: (data) => logStore.add(data),
|
|
1870
|
-
addError: (data) => errorStore.add(data),
|
|
1871
|
-
addRequest: (data) => requestStore.add(data)
|
|
1872
|
-
};
|
|
1873
|
-
return (req, res) => {
|
|
1874
|
-
if (req.method !== "POST") {
|
|
1875
|
-
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
1876
|
-
return;
|
|
2087
|
+
function writeConfig(config) {
|
|
2088
|
+
try {
|
|
2089
|
+
if (!existsSync(CONFIG_DIR))
|
|
2090
|
+
mkdirSync(CONFIG_DIR, { recursive: true, ...IS_WINDOWS ? {} : { mode: DIR_MODE_OWNER_ONLY } });
|
|
2091
|
+
writeFileSync(
|
|
2092
|
+
CONFIG_PATH,
|
|
2093
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
2094
|
+
IS_WINDOWS ? {} : { mode: FILE_MODE_OWNER_ONLY }
|
|
2095
|
+
);
|
|
2096
|
+
} catch (err) {
|
|
2097
|
+
if (process.env.BRAKIT_DEBUG) {
|
|
2098
|
+
process.stderr.write(`[brakit] config write failed: ${err?.message ?? err}
|
|
2099
|
+
`);
|
|
1877
2100
|
}
|
|
1878
|
-
|
|
1879
|
-
let totalSize = 0;
|
|
1880
|
-
req.on("data", (chunk) => {
|
|
1881
|
-
totalSize += chunk.length;
|
|
1882
|
-
if (totalSize > MAX_INGEST_BYTES) {
|
|
1883
|
-
sendJson(req, res, HTTP_PAYLOAD_TOO_LARGE, { error: "Payload too large" });
|
|
1884
|
-
req.destroy();
|
|
1885
|
-
return;
|
|
1886
|
-
}
|
|
1887
|
-
chunks.push(chunk);
|
|
1888
|
-
});
|
|
1889
|
-
req.on("end", () => {
|
|
1890
|
-
if (res.headersSent) return;
|
|
1891
|
-
try {
|
|
1892
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
1893
|
-
if (isSDKPayload(body)) {
|
|
1894
|
-
for (const event of body.events) {
|
|
1895
|
-
routeSDKEvent(event, stores);
|
|
1896
|
-
}
|
|
1897
|
-
res.writeHead(HTTP_NO_CONTENT);
|
|
1898
|
-
res.end();
|
|
1899
|
-
return;
|
|
1900
|
-
}
|
|
1901
|
-
if (isBrakitBatch(body)) {
|
|
1902
|
-
for (const event of body.events) {
|
|
1903
|
-
routeEvent(event);
|
|
1904
|
-
}
|
|
1905
|
-
res.writeHead(HTTP_NO_CONTENT);
|
|
1906
|
-
res.end();
|
|
1907
|
-
return;
|
|
1908
|
-
}
|
|
1909
|
-
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid batch" });
|
|
1910
|
-
} catch {
|
|
1911
|
-
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid JSON" });
|
|
1912
|
-
}
|
|
1913
|
-
});
|
|
1914
|
-
req.on("error", () => {
|
|
1915
|
-
if (!res.headersSent) {
|
|
1916
|
-
res.writeHead(HTTP_BAD_REQUEST);
|
|
1917
|
-
res.end();
|
|
1918
|
-
}
|
|
1919
|
-
});
|
|
1920
|
-
};
|
|
2101
|
+
}
|
|
1921
2102
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
2103
|
+
function getOrCreateConfig() {
|
|
2104
|
+
const existing = readConfig();
|
|
2105
|
+
if (existing) return existing;
|
|
2106
|
+
const config = { telemetry: true, anonymousId: randomUUID5() };
|
|
2107
|
+
writeConfig(config);
|
|
2108
|
+
return config;
|
|
2109
|
+
}
|
|
2110
|
+
function isTelemetryEnabled() {
|
|
2111
|
+
if (cachedEnabled !== null) return cachedEnabled;
|
|
2112
|
+
const env = process.env.BRAKIT_TELEMETRY;
|
|
2113
|
+
if (env !== void 0) {
|
|
2114
|
+
const normalized = env.toLowerCase().trim();
|
|
2115
|
+
cachedEnabled = normalized !== "false" && normalized !== "0" && normalized !== "off";
|
|
2116
|
+
return cachedEnabled;
|
|
2117
|
+
}
|
|
2118
|
+
cachedEnabled = readConfig()?.telemetry ?? true;
|
|
2119
|
+
return cachedEnabled;
|
|
2120
|
+
}
|
|
2121
|
+
var IS_WINDOWS, CONFIG_DIR, CONFIG_PATH, cachedEnabled;
|
|
2122
|
+
var init_config2 = __esm({
|
|
2123
|
+
"src/telemetry/config.ts"() {
|
|
1924
2124
|
"use strict";
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2125
|
+
init_features();
|
|
2126
|
+
IS_WINDOWS = platform() === "win32";
|
|
2127
|
+
CONFIG_DIR = join(homedir(), ".brakit");
|
|
2128
|
+
CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
2129
|
+
cachedEnabled = null;
|
|
1929
2130
|
}
|
|
1930
2131
|
});
|
|
1931
2132
|
|
|
1932
|
-
// src/
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2133
|
+
// src/utils/fs.ts
|
|
2134
|
+
import { access, readFile, writeFile } from "fs/promises";
|
|
2135
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2136
|
+
import { createHash } from "crypto";
|
|
2137
|
+
import { homedir as homedir2 } from "os";
|
|
2138
|
+
import { resolve, join as join2 } from "path";
|
|
2139
|
+
function getProjectDataDir(projectRoot) {
|
|
2140
|
+
const absolute = resolve(projectRoot);
|
|
2141
|
+
const hash = createHash("sha256").update(absolute).digest("hex").slice(0, PROJECT_HASH_LENGTH);
|
|
2142
|
+
return join2(homedir2(), ".brakit", "projects", hash);
|
|
2143
|
+
}
|
|
2144
|
+
async function fileExists(path) {
|
|
2145
|
+
try {
|
|
2146
|
+
await access(path);
|
|
2147
|
+
return true;
|
|
2148
|
+
} catch {
|
|
2149
|
+
return false;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
function ensureGitignore(dir, entry) {
|
|
2153
|
+
try {
|
|
2154
|
+
const gitignorePath = resolve(dir, "../.gitignore");
|
|
2155
|
+
if (existsSync2(gitignorePath)) {
|
|
2156
|
+
const content = readFileSync2(gitignorePath, "utf-8");
|
|
2157
|
+
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
2158
|
+
writeFileSync2(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
2159
|
+
} else {
|
|
2160
|
+
writeFileSync2(gitignorePath, entry + "\n");
|
|
1942
2161
|
}
|
|
1943
|
-
|
|
1944
|
-
|
|
2162
|
+
} catch (err) {
|
|
2163
|
+
brakitDebug(`ensureGitignore failed: ${getErrorMessage(err)}`);
|
|
2164
|
+
}
|
|
1945
2165
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
2166
|
+
async function ensureGitignoreAsync(dir, entry) {
|
|
2167
|
+
try {
|
|
2168
|
+
const gitignorePath = resolve(dir, "../.gitignore");
|
|
2169
|
+
if (await fileExists(gitignorePath)) {
|
|
2170
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
2171
|
+
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
2172
|
+
await writeFile(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
2173
|
+
} else {
|
|
2174
|
+
await writeFile(gitignorePath, entry + "\n");
|
|
2175
|
+
}
|
|
2176
|
+
} catch (err) {
|
|
2177
|
+
brakitDebug(`ensureGitignoreAsync failed: ${getErrorMessage(err)}`);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
var init_fs = __esm({
|
|
2181
|
+
"src/utils/fs.ts"() {
|
|
1948
2182
|
"use strict";
|
|
1949
|
-
|
|
1950
|
-
|
|
2183
|
+
init_config();
|
|
2184
|
+
init_log();
|
|
2185
|
+
init_type_guards();
|
|
1951
2186
|
}
|
|
1952
2187
|
});
|
|
1953
2188
|
|
|
1954
|
-
// src/
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
|
|
2189
|
+
// src/utils/atomic-writer.ts
|
|
2190
|
+
import {
|
|
2191
|
+
writeFileSync as writeFileSync3,
|
|
2192
|
+
existsSync as existsSync3,
|
|
2193
|
+
mkdirSync as mkdirSync3,
|
|
2194
|
+
renameSync
|
|
2195
|
+
} from "fs";
|
|
2196
|
+
import { writeFile as writeFile2, mkdir, rename } from "fs/promises";
|
|
2197
|
+
var AtomicWriter;
|
|
2198
|
+
var init_atomic_writer = __esm({
|
|
2199
|
+
"src/utils/atomic-writer.ts"() {
|
|
1963
2200
|
"use strict";
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
const queries = services.queryStore.getByRequest(requestId);
|
|
1974
|
-
const timeline = [];
|
|
1975
|
-
for (const fetch of fetches)
|
|
1976
|
-
timeline.push({ type: TIMELINE_FETCH, timestamp: fetch.timestamp, data: fetch });
|
|
1977
|
-
for (const log of logs)
|
|
1978
|
-
timeline.push({ type: TIMELINE_LOG, timestamp: log.timestamp, data: log });
|
|
1979
|
-
for (const error of errors)
|
|
1980
|
-
timeline.push({ type: TIMELINE_ERROR, timestamp: error.timestamp, data: error });
|
|
1981
|
-
for (const query of queries)
|
|
1982
|
-
timeline.push({ type: TIMELINE_QUERY, timestamp: query.timestamp, data: query });
|
|
1983
|
-
timeline.sort((a, b) => a.timestamp - b.timestamp);
|
|
1984
|
-
return {
|
|
1985
|
-
total: timeline.length,
|
|
1986
|
-
timeline,
|
|
1987
|
-
counts: {
|
|
1988
|
-
fetches: fetches.length,
|
|
1989
|
-
logs: logs.length,
|
|
1990
|
-
errors: errors.length,
|
|
1991
|
-
queries: queries.length
|
|
1992
|
-
}
|
|
1993
|
-
};
|
|
1994
|
-
}
|
|
1995
|
-
function createActivityHandler(services) {
|
|
1996
|
-
return (req, res) => {
|
|
1997
|
-
if (!requireGet(req, res)) return;
|
|
1998
|
-
try {
|
|
1999
|
-
const url = parseRequestUrl(req);
|
|
2000
|
-
const requestId = url.searchParams.get("requestId");
|
|
2001
|
-
const requestIds = url.searchParams.get("requestIds");
|
|
2002
|
-
if (!requestId && !requestIds) {
|
|
2003
|
-
sendJson(req, res, HTTP_BAD_REQUEST, { error: "requestId or requestIds parameter required" });
|
|
2004
|
-
return;
|
|
2201
|
+
init_fs();
|
|
2202
|
+
init_log();
|
|
2203
|
+
init_type_guards();
|
|
2204
|
+
AtomicWriter = class {
|
|
2205
|
+
constructor(opts) {
|
|
2206
|
+
this.opts = opts;
|
|
2207
|
+
this.writing = false;
|
|
2208
|
+
this.pendingContent = null;
|
|
2209
|
+
this.tmpPath = opts.filePath + ".tmp";
|
|
2005
2210
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2211
|
+
writeSync(content) {
|
|
2212
|
+
try {
|
|
2213
|
+
this.ensureDir();
|
|
2214
|
+
writeFileSync3(this.tmpPath, content);
|
|
2215
|
+
renameSync(this.tmpPath, this.opts.filePath);
|
|
2216
|
+
} catch (err) {
|
|
2217
|
+
brakitWarn(`failed to save ${this.opts.label}: ${getErrorMessage(err)}`);
|
|
2218
|
+
}
|
|
2010
2219
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2220
|
+
async writeAsync(content) {
|
|
2221
|
+
if (this.writing) {
|
|
2222
|
+
this.pendingContent = content;
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
this.writing = true;
|
|
2226
|
+
try {
|
|
2227
|
+
await this.ensureDirAsync();
|
|
2228
|
+
await writeFile2(this.tmpPath, content);
|
|
2229
|
+
await rename(this.tmpPath, this.opts.filePath);
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
brakitWarn(`failed to save ${this.opts.label}: ${getErrorMessage(err)}`);
|
|
2232
|
+
} finally {
|
|
2233
|
+
this.writing = false;
|
|
2234
|
+
if (this.pendingContent !== null) {
|
|
2235
|
+
const next = this.pendingContent;
|
|
2236
|
+
this.pendingContent = null;
|
|
2237
|
+
this.writeAsync(next).catch(() => {
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2015
2241
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2242
|
+
ensureDir() {
|
|
2243
|
+
if (!existsSync3(this.opts.dir)) {
|
|
2244
|
+
mkdirSync3(this.opts.dir, { recursive: true });
|
|
2245
|
+
if (this.opts.gitignoreEntry) {
|
|
2246
|
+
ensureGitignore(this.opts.dir, this.opts.gitignoreEntry);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2021
2249
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
init_log();
|
|
2032
|
-
MAX_BATCH_IDS = 50;
|
|
2033
|
-
}
|
|
2034
|
-
});
|
|
2035
|
-
|
|
2036
|
-
// src/dashboard/api/index.ts
|
|
2037
|
-
var init_api = __esm({
|
|
2038
|
-
"src/dashboard/api/index.ts"() {
|
|
2039
|
-
"use strict";
|
|
2040
|
-
init_handlers();
|
|
2041
|
-
init_ingest();
|
|
2042
|
-
init_metrics();
|
|
2043
|
-
init_metrics_live();
|
|
2044
|
-
init_activity();
|
|
2250
|
+
async ensureDirAsync() {
|
|
2251
|
+
if (!await fileExists(this.opts.dir)) {
|
|
2252
|
+
await mkdir(this.opts.dir, { recursive: true });
|
|
2253
|
+
if (this.opts.gitignoreEntry) {
|
|
2254
|
+
await ensureGitignoreAsync(this.opts.dir, this.opts.gitignoreEntry);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2045
2259
|
}
|
|
2046
2260
|
});
|
|
2047
2261
|
|
|
2048
|
-
// src/
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const categoryParam = url.searchParams.get("category");
|
|
2055
|
-
let issues;
|
|
2056
|
-
if (stateParam && isValidIssueState(stateParam)) {
|
|
2057
|
-
issues = issueStore.getByState(stateParam);
|
|
2058
|
-
} else if (categoryParam && isValidIssueCategory(categoryParam)) {
|
|
2059
|
-
issues = issueStore.getByCategory(categoryParam);
|
|
2060
|
-
} else {
|
|
2061
|
-
issues = issueStore.getAll();
|
|
2062
|
-
}
|
|
2063
|
-
sendJson(req, res, HTTP_OK, { issues });
|
|
2064
|
-
};
|
|
2065
|
-
}
|
|
2066
|
-
function createFindingsHandler(issueStore) {
|
|
2067
|
-
return (req, res) => {
|
|
2068
|
-
if (!requireGet(req, res)) return;
|
|
2069
|
-
const url = parseRequestUrl(req);
|
|
2070
|
-
const stateParam = url.searchParams.get("state");
|
|
2071
|
-
let issues;
|
|
2072
|
-
if (stateParam && isValidIssueState(stateParam)) {
|
|
2073
|
-
issues = issueStore.getByState(stateParam);
|
|
2074
|
-
} else {
|
|
2075
|
-
issues = issueStore.getAll();
|
|
2076
|
-
}
|
|
2077
|
-
sendJson(req, res, HTTP_OK, {
|
|
2078
|
-
total: issues.length,
|
|
2079
|
-
findings: issues
|
|
2080
|
-
});
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
function createIssuesReportHandler(issueStore, eventBus) {
|
|
2084
|
-
return async (req, res) => {
|
|
2085
|
-
if (req.method !== "POST") {
|
|
2086
|
-
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
|
-
const body = await readJsonBody(req, res);
|
|
2090
|
-
if (!body) return;
|
|
2091
|
-
const { findingId, status, notes } = body;
|
|
2092
|
-
if (!findingId || typeof findingId !== "string") {
|
|
2093
|
-
sendJson(req, res, HTTP_BAD_REQUEST, { error: "findingId is required" });
|
|
2094
|
-
return;
|
|
2095
|
-
}
|
|
2096
|
-
if (!isValidAiFixStatus(status)) {
|
|
2097
|
-
sendJson(req, res, HTTP_BAD_REQUEST, { error: "status must be 'fixed' or 'wont_fix'" });
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
if (!notes || typeof notes !== "string") {
|
|
2101
|
-
sendJson(req, res, HTTP_BAD_REQUEST, { error: "notes is required" });
|
|
2102
|
-
return;
|
|
2103
|
-
}
|
|
2104
|
-
if (issueStore.reportFix(findingId, status, notes)) {
|
|
2105
|
-
eventBus.emit("issues:changed", issueStore.getAll());
|
|
2106
|
-
sendJson(req, res, HTTP_OK, { ok: true });
|
|
2107
|
-
return;
|
|
2108
|
-
}
|
|
2109
|
-
sendJson(req, res, HTTP_NOT_FOUND, { error: "Finding not found" });
|
|
2110
|
-
};
|
|
2262
|
+
// src/utils/issue-id.ts
|
|
2263
|
+
import { createHash as createHash2 } from "crypto";
|
|
2264
|
+
function computeIssueId(issue) {
|
|
2265
|
+
const stableDesc = issue.desc.replace(/\d[\d,.]*\s*\w*/g, "#");
|
|
2266
|
+
const key = `${issue.rule}:${issue.endpoint ?? "global"}:${stableDesc}`;
|
|
2267
|
+
return createHash2("sha256").update(key).digest("hex").slice(0, ISSUE_ID_HASH_LENGTH);
|
|
2111
2268
|
}
|
|
2112
|
-
var
|
|
2113
|
-
"src/
|
|
2269
|
+
var init_issue_id = __esm({
|
|
2270
|
+
"src/utils/issue-id.ts"() {
|
|
2114
2271
|
"use strict";
|
|
2115
|
-
|
|
2116
|
-
init_type_guards();
|
|
2117
|
-
init_labels();
|
|
2272
|
+
init_config();
|
|
2118
2273
|
}
|
|
2119
2274
|
});
|
|
2120
2275
|
|
|
2121
|
-
// src/
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const rawLevel = url.searchParams.get("level") ?? void 0;
|
|
2129
|
-
const rawGrouping = url.searchParams.get("grouping") ?? void 0;
|
|
2130
|
-
const cluster = rawCluster && rawCluster.length <= MAX_PARAM_LENGTH ? rawCluster : void 0;
|
|
2131
|
-
const node = rawNode && rawNode.length <= MAX_PARAM_LENGTH ? rawNode : void 0;
|
|
2132
|
-
const level = rawLevel && VALID_LEVELS.has(rawLevel) ? rawLevel : void 0;
|
|
2133
|
-
const grouping = rawGrouping && VALID_GROUPINGS.has(rawGrouping) ? rawGrouping : void 0;
|
|
2134
|
-
const { graphBuilder, metricsStore } = services;
|
|
2135
|
-
graphBuilder.enrichWithMetrics((endpointKey) => {
|
|
2136
|
-
const metrics = metricsStore.getEndpoint(endpointKey);
|
|
2137
|
-
if (!metrics || metrics.sessions.length === 0) return void 0;
|
|
2138
|
-
const latest = metrics.sessions[metrics.sessions.length - 1];
|
|
2139
|
-
return latest.p95DurationMs;
|
|
2140
|
-
});
|
|
2141
|
-
const data = graphBuilder.getApiResponse({ cluster, node, level, grouping });
|
|
2142
|
-
sendJson(req, res, HTTP_OK, data);
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2145
|
-
var VALID_LEVELS, VALID_GROUPINGS, MAX_PARAM_LENGTH;
|
|
2146
|
-
var init_graph = __esm({
|
|
2147
|
-
"src/dashboard/api/graph.ts"() {
|
|
2148
|
-
"use strict";
|
|
2149
|
-
init_labels();
|
|
2150
|
-
init_shared2();
|
|
2151
|
-
VALID_LEVELS = /* @__PURE__ */ new Set(["endpoints", "clusters"]);
|
|
2152
|
-
VALID_GROUPINGS = /* @__PURE__ */ new Set(["path", "auth-boundary", "data-domain"]);
|
|
2153
|
-
MAX_PARAM_LENGTH = 200;
|
|
2154
|
-
}
|
|
2155
|
-
});
|
|
2156
|
-
|
|
2157
|
-
// src/dashboard/sse.ts
|
|
2158
|
-
function createSSEHandler(services) {
|
|
2159
|
-
const clients = /* @__PURE__ */ new Set();
|
|
2160
|
-
function broadcast(eventType, data) {
|
|
2161
|
-
if (clients.size === 0) return;
|
|
2162
|
-
const frame = eventType ? `event: ${eventType}
|
|
2163
|
-
data: ${data}
|
|
2164
|
-
|
|
2165
|
-
` : `data: ${data}
|
|
2166
|
-
|
|
2167
|
-
`;
|
|
2168
|
-
for (const client of clients) {
|
|
2169
|
-
if (client.res.destroyed) {
|
|
2170
|
-
clients.delete(client);
|
|
2171
|
-
continue;
|
|
2172
|
-
}
|
|
2173
|
-
try {
|
|
2174
|
-
client.res.write(frame);
|
|
2175
|
-
} catch {
|
|
2176
|
-
clients.delete(client);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
const bus = services.bus;
|
|
2181
|
-
bus.on("request:completed", (r) => broadcast(null, JSON.stringify(r)));
|
|
2182
|
-
bus.on("telemetry:fetch", (e) => broadcast(SSE_EVENT_FETCH, JSON.stringify(e)));
|
|
2183
|
-
bus.on("telemetry:log", (e) => broadcast(SSE_EVENT_LOG, JSON.stringify(e)));
|
|
2184
|
-
bus.on("telemetry:error", (e) => broadcast(SSE_EVENT_ERROR, JSON.stringify(e)));
|
|
2185
|
-
bus.on("telemetry:query", (e) => broadcast(SSE_EVENT_QUERY, JSON.stringify(e)));
|
|
2186
|
-
bus.on("analysis:updated", ({ issues }) => {
|
|
2187
|
-
broadcast(SSE_EVENT_ISSUES, JSON.stringify(issues));
|
|
2188
|
-
});
|
|
2189
|
-
bus.on("issues:changed", (issues) => {
|
|
2190
|
-
broadcast(SSE_EVENT_ISSUES, JSON.stringify(issues));
|
|
2191
|
-
});
|
|
2192
|
-
return (req, res) => {
|
|
2193
|
-
const headers2 = {
|
|
2194
|
-
"content-type": "text/event-stream",
|
|
2195
|
-
"cache-control": "no-cache",
|
|
2196
|
-
connection: "keep-alive"
|
|
2197
|
-
};
|
|
2198
|
-
const corsOrigin = getCorsOrigin(req);
|
|
2199
|
-
if (corsOrigin) {
|
|
2200
|
-
headers2["access-control-allow-origin"] = corsOrigin;
|
|
2201
|
-
}
|
|
2202
|
-
res.writeHead(HTTP_OK, headers2);
|
|
2203
|
-
res.write(":ok\n\n");
|
|
2204
|
-
const heartbeat = setInterval(() => {
|
|
2205
|
-
if (res.destroyed) {
|
|
2206
|
-
clearInterval(heartbeat);
|
|
2207
|
-
clients.delete(client);
|
|
2208
|
-
return;
|
|
2209
|
-
}
|
|
2210
|
-
try {
|
|
2211
|
-
res.write(":heartbeat\n\n");
|
|
2212
|
-
} catch {
|
|
2213
|
-
clearInterval(heartbeat);
|
|
2214
|
-
clients.delete(client);
|
|
2215
|
-
}
|
|
2216
|
-
}, SSE_HEARTBEAT_INTERVAL_MS);
|
|
2217
|
-
heartbeat.unref();
|
|
2218
|
-
const client = { res, heartbeat };
|
|
2219
|
-
clients.add(client);
|
|
2220
|
-
req.on("close", () => {
|
|
2221
|
-
clearInterval(heartbeat);
|
|
2222
|
-
clients.delete(client);
|
|
2223
|
-
});
|
|
2224
|
-
};
|
|
2225
|
-
}
|
|
2226
|
-
var init_sse = __esm({
|
|
2227
|
-
"src/dashboard/sse.ts"() {
|
|
2228
|
-
"use strict";
|
|
2229
|
-
init_constants();
|
|
2230
|
-
init_labels();
|
|
2231
|
-
init_shared2();
|
|
2232
|
-
}
|
|
2233
|
-
});
|
|
2234
|
-
|
|
2235
|
-
// src/utils/fs.ts
|
|
2236
|
-
import { access, readFile, writeFile } from "fs/promises";
|
|
2237
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2238
|
-
import { createHash } from "crypto";
|
|
2239
|
-
import { homedir } from "os";
|
|
2240
|
-
import { resolve, join } from "path";
|
|
2241
|
-
function getProjectDataDir(projectRoot) {
|
|
2242
|
-
const absolute = resolve(projectRoot);
|
|
2243
|
-
const hash = createHash("sha256").update(absolute).digest("hex").slice(0, PROJECT_HASH_LENGTH);
|
|
2244
|
-
return join(homedir(), ".brakit", "projects", hash);
|
|
2245
|
-
}
|
|
2246
|
-
async function fileExists(path) {
|
|
2247
|
-
try {
|
|
2248
|
-
await access(path);
|
|
2249
|
-
return true;
|
|
2250
|
-
} catch {
|
|
2251
|
-
return false;
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
function ensureGitignore(dir, entry) {
|
|
2255
|
-
try {
|
|
2256
|
-
const gitignorePath = resolve(dir, "../.gitignore");
|
|
2257
|
-
if (existsSync(gitignorePath)) {
|
|
2258
|
-
const content = readFileSync(gitignorePath, "utf-8");
|
|
2259
|
-
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
2260
|
-
writeFileSync(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
2261
|
-
} else {
|
|
2262
|
-
writeFileSync(gitignorePath, entry + "\n");
|
|
2263
|
-
}
|
|
2264
|
-
} catch (err) {
|
|
2265
|
-
brakitDebug(`ensureGitignore failed: ${getErrorMessage(err)}`);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
async function ensureGitignoreAsync(dir, entry) {
|
|
2269
|
-
try {
|
|
2270
|
-
const gitignorePath = resolve(dir, "../.gitignore");
|
|
2271
|
-
if (await fileExists(gitignorePath)) {
|
|
2272
|
-
const content = await readFile(gitignorePath, "utf-8");
|
|
2273
|
-
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
2274
|
-
await writeFile(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
2275
|
-
} else {
|
|
2276
|
-
await writeFile(gitignorePath, entry + "\n");
|
|
2277
|
-
}
|
|
2278
|
-
} catch (err) {
|
|
2279
|
-
brakitDebug(`ensureGitignoreAsync failed: ${getErrorMessage(err)}`);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
var init_fs = __esm({
|
|
2283
|
-
"src/utils/fs.ts"() {
|
|
2284
|
-
"use strict";
|
|
2285
|
-
init_config();
|
|
2286
|
-
init_log();
|
|
2287
|
-
init_type_guards();
|
|
2288
|
-
}
|
|
2289
|
-
});
|
|
2290
|
-
|
|
2291
|
-
// src/utils/atomic-writer.ts
|
|
2292
|
-
import {
|
|
2293
|
-
writeFileSync as writeFileSync2,
|
|
2294
|
-
existsSync as existsSync2,
|
|
2295
|
-
mkdirSync as mkdirSync2,
|
|
2296
|
-
renameSync
|
|
2297
|
-
} from "fs";
|
|
2298
|
-
import { writeFile as writeFile2, mkdir, rename } from "fs/promises";
|
|
2299
|
-
var AtomicWriter;
|
|
2300
|
-
var init_atomic_writer = __esm({
|
|
2301
|
-
"src/utils/atomic-writer.ts"() {
|
|
2302
|
-
"use strict";
|
|
2303
|
-
init_fs();
|
|
2304
|
-
init_log();
|
|
2305
|
-
init_type_guards();
|
|
2306
|
-
AtomicWriter = class {
|
|
2307
|
-
constructor(opts) {
|
|
2308
|
-
this.opts = opts;
|
|
2309
|
-
this.writing = false;
|
|
2310
|
-
this.pendingContent = null;
|
|
2311
|
-
this.tmpPath = opts.filePath + ".tmp";
|
|
2312
|
-
}
|
|
2313
|
-
writeSync(content) {
|
|
2314
|
-
try {
|
|
2315
|
-
this.ensureDir();
|
|
2316
|
-
writeFileSync2(this.tmpPath, content);
|
|
2317
|
-
renameSync(this.tmpPath, this.opts.filePath);
|
|
2318
|
-
} catch (err) {
|
|
2319
|
-
brakitWarn(`failed to save ${this.opts.label}: ${getErrorMessage(err)}`);
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
async writeAsync(content) {
|
|
2323
|
-
if (this.writing) {
|
|
2324
|
-
this.pendingContent = content;
|
|
2325
|
-
return;
|
|
2326
|
-
}
|
|
2327
|
-
this.writing = true;
|
|
2328
|
-
try {
|
|
2329
|
-
await this.ensureDirAsync();
|
|
2330
|
-
await writeFile2(this.tmpPath, content);
|
|
2331
|
-
await rename(this.tmpPath, this.opts.filePath);
|
|
2332
|
-
} catch (err) {
|
|
2333
|
-
brakitWarn(`failed to save ${this.opts.label}: ${getErrorMessage(err)}`);
|
|
2334
|
-
} finally {
|
|
2335
|
-
this.writing = false;
|
|
2336
|
-
if (this.pendingContent !== null) {
|
|
2337
|
-
const next = this.pendingContent;
|
|
2338
|
-
this.pendingContent = null;
|
|
2339
|
-
this.writeAsync(next).catch(() => {
|
|
2340
|
-
});
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
ensureDir() {
|
|
2345
|
-
if (!existsSync2(this.opts.dir)) {
|
|
2346
|
-
mkdirSync2(this.opts.dir, { recursive: true });
|
|
2347
|
-
if (this.opts.gitignoreEntry) {
|
|
2348
|
-
ensureGitignore(this.opts.dir, this.opts.gitignoreEntry);
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
async ensureDirAsync() {
|
|
2353
|
-
if (!await fileExists(this.opts.dir)) {
|
|
2354
|
-
await mkdir(this.opts.dir, { recursive: true });
|
|
2355
|
-
if (this.opts.gitignoreEntry) {
|
|
2356
|
-
await ensureGitignoreAsync(this.opts.dir, this.opts.gitignoreEntry);
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
});
|
|
2363
|
-
|
|
2364
|
-
// src/utils/issue-id.ts
|
|
2365
|
-
import { createHash as createHash2 } from "crypto";
|
|
2366
|
-
function computeIssueId(issue) {
|
|
2367
|
-
const stableDesc = issue.desc.replace(/\d[\d,.]*\s*\w*/g, "#");
|
|
2368
|
-
const key = `${issue.rule}:${issue.endpoint ?? "global"}:${stableDesc}`;
|
|
2369
|
-
return createHash2("sha256").update(key).digest("hex").slice(0, ISSUE_ID_HASH_LENGTH);
|
|
2370
|
-
}
|
|
2371
|
-
var init_issue_id = __esm({
|
|
2372
|
-
"src/utils/issue-id.ts"() {
|
|
2373
|
-
"use strict";
|
|
2374
|
-
init_config();
|
|
2375
|
-
}
|
|
2376
|
-
});
|
|
2377
|
-
|
|
2378
|
-
// src/store/issue-store.ts
|
|
2379
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
2380
|
-
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync } from "fs";
|
|
2381
|
-
import { resolve as resolve2 } from "path";
|
|
2382
|
-
var IssueStore;
|
|
2383
|
-
var init_issue_store = __esm({
|
|
2384
|
-
"src/store/issue-store.ts"() {
|
|
2276
|
+
// src/store/issue-store.ts
|
|
2277
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2278
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, unlinkSync } from "fs";
|
|
2279
|
+
import { resolve as resolve2 } from "path";
|
|
2280
|
+
var IssueStore;
|
|
2281
|
+
var init_issue_store = __esm({
|
|
2282
|
+
"src/store/issue-store.ts"() {
|
|
2385
2283
|
"use strict";
|
|
2386
2284
|
init_fs();
|
|
2387
2285
|
init_config();
|
|
@@ -2426,7 +2324,7 @@ var init_issue_store = __esm({
|
|
|
2426
2324
|
existing.occurrences++;
|
|
2427
2325
|
existing.issue = issue;
|
|
2428
2326
|
existing.cleanHitsSinceLastSeen = 0;
|
|
2429
|
-
if (existing.state === "resolved" || existing.state === "stale") {
|
|
2327
|
+
if (existing.aiStatus !== "wont_fix" && (existing.state === "resolved" || existing.state === "stale")) {
|
|
2430
2328
|
existing.state = "regressed";
|
|
2431
2329
|
existing.resolvedAt = null;
|
|
2432
2330
|
}
|
|
@@ -2452,10 +2350,11 @@ var init_issue_store = __esm({
|
|
|
2452
2350
|
return stateful;
|
|
2453
2351
|
}
|
|
2454
2352
|
/**
|
|
2455
|
-
*
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2353
|
+
* Evidence-based reconciliation: for each active issue whose endpoint had
|
|
2354
|
+
* traffic but the issue was NOT re-detected, increment cleanHitsSinceLastSeen.
|
|
2355
|
+
* After CLEAN_HITS_FOR_RESOLUTION consecutive clean cycles, auto-resolve.
|
|
2356
|
+
* Issues on endpoints with no recent traffic are marked stale after STALE_ISSUE_TTL_MS.
|
|
2357
|
+
* Resolved and stale issues are pruned after their respective TTLs expire.
|
|
2459
2358
|
*/
|
|
2460
2359
|
reconcile(currentIssueIds, activeEndpoints) {
|
|
2461
2360
|
const now = Date.now();
|
|
@@ -2523,7 +2422,7 @@ var init_issue_store = __esm({
|
|
|
2523
2422
|
this.issues.clear();
|
|
2524
2423
|
this.dirty = false;
|
|
2525
2424
|
try {
|
|
2526
|
-
if (
|
|
2425
|
+
if (existsSync4(this.issuesPath)) {
|
|
2527
2426
|
unlinkSync(this.issuesPath);
|
|
2528
2427
|
}
|
|
2529
2428
|
} catch {
|
|
@@ -2545,17 +2444,23 @@ var init_issue_store = __esm({
|
|
|
2545
2444
|
/** Sync load for tests only — not used in production paths. */
|
|
2546
2445
|
loadSync() {
|
|
2547
2446
|
try {
|
|
2548
|
-
if (
|
|
2549
|
-
const raw =
|
|
2447
|
+
if (existsSync4(this.issuesPath)) {
|
|
2448
|
+
const raw = readFileSync3(this.issuesPath, "utf-8");
|
|
2550
2449
|
this.hydrate(raw);
|
|
2551
2450
|
}
|
|
2552
2451
|
} catch (err) {
|
|
2553
2452
|
brakitDebug(`IssueStore: could not load issues file, starting fresh: ${err}`);
|
|
2554
2453
|
}
|
|
2555
2454
|
}
|
|
2556
|
-
/** Parse and populate issues from a raw JSON string. */
|
|
2557
2455
|
hydrate(raw) {
|
|
2558
|
-
|
|
2456
|
+
let parsed;
|
|
2457
|
+
try {
|
|
2458
|
+
parsed = JSON.parse(raw);
|
|
2459
|
+
} catch (err) {
|
|
2460
|
+
brakitDebug(`IssueStore: corrupt JSON in issues file, starting fresh: ${err}`);
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const validated = validateIssuesData(parsed);
|
|
2559
2464
|
if (!validated) return;
|
|
2560
2465
|
for (const issue of validated.issues) {
|
|
2561
2466
|
this.issues.set(issue.issueId, issue);
|
|
@@ -2568,8 +2473,12 @@ var init_issue_store = __esm({
|
|
|
2568
2473
|
}
|
|
2569
2474
|
flushSync() {
|
|
2570
2475
|
if (!this.dirty) return;
|
|
2571
|
-
|
|
2572
|
-
|
|
2476
|
+
try {
|
|
2477
|
+
this.writer.writeSync(this.serialize());
|
|
2478
|
+
this.dirty = false;
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
brakitDebug(`IssueStore: flush failed, will retry: ${err}`);
|
|
2481
|
+
}
|
|
2573
2482
|
}
|
|
2574
2483
|
serialize() {
|
|
2575
2484
|
const data = {
|
|
@@ -2584,19 +2493,26 @@ var init_issue_store = __esm({
|
|
|
2584
2493
|
|
|
2585
2494
|
// src/detect/project.ts
|
|
2586
2495
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
2587
|
-
import { existsSync as
|
|
2588
|
-
import { join as
|
|
2496
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2497
|
+
import { join as join3, relative } from "path";
|
|
2589
2498
|
function detectFrameworkFromDeps(allDeps) {
|
|
2590
2499
|
for (const f of FRAMEWORKS) {
|
|
2591
2500
|
if (allDeps[f.dep]) return f.name;
|
|
2592
2501
|
}
|
|
2593
2502
|
return "unknown";
|
|
2594
2503
|
}
|
|
2595
|
-
function
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2504
|
+
function detectConfigFiles(rootDir) {
|
|
2505
|
+
const found = /* @__PURE__ */ new Set();
|
|
2506
|
+
for (const [file, label] of Object.entries(KNOWN_CONFIG_FILES)) {
|
|
2507
|
+
if (existsSync5(join3(rootDir, file))) found.add(label);
|
|
2508
|
+
}
|
|
2509
|
+
return [...found];
|
|
2510
|
+
}
|
|
2511
|
+
function detectPackageManager(rootDir) {
|
|
2512
|
+
if (existsSync5(join3(rootDir, "bun.lockb")) || existsSync5(join3(rootDir, "bun.lock"))) return "bun";
|
|
2513
|
+
if (existsSync5(join3(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2514
|
+
if (existsSync5(join3(rootDir, "yarn.lock"))) return "yarn";
|
|
2515
|
+
if (existsSync5(join3(rootDir, "package-lock.json"))) return "npm";
|
|
2600
2516
|
return "unknown";
|
|
2601
2517
|
}
|
|
2602
2518
|
var FRAMEWORKS;
|
|
@@ -2604,12 +2520,24 @@ var init_project = __esm({
|
|
|
2604
2520
|
"src/detect/project.ts"() {
|
|
2605
2521
|
"use strict";
|
|
2606
2522
|
init_fs();
|
|
2523
|
+
init_detection();
|
|
2607
2524
|
FRAMEWORKS = [
|
|
2525
|
+
// Meta-frameworks first (they bundle Express/Vite internally)
|
|
2608
2526
|
{ name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
2609
2527
|
{ name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
|
|
2610
2528
|
{ name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
2611
|
-
{ name: "
|
|
2612
|
-
{ name: "
|
|
2529
|
+
{ name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] },
|
|
2530
|
+
{ name: "nestjs", dep: "@nestjs/core", devCmd: "nest start", bin: "nest", defaultPort: 3e3, devArgs: ["--watch"] },
|
|
2531
|
+
{ name: "adonis", dep: "@adonisjs/core", devCmd: "node ace serve", bin: "ace", defaultPort: 3333, devArgs: ["serve", "--watch"] },
|
|
2532
|
+
{ name: "sails", dep: "sails", devCmd: "sails lift", bin: "sails", defaultPort: 1337, devArgs: ["lift"] },
|
|
2533
|
+
// Server frameworks
|
|
2534
|
+
{ name: "hono", dep: "hono", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
2535
|
+
{ name: "fastify", dep: "fastify", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
2536
|
+
{ name: "koa", dep: "koa", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
2537
|
+
{ name: "hapi", dep: "@hapi/hapi", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
2538
|
+
{ name: "express", dep: "express", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
2539
|
+
// Bundlers (last — likely used alongside a framework above)
|
|
2540
|
+
{ name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] }
|
|
2613
2541
|
];
|
|
2614
2542
|
}
|
|
2615
2543
|
});
|
|
@@ -3345,14 +3273,14 @@ var init_disposable = __esm({
|
|
|
3345
3273
|
"use strict";
|
|
3346
3274
|
SubscriptionBag = class {
|
|
3347
3275
|
constructor() {
|
|
3348
|
-
this.items =
|
|
3276
|
+
this.items = /* @__PURE__ */ new Set();
|
|
3349
3277
|
}
|
|
3350
3278
|
add(teardown) {
|
|
3351
|
-
this.items.
|
|
3279
|
+
this.items.add(typeof teardown === "function" ? { dispose: teardown } : teardown);
|
|
3352
3280
|
}
|
|
3353
3281
|
dispose() {
|
|
3354
3282
|
for (const d of this.items) d.dispose();
|
|
3355
|
-
this.items.
|
|
3283
|
+
this.items.clear();
|
|
3356
3284
|
}
|
|
3357
3285
|
};
|
|
3358
3286
|
}
|
|
@@ -3967,275 +3895,936 @@ var init_pattern_rules = __esm({
|
|
|
3967
3895
|
hint: "Multiple components independently fetch the same endpoint. Lift the fetch to a parent component, use a data cache, or deduplicate with React Query / SWR."
|
|
3968
3896
|
});
|
|
3969
3897
|
}
|
|
3970
|
-
return insights;
|
|
3898
|
+
return insights;
|
|
3899
|
+
}
|
|
3900
|
+
};
|
|
3901
|
+
crossEndpointRule = {
|
|
3902
|
+
id: "cross-endpoint",
|
|
3903
|
+
check(ctx) {
|
|
3904
|
+
const insights = [];
|
|
3905
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
3906
|
+
const allEndpoints = /* @__PURE__ */ new Set();
|
|
3907
|
+
for (const [reqId, reqQueries] of ctx.queriesByReq) {
|
|
3908
|
+
const req = ctx.reqById.get(reqId);
|
|
3909
|
+
if (!req) continue;
|
|
3910
|
+
const endpoint = getEndpointKey(req.method, req.path);
|
|
3911
|
+
allEndpoints.add(endpoint);
|
|
3912
|
+
const seenInReq = /* @__PURE__ */ new Set();
|
|
3913
|
+
for (const query of reqQueries) {
|
|
3914
|
+
const shape = getQueryShape(query);
|
|
3915
|
+
let entry = queryMap.get(shape);
|
|
3916
|
+
if (!entry) {
|
|
3917
|
+
entry = { endpoints: /* @__PURE__ */ new Set(), count: 0, first: query };
|
|
3918
|
+
queryMap.set(shape, entry);
|
|
3919
|
+
}
|
|
3920
|
+
entry.count++;
|
|
3921
|
+
if (!seenInReq.has(shape)) {
|
|
3922
|
+
seenInReq.add(shape);
|
|
3923
|
+
entry.endpoints.add(endpoint);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
if (allEndpoints.size >= CROSS_ENDPOINT_MIN_ENDPOINTS) {
|
|
3928
|
+
for (const [, queryMetric] of queryMap) {
|
|
3929
|
+
if (queryMetric.count < CROSS_ENDPOINT_MIN_OCCURRENCES) continue;
|
|
3930
|
+
if (queryMetric.endpoints.size < CROSS_ENDPOINT_MIN_ENDPOINTS) continue;
|
|
3931
|
+
const coveragePct = Math.round(queryMetric.endpoints.size / allEndpoints.size * 100);
|
|
3932
|
+
if (coveragePct < CROSS_ENDPOINT_PCT) continue;
|
|
3933
|
+
const info = getQueryInfo(queryMetric.first);
|
|
3934
|
+
const label = info.op + (info.table ? ` ${info.table}` : "");
|
|
3935
|
+
insights.push({
|
|
3936
|
+
severity: "warning",
|
|
3937
|
+
type: "cross-endpoint",
|
|
3938
|
+
title: "Repeated Query Across Endpoints",
|
|
3939
|
+
desc: `${label} runs on ${queryMetric.endpoints.size} of ${allEndpoints.size} endpoints (${coveragePct}%).`,
|
|
3940
|
+
hint: "This query runs on most of your endpoints. Load it once in middleware or cache the result to avoid redundant database calls.",
|
|
3941
|
+
detail: `Endpoints: ${[...queryMetric.endpoints].slice(0, 5).join(", ")}${queryMetric.endpoints.size > 5 ? ` +${queryMetric.endpoints.size - 5} more` : ""}. Total: ${queryMetric.count} executions.`
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
return insights;
|
|
3946
|
+
}
|
|
3947
|
+
};
|
|
3948
|
+
}
|
|
3949
|
+
});
|
|
3950
|
+
|
|
3951
|
+
// src/analysis/insights/rules/security.ts
|
|
3952
|
+
var securityRule;
|
|
3953
|
+
var init_security = __esm({
|
|
3954
|
+
"src/analysis/insights/rules/security.ts"() {
|
|
3955
|
+
"use strict";
|
|
3956
|
+
securityRule = {
|
|
3957
|
+
id: "security",
|
|
3958
|
+
check(ctx) {
|
|
3959
|
+
if (!ctx.securityFindings) return [];
|
|
3960
|
+
return ctx.securityFindings.map((finding) => ({
|
|
3961
|
+
severity: finding.severity,
|
|
3962
|
+
type: "security",
|
|
3963
|
+
title: finding.title,
|
|
3964
|
+
desc: finding.desc,
|
|
3965
|
+
hint: finding.hint,
|
|
3966
|
+
detail: finding.detail
|
|
3967
|
+
}));
|
|
3968
|
+
}
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
});
|
|
3972
|
+
|
|
3973
|
+
// src/analysis/insights/rules/index.ts
|
|
3974
|
+
var init_rules2 = __esm({
|
|
3975
|
+
"src/analysis/insights/rules/index.ts"() {
|
|
3976
|
+
"use strict";
|
|
3977
|
+
init_query_rules();
|
|
3978
|
+
init_response_rules();
|
|
3979
|
+
init_reliability_rules();
|
|
3980
|
+
init_pattern_rules();
|
|
3981
|
+
init_security();
|
|
3982
|
+
}
|
|
3983
|
+
});
|
|
3984
|
+
|
|
3985
|
+
// src/analysis/insights/index.ts
|
|
3986
|
+
function createDefaultInsightRunner() {
|
|
3987
|
+
const runner = new InsightRunner();
|
|
3988
|
+
runner.register(n1Rule);
|
|
3989
|
+
runner.register(crossEndpointRule);
|
|
3990
|
+
runner.register(redundantQueryRule);
|
|
3991
|
+
runner.register(errorRule);
|
|
3992
|
+
runner.register(errorHotspotRule);
|
|
3993
|
+
runner.register(duplicateRule);
|
|
3994
|
+
runner.register(slowRule);
|
|
3995
|
+
runner.register(queryHeavyRule);
|
|
3996
|
+
runner.register(selectStarRule);
|
|
3997
|
+
runner.register(highRowsRule);
|
|
3998
|
+
runner.register(responseOverfetchRule);
|
|
3999
|
+
runner.register(largeResponseRule);
|
|
4000
|
+
runner.register(regressionRule);
|
|
4001
|
+
runner.register(securityRule);
|
|
4002
|
+
return runner;
|
|
4003
|
+
}
|
|
4004
|
+
function computeInsights(ctx) {
|
|
4005
|
+
return createDefaultInsightRunner().run(ctx);
|
|
4006
|
+
}
|
|
4007
|
+
var init_insights = __esm({
|
|
4008
|
+
"src/analysis/insights/index.ts"() {
|
|
4009
|
+
"use strict";
|
|
4010
|
+
init_runner();
|
|
4011
|
+
init_runner();
|
|
4012
|
+
init_rules2();
|
|
4013
|
+
}
|
|
4014
|
+
});
|
|
4015
|
+
|
|
4016
|
+
// src/analysis/insights.ts
|
|
4017
|
+
var init_insights2 = __esm({
|
|
4018
|
+
"src/analysis/insights.ts"() {
|
|
4019
|
+
"use strict";
|
|
4020
|
+
init_insights();
|
|
4021
|
+
}
|
|
4022
|
+
});
|
|
4023
|
+
|
|
4024
|
+
// src/analysis/issue-mappers.ts
|
|
4025
|
+
function categorizeInsight(type) {
|
|
4026
|
+
if (type === "security") return "security";
|
|
4027
|
+
if (type === "error" || type === "error-hotspot") return "reliability";
|
|
4028
|
+
return "performance";
|
|
4029
|
+
}
|
|
4030
|
+
function insightToIssue(insight) {
|
|
4031
|
+
return {
|
|
4032
|
+
category: categorizeInsight(insight.type),
|
|
4033
|
+
rule: insight.type,
|
|
4034
|
+
severity: insight.severity,
|
|
4035
|
+
title: insight.title,
|
|
4036
|
+
desc: insight.desc,
|
|
4037
|
+
hint: insight.hint,
|
|
4038
|
+
detail: insight.detail,
|
|
4039
|
+
endpoint: extractEndpointFromDesc(insight.desc) ?? void 0
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4042
|
+
function securityFindingToIssue(finding) {
|
|
4043
|
+
return {
|
|
4044
|
+
category: "security",
|
|
4045
|
+
rule: finding.rule,
|
|
4046
|
+
severity: finding.severity,
|
|
4047
|
+
title: finding.title,
|
|
4048
|
+
desc: finding.desc,
|
|
4049
|
+
hint: finding.hint,
|
|
4050
|
+
detail: finding.detail,
|
|
4051
|
+
endpoint: finding.endpoint
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
var init_issue_mappers = __esm({
|
|
4055
|
+
"src/analysis/issue-mappers.ts"() {
|
|
4056
|
+
"use strict";
|
|
4057
|
+
init_endpoint();
|
|
4058
|
+
}
|
|
4059
|
+
});
|
|
4060
|
+
|
|
4061
|
+
// src/analysis/engine.ts
|
|
4062
|
+
var AnalysisEngine;
|
|
4063
|
+
var init_engine = __esm({
|
|
4064
|
+
"src/analysis/engine.ts"() {
|
|
4065
|
+
"use strict";
|
|
4066
|
+
init_config();
|
|
4067
|
+
init_disposable();
|
|
4068
|
+
init_group();
|
|
4069
|
+
init_rules();
|
|
4070
|
+
init_insights2();
|
|
4071
|
+
init_issue_mappers();
|
|
4072
|
+
init_issue_id();
|
|
4073
|
+
init_prepare();
|
|
4074
|
+
AnalysisEngine = class {
|
|
4075
|
+
constructor(services, debounceMs = ANALYSIS_DEBOUNCE_MS) {
|
|
4076
|
+
this.services = services;
|
|
4077
|
+
this.debounceMs = debounceMs;
|
|
4078
|
+
this.cachedInsights = [];
|
|
4079
|
+
this.cachedFindings = [];
|
|
4080
|
+
this.debounceTimer = null;
|
|
4081
|
+
this.subs = new SubscriptionBag();
|
|
4082
|
+
this.scanner = createDefaultScanner();
|
|
4083
|
+
}
|
|
4084
|
+
start() {
|
|
4085
|
+
const bus = this.services.bus;
|
|
4086
|
+
this.subs.add(bus.on("request:completed", () => this.scheduleRecompute()));
|
|
4087
|
+
this.subs.add(bus.on("telemetry:query", () => this.scheduleRecompute()));
|
|
4088
|
+
this.subs.add(bus.on("telemetry:error", () => this.scheduleRecompute()));
|
|
4089
|
+
this.subs.add(bus.on("telemetry:log", () => this.scheduleRecompute()));
|
|
4090
|
+
}
|
|
4091
|
+
stop() {
|
|
4092
|
+
this.subs.dispose();
|
|
4093
|
+
if (this.debounceTimer) {
|
|
4094
|
+
clearTimeout(this.debounceTimer);
|
|
4095
|
+
this.debounceTimer = null;
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
getInsights() {
|
|
4099
|
+
return this.cachedInsights;
|
|
4100
|
+
}
|
|
4101
|
+
getFindings() {
|
|
4102
|
+
return this.cachedFindings;
|
|
4103
|
+
}
|
|
4104
|
+
scheduleRecompute() {
|
|
4105
|
+
if (this.debounceTimer) return;
|
|
4106
|
+
this.debounceTimer = setTimeout(() => {
|
|
4107
|
+
this.debounceTimer = null;
|
|
4108
|
+
this.recompute();
|
|
4109
|
+
}, this.debounceMs);
|
|
4110
|
+
}
|
|
4111
|
+
recompute() {
|
|
4112
|
+
const allRequests = this.services.requestStore.getAll();
|
|
4113
|
+
const queries = this.services.queryStore.getAll();
|
|
4114
|
+
const errors = this.services.errorStore.getAll();
|
|
4115
|
+
const logs = this.services.logStore.getAll();
|
|
4116
|
+
const fetches = this.services.fetchStore.getAll();
|
|
4117
|
+
const requests = keepRecentPerEndpoint(allRequests);
|
|
4118
|
+
const flows = groupRequestsIntoFlows(requests);
|
|
4119
|
+
this.cachedFindings = this.scanner.scan({ requests, logs });
|
|
4120
|
+
this.cachedInsights = computeInsights({
|
|
4121
|
+
requests,
|
|
4122
|
+
queries,
|
|
4123
|
+
errors,
|
|
4124
|
+
flows,
|
|
4125
|
+
fetches,
|
|
4126
|
+
previousMetrics: this.services.metricsStore.getAll(),
|
|
4127
|
+
securityFindings: this.cachedFindings
|
|
4128
|
+
});
|
|
4129
|
+
const issueStore = this.services.issueStore;
|
|
4130
|
+
const currentIssueIds = /* @__PURE__ */ new Set();
|
|
4131
|
+
for (const finding of this.cachedFindings) {
|
|
4132
|
+
const issue = securityFindingToIssue(finding);
|
|
4133
|
+
issueStore.upsert(issue, "passive");
|
|
4134
|
+
currentIssueIds.add(computeIssueId(issue));
|
|
4135
|
+
}
|
|
4136
|
+
for (const insight of this.cachedInsights) {
|
|
4137
|
+
const issue = insightToIssue(insight);
|
|
4138
|
+
issueStore.upsert(issue, "passive");
|
|
4139
|
+
currentIssueIds.add(computeIssueId(issue));
|
|
4140
|
+
}
|
|
4141
|
+
const activeEndpoints = extractActiveEndpoints(allRequests);
|
|
4142
|
+
issueStore.reconcile(currentIssueIds, activeEndpoints);
|
|
4143
|
+
const update = {
|
|
4144
|
+
insights: this.cachedInsights,
|
|
4145
|
+
findings: this.cachedFindings,
|
|
4146
|
+
issues: issueStore.getAll()
|
|
4147
|
+
};
|
|
4148
|
+
this.services.bus.emit("analysis:updated", update);
|
|
4149
|
+
}
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
});
|
|
4153
|
+
|
|
4154
|
+
// src/index.ts
|
|
4155
|
+
var VERSION;
|
|
4156
|
+
var init_src = __esm({
|
|
4157
|
+
"src/index.ts"() {
|
|
4158
|
+
"use strict";
|
|
4159
|
+
init_issue_store();
|
|
4160
|
+
init_project();
|
|
4161
|
+
init_adapter_registry();
|
|
4162
|
+
init_rules();
|
|
4163
|
+
init_engine();
|
|
4164
|
+
init_insights2();
|
|
4165
|
+
init_insights();
|
|
4166
|
+
VERSION = "0.10.2";
|
|
4167
|
+
}
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4170
|
+
// src/telemetry/transport.ts
|
|
4171
|
+
import { platform as platform2, release, arch } from "os";
|
|
4172
|
+
import { spawn } from "child_process";
|
|
4173
|
+
function commonProperties() {
|
|
4174
|
+
return {
|
|
4175
|
+
brakit_version: VERSION,
|
|
4176
|
+
node_version: process.version,
|
|
4177
|
+
os: `${platform2()}-${release()}`,
|
|
4178
|
+
arch: arch(),
|
|
4179
|
+
$lib: "brakit",
|
|
4180
|
+
$process_person_profile: false,
|
|
4181
|
+
$geoip_disable: true
|
|
4182
|
+
};
|
|
4183
|
+
}
|
|
4184
|
+
function sendToPosthog(event, properties) {
|
|
4185
|
+
if (!isTelemetryEnabled() || !POSTHOG_KEY) return;
|
|
4186
|
+
const config = getOrCreateConfig();
|
|
4187
|
+
const payload = {
|
|
4188
|
+
api_key: POSTHOG_KEY,
|
|
4189
|
+
event,
|
|
4190
|
+
distinct_id: config.anonymousId,
|
|
4191
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4192
|
+
properties: { ...commonProperties(), ...properties }
|
|
4193
|
+
};
|
|
4194
|
+
try {
|
|
4195
|
+
const serializedPayload = JSON.stringify(payload);
|
|
4196
|
+
const url = `${POSTHOG_HOST}${POSTHOG_CAPTURE_PATH}`;
|
|
4197
|
+
const child = spawn(
|
|
4198
|
+
process.execPath,
|
|
4199
|
+
[
|
|
4200
|
+
"-e",
|
|
4201
|
+
`fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(serializedPayload)},signal:AbortSignal.timeout(${POSTHOG_REQUEST_TIMEOUT_MS})}).catch(()=>{})`
|
|
4202
|
+
],
|
|
4203
|
+
{ detached: true, stdio: "ignore" }
|
|
4204
|
+
);
|
|
4205
|
+
child.unref();
|
|
4206
|
+
} catch (err) {
|
|
4207
|
+
brakitDebug(`telemetry send failed: ${err}`);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
function trackEvent(event, properties) {
|
|
4211
|
+
sendToPosthog(event, { sdk: TELEMETRY_SDK_NAME, ...properties });
|
|
4212
|
+
}
|
|
4213
|
+
var POSTHOG_KEY;
|
|
4214
|
+
var init_transport = __esm({
|
|
4215
|
+
"src/telemetry/transport.ts"() {
|
|
4216
|
+
"use strict";
|
|
4217
|
+
init_src();
|
|
4218
|
+
init_telemetry();
|
|
4219
|
+
init_config2();
|
|
4220
|
+
init_log();
|
|
4221
|
+
POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
|
|
4222
|
+
}
|
|
4223
|
+
});
|
|
4224
|
+
|
|
4225
|
+
// src/telemetry/session.ts
|
|
4226
|
+
function speedBucket(ms) {
|
|
4227
|
+
if (ms === 0) return "none";
|
|
4228
|
+
const t = SPEED_BUCKET_THRESHOLDS;
|
|
4229
|
+
if (ms < t[0]) return `<${t[0]}ms`;
|
|
4230
|
+
for (let i = 1; i < t.length; i++) {
|
|
4231
|
+
if (ms < t[i]) return `${t[i - 1]}-${t[i]}ms`;
|
|
4232
|
+
}
|
|
4233
|
+
return `>${t[t.length - 1]}ms`;
|
|
4234
|
+
}
|
|
4235
|
+
var defaultTransport, Session;
|
|
4236
|
+
var init_session = __esm({
|
|
4237
|
+
"src/telemetry/session.ts"() {
|
|
4238
|
+
"use strict";
|
|
4239
|
+
init_telemetry();
|
|
4240
|
+
init_config2();
|
|
4241
|
+
init_transport();
|
|
4242
|
+
defaultTransport = { send: sendToPosthog, track: trackEvent };
|
|
4243
|
+
Session = class {
|
|
4244
|
+
constructor(transport = defaultTransport) {
|
|
4245
|
+
// ── Setup phase ──
|
|
4246
|
+
this.startTime = 0;
|
|
4247
|
+
this.framework = "unknown";
|
|
4248
|
+
this.packageManager = "";
|
|
4249
|
+
this.isCustomCommand = false;
|
|
4250
|
+
this.adapters = [];
|
|
4251
|
+
this.setupDurationMs = 0;
|
|
4252
|
+
this.setupSucceeded = false;
|
|
4253
|
+
// ── Detection ──
|
|
4254
|
+
this.detectedDependencies = [];
|
|
4255
|
+
this.adaptersFailed = [];
|
|
4256
|
+
this.configFilesDetected = [];
|
|
4257
|
+
this.jsRuntime = "node";
|
|
4258
|
+
this.loadedPackages = [];
|
|
4259
|
+
// ── Python SDK ──
|
|
4260
|
+
this.pythonConnected = false;
|
|
4261
|
+
this.pythonFramework = "unknown";
|
|
4262
|
+
this.pythonAdapters = [];
|
|
4263
|
+
this.pythonVersion = "";
|
|
4264
|
+
// ── Runtime accumulation ──
|
|
4265
|
+
this.requestCount = 0;
|
|
4266
|
+
this.insightTypes = /* @__PURE__ */ new Set();
|
|
4267
|
+
this.rulesTriggered = /* @__PURE__ */ new Set();
|
|
4268
|
+
this.tabsViewed = /* @__PURE__ */ new Set();
|
|
4269
|
+
this.dashboardOpened = false;
|
|
4270
|
+
this.explainUsed = false;
|
|
4271
|
+
this.firstRequestAt = 0;
|
|
4272
|
+
this.dashboardOpenedAt = 0;
|
|
4273
|
+
this.exitReason = "unknown";
|
|
4274
|
+
this.transport = transport;
|
|
4275
|
+
}
|
|
4276
|
+
// ── Setup phase ──
|
|
4277
|
+
init(framework, packageManager, isCustomCommand, adapters) {
|
|
4278
|
+
getOrCreateConfig();
|
|
4279
|
+
this.startTime = Date.now();
|
|
4280
|
+
this.framework = framework;
|
|
4281
|
+
this.packageManager = packageManager;
|
|
4282
|
+
this.isCustomCommand = isCustomCommand;
|
|
4283
|
+
this.adapters = adapters;
|
|
4284
|
+
}
|
|
4285
|
+
recordSetup(detection, durationMs) {
|
|
4286
|
+
this.detectedDependencies = detection.detectedDependencies;
|
|
4287
|
+
this.adaptersFailed = detection.adaptersFailed;
|
|
4288
|
+
this.configFilesDetected = detection.configFilesDetected;
|
|
4289
|
+
this.jsRuntime = detection.jsRuntime;
|
|
4290
|
+
this.setupDurationMs = durationMs;
|
|
4291
|
+
this.setupSucceeded = true;
|
|
4292
|
+
}
|
|
4293
|
+
// ── Runtime events ──
|
|
4294
|
+
recordFirstRequest(loadedPackages) {
|
|
4295
|
+
if (!this.firstRequestAt) this.firstRequestAt = Date.now();
|
|
4296
|
+
this.loadedPackages = loadedPackages;
|
|
4297
|
+
}
|
|
4298
|
+
recordPythonStack(info) {
|
|
4299
|
+
this.pythonConnected = true;
|
|
4300
|
+
this.pythonFramework = info.framework;
|
|
4301
|
+
this.pythonAdapters = info.adapters;
|
|
4302
|
+
this.pythonVersion = info.pythonVersion;
|
|
4303
|
+
}
|
|
4304
|
+
recordDashboardOpened() {
|
|
4305
|
+
if (this.dashboardOpened) return;
|
|
4306
|
+
this.dashboardOpened = true;
|
|
4307
|
+
this.dashboardOpenedAt = Date.now();
|
|
4308
|
+
this.transport.track(TELEMETRY_EVENT_DASHBOARD_VIEWED, {
|
|
4309
|
+
time_to_dashboard_ms: this.startTime > 0 ? Date.now() - this.startTime : null,
|
|
4310
|
+
request_count_at_open: this.requestCount
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
recordTabViewed(tab) {
|
|
4314
|
+
this.tabsViewed.add(tab);
|
|
4315
|
+
}
|
|
4316
|
+
recordExplainUsed() {
|
|
4317
|
+
this.explainUsed = true;
|
|
4318
|
+
}
|
|
4319
|
+
recordExitReason(reason) {
|
|
4320
|
+
if (this.exitReason === "unknown") this.exitReason = reason;
|
|
4321
|
+
}
|
|
4322
|
+
// ── Pre-flush snapshot from services ──
|
|
4323
|
+
recordCounts(requestCount, insightTypes, rulesTriggered) {
|
|
4324
|
+
this.requestCount = requestCount;
|
|
4325
|
+
for (const t of insightTypes) this.insightTypes.add(t);
|
|
4326
|
+
for (const r of rulesTriggered) this.rulesTriggered.add(r);
|
|
4327
|
+
}
|
|
4328
|
+
// ── Serialization ──
|
|
4329
|
+
/** Build the full PostHog session payload. Single source of truth for all fields. */
|
|
4330
|
+
toPostHogPayload(services) {
|
|
4331
|
+
const metricsStore = services.metricsStore;
|
|
4332
|
+
const analysisEngine = services.analysisEngine;
|
|
4333
|
+
const live = metricsStore.getLiveEndpoints();
|
|
4334
|
+
const insights = analysisEngine.getInsights();
|
|
4335
|
+
const findings = analysisEngine.getFindings();
|
|
4336
|
+
let totalRequests = 0;
|
|
4337
|
+
let totalDuration = 0;
|
|
4338
|
+
let slowestP95 = 0;
|
|
4339
|
+
for (const ep of live) {
|
|
4340
|
+
totalRequests += ep.summary.totalRequests;
|
|
4341
|
+
totalDuration += ep.summary.p95Ms * ep.summary.totalRequests;
|
|
4342
|
+
if (ep.summary.p95Ms > slowestP95) slowestP95 = ep.summary.p95Ms;
|
|
4343
|
+
}
|
|
4344
|
+
const now = Date.now();
|
|
4345
|
+
return {
|
|
4346
|
+
sdk: TELEMETRY_SDK_NAME,
|
|
4347
|
+
// Stack detection
|
|
4348
|
+
framework: this.framework,
|
|
4349
|
+
package_manager: this.packageManager,
|
|
4350
|
+
js_runtime: this.jsRuntime,
|
|
4351
|
+
framework_detection_candidates: this.detectedDependencies,
|
|
4352
|
+
config_files_detected: this.configFilesDetected,
|
|
4353
|
+
loaded_packages: this.loadedPackages,
|
|
4354
|
+
loaded_package_count: this.loadedPackages.length,
|
|
4355
|
+
adapters_detected: this.adapters,
|
|
4356
|
+
adapters_failed: this.adaptersFailed,
|
|
4357
|
+
// Python SDK
|
|
4358
|
+
python_connected: this.pythonConnected,
|
|
4359
|
+
python_framework: this.pythonFramework,
|
|
4360
|
+
python_adapters: this.pythonAdapters,
|
|
4361
|
+
python_version: this.pythonVersion,
|
|
4362
|
+
// Session metadata
|
|
4363
|
+
is_custom_command: this.isCustomCommand,
|
|
4364
|
+
first_session: readConfig() === null,
|
|
4365
|
+
setup_succeeded: this.setupSucceeded,
|
|
4366
|
+
setup_duration_ms: this.setupDurationMs,
|
|
4367
|
+
session_duration_s: Math.ceil((now - this.startTime) / 1e3),
|
|
4368
|
+
session_duration_ms: now - this.startTime,
|
|
4369
|
+
exit_reason: this.exitReason,
|
|
4370
|
+
// Usage
|
|
4371
|
+
request_count: this.requestCount,
|
|
4372
|
+
error_count: services.errorStore.getAll().length,
|
|
4373
|
+
query_count: services.queryStore.getAll().length,
|
|
4374
|
+
fetch_count: services.fetchStore.getAll().length,
|
|
4375
|
+
endpoint_count: live.length,
|
|
4376
|
+
avg_duration_ms: totalRequests > 0 ? Math.round(totalDuration / totalRequests) : 0,
|
|
4377
|
+
slowest_endpoint_bucket: speedBucket(slowestP95),
|
|
4378
|
+
// Analysis
|
|
4379
|
+
insight_count: insights.length,
|
|
4380
|
+
finding_count: findings.length,
|
|
4381
|
+
insight_types: [...this.insightTypes],
|
|
4382
|
+
rules_triggered: [...this.rulesTriggered],
|
|
4383
|
+
// Dashboard engagement
|
|
4384
|
+
tabs_viewed: [...this.tabsViewed],
|
|
4385
|
+
dashboard_opened: this.dashboardOpened,
|
|
4386
|
+
explain_used: this.explainUsed,
|
|
4387
|
+
// Timing
|
|
4388
|
+
time_to_first_request_ms: this.firstRequestAt ? this.firstRequestAt - this.startTime : null,
|
|
4389
|
+
time_to_dashboard_ms: this.dashboardOpenedAt ? this.dashboardOpenedAt - this.startTime : null
|
|
4390
|
+
};
|
|
4391
|
+
}
|
|
4392
|
+
flush(services) {
|
|
4393
|
+
this.transport.send(TELEMETRY_EVENT_SESSION, this.toPostHogPayload(services));
|
|
4394
|
+
getOrCreateConfig();
|
|
3971
4395
|
}
|
|
3972
4396
|
};
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
4397
|
+
}
|
|
4398
|
+
});
|
|
4399
|
+
|
|
4400
|
+
// src/telemetry/index.ts
|
|
4401
|
+
function recordGraphFeature(feature, detail) {
|
|
4402
|
+
trackEvent(TELEMETRY_EVENT_GRAPH_FEATURE, {
|
|
4403
|
+
feature,
|
|
4404
|
+
...detail ? { detail } : {}
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
function flushSession(services) {
|
|
4408
|
+
if (!isTelemetryEnabled()) return;
|
|
4409
|
+
session.flush(services);
|
|
4410
|
+
}
|
|
4411
|
+
var session;
|
|
4412
|
+
var init_telemetry2 = __esm({
|
|
4413
|
+
"src/telemetry/index.ts"() {
|
|
4414
|
+
"use strict";
|
|
4415
|
+
init_config2();
|
|
4416
|
+
init_transport();
|
|
4417
|
+
init_session();
|
|
4418
|
+
init_session();
|
|
4419
|
+
init_telemetry();
|
|
4420
|
+
init_config2();
|
|
4421
|
+
init_transport();
|
|
4422
|
+
session = new Session();
|
|
4423
|
+
}
|
|
4424
|
+
});
|
|
4425
|
+
|
|
4426
|
+
// src/dashboard/api/ingest.ts
|
|
4427
|
+
function isBrakitBatch(msg) {
|
|
4428
|
+
return typeof msg === "object" && msg !== null && "_brakit" in msg && msg._brakit === true && !("version" in msg);
|
|
4429
|
+
}
|
|
4430
|
+
function isSDKPayload(msg) {
|
|
4431
|
+
return typeof msg === "object" && msg !== null && "_brakit" in msg && "version" in msg && typeof msg.version === "number";
|
|
4432
|
+
}
|
|
4433
|
+
function createIngestHandler(services) {
|
|
4434
|
+
const routeEvent = (event) => {
|
|
4435
|
+
switch (event.type) {
|
|
4436
|
+
case TIMELINE_FETCH:
|
|
4437
|
+
bus.emit("telemetry:fetch", event.data);
|
|
4438
|
+
break;
|
|
4439
|
+
case TIMELINE_LOG:
|
|
4440
|
+
bus.emit("telemetry:log", event.data);
|
|
4441
|
+
break;
|
|
4442
|
+
case TIMELINE_ERROR:
|
|
4443
|
+
bus.emit("telemetry:error", event.data);
|
|
4444
|
+
break;
|
|
4445
|
+
case TIMELINE_QUERY:
|
|
4446
|
+
bus.emit("telemetry:query", event.data);
|
|
4447
|
+
break;
|
|
4448
|
+
}
|
|
4449
|
+
};
|
|
4450
|
+
const { bus, requestStore } = services;
|
|
4451
|
+
const stores = {
|
|
4452
|
+
addQuery: (data) => bus.emit("telemetry:query", data),
|
|
4453
|
+
addFetch: (data) => bus.emit("telemetry:fetch", data),
|
|
4454
|
+
addLog: (data) => bus.emit("telemetry:log", data),
|
|
4455
|
+
addError: (data) => bus.emit("telemetry:error", data),
|
|
4456
|
+
addRequest: (data) => requestStore.add(data)
|
|
4457
|
+
};
|
|
4458
|
+
return (req, res) => {
|
|
4459
|
+
if (req.method !== "POST") {
|
|
4460
|
+
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4463
|
+
const chunks = [];
|
|
4464
|
+
let totalSize = 0;
|
|
4465
|
+
req.on("data", (chunk) => {
|
|
4466
|
+
totalSize += chunk.length;
|
|
4467
|
+
if (totalSize > MAX_INGEST_BYTES) {
|
|
4468
|
+
sendJson(req, res, HTTP_PAYLOAD_TOO_LARGE, { error: "Payload too large" });
|
|
4469
|
+
req.destroy();
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
chunks.push(chunk);
|
|
4473
|
+
});
|
|
4474
|
+
req.on("end", () => {
|
|
4475
|
+
if (res.headersSent) return;
|
|
4476
|
+
try {
|
|
4477
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
4478
|
+
if (isSDKPayload(body)) {
|
|
4479
|
+
for (const event of body.events) {
|
|
4480
|
+
if (event.type === SDK_EVENT_HELLO) {
|
|
4481
|
+
const d = event.data;
|
|
4482
|
+
session.recordPythonStack({
|
|
4483
|
+
framework: String(d.framework ?? "unknown"),
|
|
4484
|
+
adapters: Array.isArray(d.adapters) ? d.adapters.map(String) : [],
|
|
4485
|
+
pythonVersion: String(d.pythonVersion ?? "unknown")
|
|
4486
|
+
});
|
|
4487
|
+
continue;
|
|
3996
4488
|
}
|
|
4489
|
+
routeSDKEvent(event, stores);
|
|
3997
4490
|
}
|
|
4491
|
+
res.writeHead(HTTP_NO_CONTENT);
|
|
4492
|
+
res.end();
|
|
4493
|
+
return;
|
|
3998
4494
|
}
|
|
3999
|
-
if (
|
|
4000
|
-
for (const
|
|
4001
|
-
|
|
4002
|
-
if (queryMetric.endpoints.size < CROSS_ENDPOINT_MIN_ENDPOINTS) continue;
|
|
4003
|
-
const coveragePct = Math.round(queryMetric.endpoints.size / allEndpoints.size * 100);
|
|
4004
|
-
if (coveragePct < CROSS_ENDPOINT_PCT) continue;
|
|
4005
|
-
const info = getQueryInfo(queryMetric.first);
|
|
4006
|
-
const label = info.op + (info.table ? ` ${info.table}` : "");
|
|
4007
|
-
insights.push({
|
|
4008
|
-
severity: "warning",
|
|
4009
|
-
type: "cross-endpoint",
|
|
4010
|
-
title: "Repeated Query Across Endpoints",
|
|
4011
|
-
desc: `${label} runs on ${queryMetric.endpoints.size} of ${allEndpoints.size} endpoints (${coveragePct}%).`,
|
|
4012
|
-
hint: "This query runs on most of your endpoints. Load it once in middleware or cache the result to avoid redundant database calls.",
|
|
4013
|
-
detail: `Endpoints: ${[...queryMetric.endpoints].slice(0, 5).join(", ")}${queryMetric.endpoints.size > 5 ? ` +${queryMetric.endpoints.size - 5} more` : ""}. Total: ${queryMetric.count} executions.`
|
|
4014
|
-
});
|
|
4495
|
+
if (isBrakitBatch(body)) {
|
|
4496
|
+
for (const event of body.events) {
|
|
4497
|
+
routeEvent(event);
|
|
4015
4498
|
}
|
|
4499
|
+
res.writeHead(HTTP_NO_CONTENT);
|
|
4500
|
+
res.end();
|
|
4501
|
+
return;
|
|
4016
4502
|
}
|
|
4017
|
-
|
|
4503
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid batch" });
|
|
4504
|
+
} catch {
|
|
4505
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid JSON" });
|
|
4018
4506
|
}
|
|
4019
|
-
};
|
|
4507
|
+
});
|
|
4508
|
+
req.on("error", () => {
|
|
4509
|
+
if (!res.headersSent) {
|
|
4510
|
+
res.writeHead(HTTP_BAD_REQUEST);
|
|
4511
|
+
res.end();
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4514
|
+
};
|
|
4515
|
+
}
|
|
4516
|
+
var init_ingest = __esm({
|
|
4517
|
+
"src/dashboard/api/ingest.ts"() {
|
|
4518
|
+
"use strict";
|
|
4519
|
+
init_config();
|
|
4520
|
+
init_labels();
|
|
4521
|
+
init_shared2();
|
|
4522
|
+
init_sdk_event_parser();
|
|
4523
|
+
init_labels();
|
|
4524
|
+
init_telemetry2();
|
|
4020
4525
|
}
|
|
4021
4526
|
});
|
|
4022
4527
|
|
|
4023
|
-
// src/
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4528
|
+
// src/dashboard/api/metrics.ts
|
|
4529
|
+
function createMetricsHandler(metricsStore) {
|
|
4530
|
+
return (req, res) => {
|
|
4531
|
+
if (!requireGet(req, res)) return;
|
|
4532
|
+
const url = parseRequestUrl(req);
|
|
4533
|
+
const endpoint = url.searchParams.get("endpoint");
|
|
4534
|
+
if (endpoint) {
|
|
4535
|
+
const ep = metricsStore.getEndpoint(endpoint);
|
|
4536
|
+
sendJson(req, res, HTTP_OK, { endpoints: ep ? [ep] : [] });
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4539
|
+
sendJson(req, res, HTTP_OK, { endpoints: metricsStore.getAll() });
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
var init_metrics = __esm({
|
|
4543
|
+
"src/dashboard/api/metrics.ts"() {
|
|
4027
4544
|
"use strict";
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
check(ctx) {
|
|
4031
|
-
if (!ctx.securityFindings) return [];
|
|
4032
|
-
return ctx.securityFindings.map((finding) => ({
|
|
4033
|
-
severity: finding.severity,
|
|
4034
|
-
type: "security",
|
|
4035
|
-
title: finding.title,
|
|
4036
|
-
desc: finding.desc,
|
|
4037
|
-
hint: finding.hint,
|
|
4038
|
-
detail: finding.detail
|
|
4039
|
-
}));
|
|
4040
|
-
}
|
|
4041
|
-
};
|
|
4545
|
+
init_shared2();
|
|
4546
|
+
init_labels();
|
|
4042
4547
|
}
|
|
4043
4548
|
});
|
|
4044
4549
|
|
|
4045
|
-
// src/
|
|
4046
|
-
|
|
4047
|
-
|
|
4550
|
+
// src/dashboard/api/metrics-live.ts
|
|
4551
|
+
function createLiveMetricsHandler(metricsStore) {
|
|
4552
|
+
return (req, res) => {
|
|
4553
|
+
if (!requireGet(req, res)) return;
|
|
4554
|
+
sendJson(req, res, 200, { endpoints: metricsStore.getLiveEndpoints() });
|
|
4555
|
+
};
|
|
4556
|
+
}
|
|
4557
|
+
var init_metrics_live = __esm({
|
|
4558
|
+
"src/dashboard/api/metrics-live.ts"() {
|
|
4048
4559
|
"use strict";
|
|
4049
|
-
|
|
4050
|
-
init_response_rules();
|
|
4051
|
-
init_reliability_rules();
|
|
4052
|
-
init_pattern_rules();
|
|
4053
|
-
init_security();
|
|
4560
|
+
init_shared2();
|
|
4054
4561
|
}
|
|
4055
4562
|
});
|
|
4056
4563
|
|
|
4057
|
-
// src/
|
|
4058
|
-
function
|
|
4059
|
-
const
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4564
|
+
// src/dashboard/api/activity.ts
|
|
4565
|
+
function buildTimeline(services, requestId) {
|
|
4566
|
+
const fetches = services.fetchStore.getByRequest(requestId);
|
|
4567
|
+
const logs = services.logStore.getByRequest(requestId);
|
|
4568
|
+
const errors = services.errorStore.getByRequest(requestId);
|
|
4569
|
+
const queries = services.queryStore.getByRequest(requestId);
|
|
4570
|
+
const timeline = [];
|
|
4571
|
+
for (const fetch of fetches)
|
|
4572
|
+
timeline.push({ type: TIMELINE_FETCH, timestamp: fetch.timestamp, data: fetch });
|
|
4573
|
+
for (const log of logs)
|
|
4574
|
+
timeline.push({ type: TIMELINE_LOG, timestamp: log.timestamp, data: log });
|
|
4575
|
+
for (const error of errors)
|
|
4576
|
+
timeline.push({ type: TIMELINE_ERROR, timestamp: error.timestamp, data: error });
|
|
4577
|
+
for (const query of queries)
|
|
4578
|
+
timeline.push({ type: TIMELINE_QUERY, timestamp: query.timestamp, data: query });
|
|
4579
|
+
timeline.sort((a, b) => a.timestamp - b.timestamp);
|
|
4580
|
+
return {
|
|
4581
|
+
total: timeline.length,
|
|
4582
|
+
timeline,
|
|
4583
|
+
counts: {
|
|
4584
|
+
fetches: fetches.length,
|
|
4585
|
+
logs: logs.length,
|
|
4586
|
+
errors: errors.length,
|
|
4587
|
+
queries: queries.length
|
|
4588
|
+
}
|
|
4589
|
+
};
|
|
4075
4590
|
}
|
|
4076
|
-
function
|
|
4077
|
-
return
|
|
4591
|
+
function createActivityHandler(services) {
|
|
4592
|
+
return (req, res) => {
|
|
4593
|
+
if (!requireGet(req, res)) return;
|
|
4594
|
+
try {
|
|
4595
|
+
const url = parseRequestUrl(req);
|
|
4596
|
+
const requestId = url.searchParams.get("requestId");
|
|
4597
|
+
const requestIds = url.searchParams.get("requestIds");
|
|
4598
|
+
if (!requestId && !requestIds) {
|
|
4599
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "requestId or requestIds parameter required" });
|
|
4600
|
+
return;
|
|
4601
|
+
}
|
|
4602
|
+
if (requestId) {
|
|
4603
|
+
const result = buildTimeline(services, requestId);
|
|
4604
|
+
sendJson(req, res, HTTP_OK, { requestId, ...result });
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4607
|
+
const ids = (requestIds || "").split(",").filter(Boolean).slice(0, MAX_BATCH_IDS);
|
|
4608
|
+
const activities = {};
|
|
4609
|
+
for (const id of ids) {
|
|
4610
|
+
activities[id] = buildTimeline(services, id);
|
|
4611
|
+
}
|
|
4612
|
+
sendJson(req, res, HTTP_OK, { requestIds: ids, activities });
|
|
4613
|
+
} catch (err) {
|
|
4614
|
+
brakitDebug(`activity handler error: ${err}`);
|
|
4615
|
+
if (!res.headersSent) {
|
|
4616
|
+
sendJson(req, res, HTTP_INTERNAL_ERROR, { error: "Internal error" });
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4078
4620
|
}
|
|
4079
|
-
var
|
|
4080
|
-
|
|
4621
|
+
var MAX_BATCH_IDS;
|
|
4622
|
+
var init_activity = __esm({
|
|
4623
|
+
"src/dashboard/api/activity.ts"() {
|
|
4081
4624
|
"use strict";
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4625
|
+
init_shared2();
|
|
4626
|
+
init_labels();
|
|
4627
|
+
init_log();
|
|
4628
|
+
MAX_BATCH_IDS = 50;
|
|
4085
4629
|
}
|
|
4086
4630
|
});
|
|
4087
4631
|
|
|
4088
|
-
// src/
|
|
4089
|
-
var
|
|
4090
|
-
"src/
|
|
4632
|
+
// src/dashboard/api/index.ts
|
|
4633
|
+
var init_api = __esm({
|
|
4634
|
+
"src/dashboard/api/index.ts"() {
|
|
4091
4635
|
"use strict";
|
|
4092
|
-
|
|
4636
|
+
init_handlers();
|
|
4637
|
+
init_ingest();
|
|
4638
|
+
init_metrics();
|
|
4639
|
+
init_metrics_live();
|
|
4640
|
+
init_activity();
|
|
4093
4641
|
}
|
|
4094
4642
|
});
|
|
4095
4643
|
|
|
4096
|
-
// src/
|
|
4097
|
-
function
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4644
|
+
// src/dashboard/api/issues.ts
|
|
4645
|
+
function createIssuesHandler(issueStore) {
|
|
4646
|
+
return (req, res) => {
|
|
4647
|
+
if (!requireGet(req, res)) return;
|
|
4648
|
+
const url = parseRequestUrl(req);
|
|
4649
|
+
const stateParam = url.searchParams.get("state");
|
|
4650
|
+
const categoryParam = url.searchParams.get("category");
|
|
4651
|
+
let issues;
|
|
4652
|
+
if (stateParam && isValidIssueState(stateParam)) {
|
|
4653
|
+
issues = issueStore.getByState(stateParam);
|
|
4654
|
+
} else if (categoryParam && isValidIssueCategory(categoryParam)) {
|
|
4655
|
+
issues = issueStore.getByCategory(categoryParam);
|
|
4656
|
+
} else {
|
|
4657
|
+
issues = issueStore.getAll();
|
|
4658
|
+
}
|
|
4659
|
+
sendJson(req, res, HTTP_OK, { issues });
|
|
4660
|
+
};
|
|
4101
4661
|
}
|
|
4102
|
-
function
|
|
4103
|
-
return {
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4662
|
+
function createFindingsHandler(issueStore) {
|
|
4663
|
+
return (req, res) => {
|
|
4664
|
+
if (!requireGet(req, res)) return;
|
|
4665
|
+
const url = parseRequestUrl(req);
|
|
4666
|
+
const stateParam = url.searchParams.get("state");
|
|
4667
|
+
let issues;
|
|
4668
|
+
if (stateParam && isValidIssueState(stateParam)) {
|
|
4669
|
+
issues = issueStore.getByState(stateParam);
|
|
4670
|
+
} else {
|
|
4671
|
+
issues = issueStore.getAll();
|
|
4672
|
+
}
|
|
4673
|
+
sendJson(req, res, HTTP_OK, {
|
|
4674
|
+
total: issues.length,
|
|
4675
|
+
findings: issues
|
|
4676
|
+
});
|
|
4112
4677
|
};
|
|
4113
4678
|
}
|
|
4114
|
-
function
|
|
4115
|
-
return {
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4679
|
+
function createIssuesReportHandler(issueStore, eventBus) {
|
|
4680
|
+
return async (req, res) => {
|
|
4681
|
+
if (req.method !== "POST") {
|
|
4682
|
+
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
4683
|
+
return;
|
|
4684
|
+
}
|
|
4685
|
+
const body = await readJsonBody(req, res);
|
|
4686
|
+
if (!body) return;
|
|
4687
|
+
const { findingId, status, notes } = body;
|
|
4688
|
+
if (!findingId || typeof findingId !== "string") {
|
|
4689
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "findingId is required" });
|
|
4690
|
+
return;
|
|
4691
|
+
}
|
|
4692
|
+
if (!isValidAiFixStatus(status)) {
|
|
4693
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "status must be 'fixed' or 'wont_fix'" });
|
|
4694
|
+
return;
|
|
4695
|
+
}
|
|
4696
|
+
if (!notes || typeof notes !== "string") {
|
|
4697
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "notes is required" });
|
|
4698
|
+
return;
|
|
4699
|
+
}
|
|
4700
|
+
if (issueStore.reportFix(findingId, status, notes)) {
|
|
4701
|
+
eventBus.emit("issues:changed", issueStore.getAll());
|
|
4702
|
+
sendJson(req, res, HTTP_OK, { ok: true });
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
sendJson(req, res, HTTP_NOT_FOUND, { error: "Finding not found" });
|
|
4124
4706
|
};
|
|
4125
4707
|
}
|
|
4126
|
-
var
|
|
4127
|
-
"src/
|
|
4708
|
+
var init_issues = __esm({
|
|
4709
|
+
"src/dashboard/api/issues.ts"() {
|
|
4128
4710
|
"use strict";
|
|
4129
|
-
|
|
4711
|
+
init_shared2();
|
|
4712
|
+
init_type_guards();
|
|
4713
|
+
init_labels();
|
|
4130
4714
|
}
|
|
4131
4715
|
});
|
|
4132
4716
|
|
|
4133
|
-
// src/
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4717
|
+
// src/dashboard/api/graph.ts
|
|
4718
|
+
function createGraphHandler(services) {
|
|
4719
|
+
return (req, res) => {
|
|
4720
|
+
if (!requireGet(req, res)) return;
|
|
4721
|
+
const url = parseRequestUrl(req);
|
|
4722
|
+
const rawCluster = url.searchParams.get("cluster") ?? void 0;
|
|
4723
|
+
const rawNode = url.searchParams.get("node") ?? void 0;
|
|
4724
|
+
const rawLevel = url.searchParams.get("level") ?? void 0;
|
|
4725
|
+
const rawGrouping = url.searchParams.get("grouping") ?? void 0;
|
|
4726
|
+
const cluster = rawCluster && rawCluster.length <= MAX_PARAM_LENGTH ? rawCluster : void 0;
|
|
4727
|
+
const node = rawNode && rawNode.length <= MAX_PARAM_LENGTH ? rawNode : void 0;
|
|
4728
|
+
const level = rawLevel && VALID_LEVELS.has(rawLevel) ? rawLevel : void 0;
|
|
4729
|
+
const grouping = rawGrouping && VALID_GROUPINGS.has(rawGrouping) ? rawGrouping : void 0;
|
|
4730
|
+
const { graphBuilder, metricsStore } = services;
|
|
4731
|
+
graphBuilder.enrichWithMetrics((endpointKey) => {
|
|
4732
|
+
const metrics = metricsStore.getEndpoint(endpointKey);
|
|
4733
|
+
if (!metrics || metrics.sessions.length === 0) return void 0;
|
|
4734
|
+
const latest = metrics.sessions[metrics.sessions.length - 1];
|
|
4735
|
+
return latest.p95DurationMs;
|
|
4736
|
+
});
|
|
4737
|
+
const data = graphBuilder.getApiResponse({ cluster, node, level, grouping });
|
|
4738
|
+
sendJson(req, res, HTTP_OK, data);
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
var VALID_LEVELS, VALID_GROUPINGS, MAX_PARAM_LENGTH;
|
|
4742
|
+
var init_graph = __esm({
|
|
4743
|
+
"src/dashboard/api/graph.ts"() {
|
|
4137
4744
|
"use strict";
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
init_issue_mappers();
|
|
4144
|
-
init_issue_id();
|
|
4145
|
-
init_prepare();
|
|
4146
|
-
AnalysisEngine = class {
|
|
4147
|
-
constructor(services, debounceMs = ANALYSIS_DEBOUNCE_MS) {
|
|
4148
|
-
this.services = services;
|
|
4149
|
-
this.debounceMs = debounceMs;
|
|
4150
|
-
this.cachedInsights = [];
|
|
4151
|
-
this.cachedFindings = [];
|
|
4152
|
-
this.debounceTimer = null;
|
|
4153
|
-
this.subs = new SubscriptionBag();
|
|
4154
|
-
this.scanner = createDefaultScanner();
|
|
4155
|
-
}
|
|
4156
|
-
start() {
|
|
4157
|
-
const bus = this.services.bus;
|
|
4158
|
-
this.subs.add(bus.on("request:completed", () => this.scheduleRecompute()));
|
|
4159
|
-
this.subs.add(bus.on("telemetry:query", () => this.scheduleRecompute()));
|
|
4160
|
-
this.subs.add(bus.on("telemetry:error", () => this.scheduleRecompute()));
|
|
4161
|
-
this.subs.add(bus.on("telemetry:log", () => this.scheduleRecompute()));
|
|
4162
|
-
}
|
|
4163
|
-
stop() {
|
|
4164
|
-
this.subs.dispose();
|
|
4165
|
-
if (this.debounceTimer) {
|
|
4166
|
-
clearTimeout(this.debounceTimer);
|
|
4167
|
-
this.debounceTimer = null;
|
|
4168
|
-
}
|
|
4169
|
-
}
|
|
4170
|
-
getInsights() {
|
|
4171
|
-
return this.cachedInsights;
|
|
4172
|
-
}
|
|
4173
|
-
getFindings() {
|
|
4174
|
-
return this.cachedFindings;
|
|
4175
|
-
}
|
|
4176
|
-
scheduleRecompute() {
|
|
4177
|
-
if (this.debounceTimer) return;
|
|
4178
|
-
this.debounceTimer = setTimeout(() => {
|
|
4179
|
-
this.debounceTimer = null;
|
|
4180
|
-
this.recompute();
|
|
4181
|
-
}, this.debounceMs);
|
|
4182
|
-
}
|
|
4183
|
-
recompute() {
|
|
4184
|
-
const allRequests = this.services.requestStore.getAll();
|
|
4185
|
-
const queries = this.services.queryStore.getAll();
|
|
4186
|
-
const errors = this.services.errorStore.getAll();
|
|
4187
|
-
const logs = this.services.logStore.getAll();
|
|
4188
|
-
const fetches = this.services.fetchStore.getAll();
|
|
4189
|
-
const requests = keepRecentPerEndpoint(allRequests);
|
|
4190
|
-
const flows = groupRequestsIntoFlows(requests);
|
|
4191
|
-
this.cachedFindings = this.scanner.scan({ requests, logs });
|
|
4192
|
-
this.cachedInsights = computeInsights({
|
|
4193
|
-
requests,
|
|
4194
|
-
queries,
|
|
4195
|
-
errors,
|
|
4196
|
-
flows,
|
|
4197
|
-
fetches,
|
|
4198
|
-
previousMetrics: this.services.metricsStore.getAll(),
|
|
4199
|
-
securityFindings: this.cachedFindings
|
|
4200
|
-
});
|
|
4201
|
-
const issueStore = this.services.issueStore;
|
|
4202
|
-
const currentIssueIds = /* @__PURE__ */ new Set();
|
|
4203
|
-
for (const finding of this.cachedFindings) {
|
|
4204
|
-
const issue = securityFindingToIssue(finding);
|
|
4205
|
-
issueStore.upsert(issue, "passive");
|
|
4206
|
-
currentIssueIds.add(computeIssueId(issue));
|
|
4207
|
-
}
|
|
4208
|
-
for (const insight of this.cachedInsights) {
|
|
4209
|
-
const issue = insightToIssue(insight);
|
|
4210
|
-
issueStore.upsert(issue, "passive");
|
|
4211
|
-
currentIssueIds.add(computeIssueId(issue));
|
|
4212
|
-
}
|
|
4213
|
-
const activeEndpoints = extractActiveEndpoints(allRequests);
|
|
4214
|
-
issueStore.reconcile(currentIssueIds, activeEndpoints);
|
|
4215
|
-
const update = {
|
|
4216
|
-
insights: this.cachedInsights,
|
|
4217
|
-
findings: this.cachedFindings,
|
|
4218
|
-
issues: issueStore.getAll()
|
|
4219
|
-
};
|
|
4220
|
-
this.services.bus.emit("analysis:updated", update);
|
|
4221
|
-
}
|
|
4222
|
-
};
|
|
4745
|
+
init_labels();
|
|
4746
|
+
init_shared2();
|
|
4747
|
+
VALID_LEVELS = /* @__PURE__ */ new Set(["endpoints", "clusters"]);
|
|
4748
|
+
VALID_GROUPINGS = /* @__PURE__ */ new Set(["path", "auth-boundary", "data-domain"]);
|
|
4749
|
+
MAX_PARAM_LENGTH = 200;
|
|
4223
4750
|
}
|
|
4224
4751
|
});
|
|
4225
4752
|
|
|
4226
|
-
// src/
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4753
|
+
// src/dashboard/sse.ts
|
|
4754
|
+
function createSSEHandler(services) {
|
|
4755
|
+
const clients = /* @__PURE__ */ new Set();
|
|
4756
|
+
function broadcast(eventType, data) {
|
|
4757
|
+
if (clients.size === 0) return;
|
|
4758
|
+
const frame = eventType ? `event: ${eventType}
|
|
4759
|
+
data: ${data}
|
|
4760
|
+
|
|
4761
|
+
` : `data: ${data}
|
|
4762
|
+
|
|
4763
|
+
`;
|
|
4764
|
+
for (const client of clients) {
|
|
4765
|
+
if (client.res.destroyed) {
|
|
4766
|
+
clients.delete(client);
|
|
4767
|
+
continue;
|
|
4768
|
+
}
|
|
4769
|
+
try {
|
|
4770
|
+
client.res.write(frame);
|
|
4771
|
+
} catch {
|
|
4772
|
+
clients.delete(client);
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
const bus = services.bus;
|
|
4777
|
+
bus.on("request:completed", (r) => broadcast(null, JSON.stringify(r)));
|
|
4778
|
+
bus.on("telemetry:fetch", (e) => broadcast(SSE_EVENT_FETCH, JSON.stringify(e)));
|
|
4779
|
+
bus.on("telemetry:log", (e) => broadcast(SSE_EVENT_LOG, JSON.stringify(e)));
|
|
4780
|
+
bus.on("telemetry:error", (e) => broadcast(SSE_EVENT_ERROR, JSON.stringify(e)));
|
|
4781
|
+
bus.on("telemetry:query", (e) => broadcast(SSE_EVENT_QUERY, JSON.stringify(e)));
|
|
4782
|
+
bus.on("analysis:updated", ({ issues }) => {
|
|
4783
|
+
broadcast(SSE_EVENT_ISSUES, JSON.stringify(issues));
|
|
4784
|
+
});
|
|
4785
|
+
bus.on("issues:changed", (issues) => {
|
|
4786
|
+
broadcast(SSE_EVENT_ISSUES, JSON.stringify(issues));
|
|
4787
|
+
});
|
|
4788
|
+
return (req, res) => {
|
|
4789
|
+
const headers2 = {
|
|
4790
|
+
"content-type": "text/event-stream",
|
|
4791
|
+
"cache-control": "no-cache",
|
|
4792
|
+
connection: "keep-alive"
|
|
4793
|
+
};
|
|
4794
|
+
const corsOrigin = getCorsOrigin(req);
|
|
4795
|
+
if (corsOrigin) {
|
|
4796
|
+
headers2["access-control-allow-origin"] = corsOrigin;
|
|
4797
|
+
}
|
|
4798
|
+
res.writeHead(HTTP_OK, headers2);
|
|
4799
|
+
res.write(":ok\n\n");
|
|
4800
|
+
const heartbeat = setInterval(() => {
|
|
4801
|
+
if (res.destroyed) {
|
|
4802
|
+
clearInterval(heartbeat);
|
|
4803
|
+
clients.delete(client);
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
try {
|
|
4807
|
+
res.write(":heartbeat\n\n");
|
|
4808
|
+
} catch {
|
|
4809
|
+
clearInterval(heartbeat);
|
|
4810
|
+
clients.delete(client);
|
|
4811
|
+
}
|
|
4812
|
+
}, SSE_HEARTBEAT_INTERVAL_MS);
|
|
4813
|
+
heartbeat.unref();
|
|
4814
|
+
const client = { res, heartbeat };
|
|
4815
|
+
clients.add(client);
|
|
4816
|
+
req.on("close", () => {
|
|
4817
|
+
clearInterval(heartbeat);
|
|
4818
|
+
clients.delete(client);
|
|
4819
|
+
});
|
|
4820
|
+
};
|
|
4821
|
+
}
|
|
4822
|
+
var init_sse = __esm({
|
|
4823
|
+
"src/dashboard/sse.ts"() {
|
|
4230
4824
|
"use strict";
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
init_rules();
|
|
4235
|
-
init_engine();
|
|
4236
|
-
init_insights2();
|
|
4237
|
-
init_insights();
|
|
4238
|
-
VERSION = "0.10.0";
|
|
4825
|
+
init_constants();
|
|
4826
|
+
init_labels();
|
|
4827
|
+
init_shared2();
|
|
4239
4828
|
}
|
|
4240
4829
|
});
|
|
4241
4830
|
|
|
@@ -4255,7 +4844,10 @@ function getBaseStyles() {
|
|
|
4255
4844
|
--red:#dc2626;
|
|
4256
4845
|
--cyan:#0891b2;
|
|
4257
4846
|
--green-bg:rgba(22,163,74,0.08);--green-bg-subtle:rgba(22,163,74,0.05);--green-border:rgba(22,163,74,0.2);--green-border-subtle:rgba(22,163,74,0.15);
|
|
4258
|
-
--amber-bg:rgba(217,119,6,0.
|
|
4847
|
+
--amber-bg:rgba(217,119,6,0.08);--amber-border:rgba(217,119,6,0.15);
|
|
4848
|
+
--red-bg:rgba(220,38,38,0.08);--red-border:rgba(220,38,38,0.2);
|
|
4849
|
+
--blue-bg:rgba(37,99,235,0.08);--cyan-bg:rgba(8,145,178,0.07);
|
|
4850
|
+
--accent-bg:rgba(99,102,241,0.08);
|
|
4259
4851
|
--sidebar-width:232px;--header-height:52px;
|
|
4260
4852
|
--radius:8px;--radius-sm:6px;
|
|
4261
4853
|
--shadow-sm:0 1px 3px rgba(0,0,0,0.06),0 1px 2px rgba(0,0,0,0.03);
|
|
@@ -4312,6 +4904,7 @@ function getLayoutStyles() {
|
|
|
4312
4904
|
.sidebar-logo .logo-version{font-weight:400;font-size:11px;color:var(--text-muted);margin-left:8px;letter-spacing:0}
|
|
4313
4905
|
.sidebar-nav{padding:12px;flex:1}
|
|
4314
4906
|
.sidebar-section{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);padding:16px 12px 8px}
|
|
4907
|
+
.sidebar-divider{height:1px;background:var(--border-subtle);margin:8px 12px}
|
|
4315
4908
|
.sidebar-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:var(--radius);color:var(--text-dim);font-size:14px;font-weight:500;cursor:pointer;transition:all .15s;border:none;background:transparent;width:100%;text-align:left;font-family:var(--sans)}
|
|
4316
4909
|
.sidebar-item:hover{background:var(--bg-hover);color:var(--text)}
|
|
4317
4910
|
.sidebar-item.active{background:var(--bg-active);color:var(--accent)}
|
|
@@ -4345,7 +4938,7 @@ function getLayoutStyles() {
|
|
|
4345
4938
|
/* Content */
|
|
4346
4939
|
.main-content{flex:1;overflow-y:auto}
|
|
4347
4940
|
bk-dashboard{display:contents}
|
|
4348
|
-
bk-overview-view,bk-flows-view,bk-requests-view,bk-fetches-view,bk-queries-view,bk-errors-view,bk-logs-view,bk-security-view,bk-performance-view,bk-timeline-panel,bk-empty-state{display:block}
|
|
4941
|
+
bk-overview-view,bk-flows-view,bk-requests-view,bk-fetches-view,bk-queries-view,bk-errors-view,bk-logs-view,bk-security-view,bk-performance-view,bk-explorer-view,bk-insights-view,bk-timeline-panel,bk-empty-state{display:block}
|
|
4349
4942
|
bk-graph-view{display:block}
|
|
4350
4943
|
bk-method-badge,bk-status-pill,bk-duration-label,bk-copy-button{display:inline-flex;flex-shrink:0}
|
|
4351
4944
|
bk-stat-card{display:inline-flex}
|
|
@@ -4732,53 +5325,25 @@ var init_graph2 = __esm({
|
|
|
4732
5325
|
// src/dashboard/styles/overview.ts
|
|
4733
5326
|
function getOverviewStyles() {
|
|
4734
5327
|
return `
|
|
4735
|
-
|
|
4736
|
-
.ov-container{padding:24px 28px}
|
|
4737
|
-
|
|
4738
|
-
/* Summary banner */
|
|
4739
|
-
.ov-summary{display:flex;gap:10px;margin-bottom:24px;flex-wrap:wrap}
|
|
4740
|
-
.ov-stat{display:flex;flex-direction:column;gap:4px;flex:1;min-width:100px;padding:16px 18px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-sm);transition:box-shadow var(--transition, .15s ease)}
|
|
4741
|
-
.ov-stat:hover{box-shadow:var(--shadow-md)}
|
|
4742
|
-
.ov-stat-value{font-size:22px;font-weight:700;font-family:var(--mono);color:var(--text);line-height:1.2}
|
|
4743
|
-
.ov-stat-label{font-size:10px;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);font-weight:600}
|
|
4744
|
-
|
|
4745
|
-
/* Section header */
|
|
4746
|
-
.ov-section-title{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);margin-bottom:12px;display:flex;align-items:center;gap:8px}
|
|
4747
|
-
.ov-issue-count{font-size:11px;font-family:var(--mono);color:var(--text-dim);background:var(--bg-muted);border:1px solid var(--border);padding:1px 8px;border-radius:10px}
|
|
4748
|
-
|
|
4749
|
-
/* Insight cards */
|
|
4750
|
-
.ov-cards{display:flex;flex-direction:column;gap:8px}
|
|
4751
|
-
.ov-card{display:flex;align-items:flex-start;gap:14px;padding:16px 20px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;transition:all var(--transition, .15s ease);box-shadow:var(--shadow-sm)}
|
|
4752
|
-
.ov-card:hover{background:var(--bg-hover);border-color:var(--border-light);box-shadow:var(--shadow-md);transform:translateY(-1px)}
|
|
4753
|
-
.ov-card-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:10px;border-radius:50%;margin-top:2px}
|
|
4754
|
-
.ov-card-icon.critical{background:rgba(220,38,38,.08);color:var(--red)}
|
|
4755
|
-
.ov-card-icon.warning{background:rgba(217,119,6,.08);color:var(--amber)}
|
|
4756
|
-
.ov-card-icon.info{background:rgba(37,99,235,.08);color:var(--blue)}
|
|
4757
|
-
.ov-card-icon.resolved{background:var(--green-bg);color:var(--green)}
|
|
4758
|
-
.ov-card-body{flex:1;min-width:0}
|
|
4759
|
-
.ov-card-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:2px}
|
|
4760
|
-
.ov-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
|
|
4761
|
-
.ov-card-detail{font-size:11px;font-family:var(--mono);color:var(--text-muted);margin-top:6px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);line-height:1.5}
|
|
4762
|
-
.ov-card-desc strong{color:var(--text);font-family:var(--mono);font-weight:600}
|
|
4763
|
-
.ov-card-arrow{color:var(--text-muted);font-size:12px;flex-shrink:0;margin-top:2px;font-family:var(--mono);transition:transform .15s}
|
|
4764
|
-
|
|
4765
|
-
/* Expanded card */
|
|
4766
|
-
.ov-card.expanded{border-color:var(--border-light);box-shadow:var(--shadow-md)}
|
|
4767
|
-
.ov-card-expand{display:none;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}
|
|
4768
|
-
.ov-card-hint{font-size:12px;color:var(--text-dim);line-height:1.5;margin-bottom:10px}
|
|
4769
|
-
.ov-card-link{font-size:12px;font-weight:600;color:var(--blue);cursor:pointer;display:inline-block;padding:4px 0}
|
|
4770
|
-
.ov-card-link:hover{text-decoration:underline}
|
|
4771
|
-
.ov-detail-label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
|
|
4772
|
-
.ov-detail-item{font-size:12px;color:var(--text);font-family:var(--mono);padding:2px 0}
|
|
4773
|
-
|
|
4774
|
-
/* All-clear banner */
|
|
4775
|
-
.ov-clear{display:flex;align-items:center;gap:12px;padding:16px 20px;background:var(--green-bg-subtle);border:1px solid var(--green-border);border-radius:var(--radius);color:var(--green);font-size:13px;font-weight:500}
|
|
4776
|
-
.ov-clear-icon{font-size:16px}
|
|
5328
|
+
.ov-container{padding:28px}
|
|
4777
5329
|
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
.ov-card-
|
|
4781
|
-
.ov-card-
|
|
5330
|
+
.ov-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
|
5331
|
+
|
|
5332
|
+
.ov-card-nav{display:flex;align-items:center;gap:16px;padding:20px 24px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;cursor:pointer;transition:all .18s ease;box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
|
5333
|
+
.ov-card-nav:hover{border-color:var(--border-light);box-shadow:0 4px 16px rgba(0,0,0,.06);transform:translateY(-2px)}
|
|
5334
|
+
.ov-card-nav:active{transform:translateY(0)}
|
|
5335
|
+
|
|
5336
|
+
.ov-card-empty{opacity:.55}
|
|
5337
|
+
.ov-card-empty:hover{opacity:.8}
|
|
5338
|
+
|
|
5339
|
+
.ov-card-icon-lg{font-size:22px;width:40px;height:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--bg-muted);border-radius:10px;color:var(--text-muted)}
|
|
5340
|
+
|
|
5341
|
+
.ov-card-content{flex:1;min-width:0}
|
|
5342
|
+
.ov-card-headline{font-size:16px;font-weight:700;color:var(--text);font-family:var(--mono);line-height:1.3}
|
|
5343
|
+
.ov-card-context{font-size:12px;color:var(--text-muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
5344
|
+
|
|
5345
|
+
.ov-card-arrow{font-size:16px;color:var(--text-muted);opacity:0;transition:opacity .15s,transform .15s;flex-shrink:0}
|
|
5346
|
+
.ov-card-nav:hover .ov-card-arrow{opacity:1;transform:translateX(2px)}
|
|
4782
5347
|
`;
|
|
4783
5348
|
}
|
|
4784
5349
|
var init_overview = __esm({
|
|
@@ -5158,325 +5723,181 @@ function getGraphViewStyles() {
|
|
|
5158
5723
|
.graph-detail-empty{font-size:12px;color:var(--text-muted);padding:16px;text-align:center}
|
|
5159
5724
|
|
|
5160
5725
|
/* Pulse animation for critical security badges */
|
|
5161
|
-
@keyframes graph-pulse{0%,100%{opacity:1}50%{opacity:0.5}}
|
|
5162
|
-
.graph-pulse{animation:graph-pulse 2s ease-in-out infinite}
|
|
5163
|
-
|
|
5164
|
-
/* Flow edge animation */
|
|
5165
|
-
@keyframes graph-flow-dash{to{stroke-dashoffset:-24}}
|
|
5166
|
-
.graph-flow-edge{animation:graph-flow-dash 1s linear infinite}
|
|
5167
|
-
`;
|
|
5168
|
-
}
|
|
5169
|
-
var init_graph_view = __esm({
|
|
5170
|
-
"src/dashboard/styles/graph-view.ts"() {
|
|
5171
|
-
"use strict";
|
|
5172
|
-
}
|
|
5173
|
-
});
|
|
5174
|
-
|
|
5175
|
-
// src/dashboard/styles.ts
|
|
5176
|
-
function getStyles() {
|
|
5177
|
-
return getBaseStyles() + getLayoutStyles() + getFlowStyles() + getRequestStyles() + getPerformanceStyles() + getOverviewStyles() + getSecurityStyles() + getTimelineStyles() + getGraphViewStyles();
|
|
5178
|
-
}
|
|
5179
|
-
var init_styles = __esm({
|
|
5180
|
-
"src/dashboard/styles.ts"() {
|
|
5181
|
-
"use strict";
|
|
5182
|
-
init_base();
|
|
5183
|
-
init_layout();
|
|
5184
|
-
init_flows();
|
|
5185
|
-
init_requests();
|
|
5186
|
-
init_graph2();
|
|
5187
|
-
init_overview();
|
|
5188
|
-
init_security2();
|
|
5189
|
-
init_timeline();
|
|
5190
|
-
init_graph_view();
|
|
5191
|
-
}
|
|
5192
|
-
});
|
|
5193
|
-
|
|
5194
|
-
// src/dashboard/layout.ts
|
|
5195
|
-
function getLayoutHtml() {
|
|
5196
|
-
return `<bk-dashboard></bk-dashboard>`;
|
|
5197
|
-
}
|
|
5198
|
-
var init_layout2 = __esm({
|
|
5199
|
-
"src/dashboard/layout.ts"() {
|
|
5200
|
-
"use strict";
|
|
5201
|
-
}
|
|
5202
|
-
});
|
|
5203
|
-
|
|
5204
|
-
// src/dashboard/page.ts
|
|
5205
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
5206
|
-
import { resolve as resolve3, dirname } from "path";
|
|
5207
|
-
import { fileURLToPath } from "url";
|
|
5208
|
-
function getClientBundle() {
|
|
5209
|
-
if (clientBundle) return clientBundle;
|
|
5210
|
-
const bundlePath = resolve3(__dirname, "../dashboard-client.global.js");
|
|
5211
|
-
clientBundle = readFileSync3(bundlePath, "utf-8");
|
|
5212
|
-
return clientBundle;
|
|
5213
|
-
}
|
|
5214
|
-
function getDashboardHtml(config) {
|
|
5215
|
-
return `<!DOCTYPE html>
|
|
5216
|
-
<html lang="en">
|
|
5217
|
-
<head>
|
|
5218
|
-
<meta charset="UTF-8">
|
|
5219
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5220
|
-
<title>brakit</title>
|
|
5221
|
-
<style>${getStyles()}</style>
|
|
5222
|
-
</head>
|
|
5223
|
-
<body>
|
|
5224
|
-
${getLayoutHtml()}
|
|
5225
|
-
<script>window.__BRAKIT_CONFIG__={port:${config.proxyPort},version:"${VERSION}"};</script>
|
|
5226
|
-
<script>${getClientBundle()}</script>
|
|
5227
|
-
</body>
|
|
5228
|
-
</html>`;
|
|
5229
|
-
}
|
|
5230
|
-
var __dirname, clientBundle;
|
|
5231
|
-
var init_page = __esm({
|
|
5232
|
-
"src/dashboard/page.ts"() {
|
|
5233
|
-
"use strict";
|
|
5234
|
-
init_src();
|
|
5235
|
-
init_styles();
|
|
5236
|
-
init_layout2();
|
|
5237
|
-
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
5238
|
-
clientBundle = null;
|
|
5239
|
-
}
|
|
5240
|
-
});
|
|
5241
|
-
|
|
5242
|
-
// src/telemetry/config.ts
|
|
5243
|
-
import { homedir as homedir2, platform } from "os";
|
|
5244
|
-
import { join as join3 } from "path";
|
|
5245
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5246
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
5247
|
-
function readConfig() {
|
|
5248
|
-
try {
|
|
5249
|
-
if (!existsSync5(CONFIG_PATH)) return null;
|
|
5250
|
-
return JSON.parse(readFileSync4(CONFIG_PATH, "utf-8"));
|
|
5251
|
-
} catch {
|
|
5252
|
-
return null;
|
|
5253
|
-
}
|
|
5254
|
-
}
|
|
5255
|
-
function writeConfig(config) {
|
|
5256
|
-
try {
|
|
5257
|
-
if (!existsSync5(CONFIG_DIR))
|
|
5258
|
-
mkdirSync3(CONFIG_DIR, { recursive: true, ...IS_WINDOWS ? {} : { mode: DIR_MODE_OWNER_ONLY } });
|
|
5259
|
-
writeFileSync3(
|
|
5260
|
-
CONFIG_PATH,
|
|
5261
|
-
JSON.stringify(config, null, 2) + "\n",
|
|
5262
|
-
IS_WINDOWS ? {} : { mode: FILE_MODE_OWNER_ONLY }
|
|
5263
|
-
);
|
|
5264
|
-
} catch (err) {
|
|
5265
|
-
if (process.env.BRAKIT_DEBUG) {
|
|
5266
|
-
process.stderr.write(`[brakit] config write failed: ${err?.message ?? err}
|
|
5267
|
-
`);
|
|
5268
|
-
}
|
|
5269
|
-
}
|
|
5270
|
-
}
|
|
5271
|
-
function getOrCreateConfig() {
|
|
5272
|
-
const existing = readConfig();
|
|
5273
|
-
if (existing && typeof existing.telemetry === "boolean" && existing.anonymousId) {
|
|
5274
|
-
return existing;
|
|
5275
|
-
}
|
|
5276
|
-
const config = { telemetry: true, anonymousId: randomUUID5() };
|
|
5277
|
-
writeConfig(config);
|
|
5278
|
-
return config;
|
|
5279
|
-
}
|
|
5280
|
-
function isTelemetryEnabled() {
|
|
5281
|
-
if (cachedEnabled !== null) return cachedEnabled;
|
|
5282
|
-
const env = process.env.BRAKIT_TELEMETRY;
|
|
5283
|
-
if (env !== void 0) {
|
|
5284
|
-
cachedEnabled = env !== "false" && env !== "0" && env !== "off";
|
|
5285
|
-
return cachedEnabled;
|
|
5286
|
-
}
|
|
5287
|
-
cachedEnabled = readConfig()?.telemetry ?? true;
|
|
5288
|
-
return cachedEnabled;
|
|
5289
|
-
}
|
|
5290
|
-
var IS_WINDOWS, CONFIG_DIR, CONFIG_PATH, cachedEnabled;
|
|
5291
|
-
var init_config2 = __esm({
|
|
5292
|
-
"src/telemetry/config.ts"() {
|
|
5293
|
-
"use strict";
|
|
5294
|
-
init_features();
|
|
5295
|
-
IS_WINDOWS = platform() === "win32";
|
|
5296
|
-
CONFIG_DIR = join3(homedir2(), ".brakit");
|
|
5297
|
-
CONFIG_PATH = join3(CONFIG_DIR, "config.json");
|
|
5298
|
-
cachedEnabled = null;
|
|
5299
|
-
}
|
|
5300
|
-
});
|
|
5301
|
-
|
|
5302
|
-
// src/telemetry/index.ts
|
|
5303
|
-
import { platform as platform2, release, arch } from "os";
|
|
5304
|
-
import { spawn } from "child_process";
|
|
5305
|
-
function commonProperties() {
|
|
5306
|
-
return {
|
|
5307
|
-
brakit_version: VERSION,
|
|
5308
|
-
node_version: process.version,
|
|
5309
|
-
os: `${platform2()}-${release()}`,
|
|
5310
|
-
arch: arch(),
|
|
5311
|
-
$lib: "brakit",
|
|
5312
|
-
$process_person_profile: false,
|
|
5313
|
-
$geoip_disable: true
|
|
5314
|
-
};
|
|
5315
|
-
}
|
|
5316
|
-
function sendToPosthog(event, properties) {
|
|
5317
|
-
if (!isTelemetryEnabled()) return;
|
|
5318
|
-
const config = getOrCreateConfig();
|
|
5319
|
-
const payload = {
|
|
5320
|
-
api_key: POSTHOG_KEY,
|
|
5321
|
-
event,
|
|
5322
|
-
distinct_id: config.anonymousId,
|
|
5323
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5324
|
-
properties: { ...commonProperties(), ...properties }
|
|
5325
|
-
};
|
|
5326
|
-
try {
|
|
5327
|
-
const body = JSON.stringify(payload);
|
|
5328
|
-
const url = `${POSTHOG_HOST}${POSTHOG_CAPTURE_PATH}`;
|
|
5329
|
-
const child = spawn(
|
|
5330
|
-
process.execPath,
|
|
5331
|
-
[
|
|
5332
|
-
"-e",
|
|
5333
|
-
`fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(body)},signal:AbortSignal.timeout(${POSTHOG_REQUEST_TIMEOUT_MS})}).catch(()=>{})`
|
|
5334
|
-
],
|
|
5335
|
-
{ detached: true, stdio: "ignore" }
|
|
5336
|
-
);
|
|
5337
|
-
child.unref();
|
|
5338
|
-
} catch {
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
|
-
function trackEvent(event, properties) {
|
|
5342
|
-
sendToPosthog(event, { sdk: "node", ...properties });
|
|
5343
|
-
}
|
|
5344
|
-
function initSession(framework, packageManager, isCustomCommand, adapters) {
|
|
5345
|
-
getOrCreateConfig();
|
|
5346
|
-
session.startTime = Date.now();
|
|
5347
|
-
session.framework = framework;
|
|
5348
|
-
session.packageManager = packageManager;
|
|
5349
|
-
session.isCustomCommand = isCustomCommand;
|
|
5350
|
-
session.adapters = adapters;
|
|
5351
|
-
}
|
|
5352
|
-
function recordRequestCount(count) {
|
|
5353
|
-
session.requestCount = count;
|
|
5354
|
-
}
|
|
5355
|
-
function recordInsightTypes(types) {
|
|
5356
|
-
for (const t of types) session.insightTypes.add(t);
|
|
5357
|
-
}
|
|
5358
|
-
function recordRulesTriggered(rules) {
|
|
5359
|
-
for (const r of rules) session.rulesTriggered.add(r);
|
|
5360
|
-
}
|
|
5361
|
-
function recordTabViewed(tab) {
|
|
5362
|
-
session.tabsViewed.add(tab);
|
|
5363
|
-
}
|
|
5364
|
-
function recordDashboardOpened() {
|
|
5365
|
-
if (session.dashboardOpened) return;
|
|
5366
|
-
session.dashboardOpened = true;
|
|
5367
|
-
session.dashboardOpenedAt = Date.now();
|
|
5368
|
-
trackEvent(TELEMETRY_EVENT_DASHBOARD_VIEWED, {
|
|
5369
|
-
time_to_dashboard_ms: session.startTime > 0 ? Date.now() - session.startTime : null,
|
|
5370
|
-
request_count_at_open: session.requestCount
|
|
5371
|
-
});
|
|
5726
|
+
@keyframes graph-pulse{0%,100%{opacity:1}50%{opacity:0.5}}
|
|
5727
|
+
.graph-pulse{animation:graph-pulse 2s ease-in-out infinite}
|
|
5728
|
+
|
|
5729
|
+
/* Flow edge animation */
|
|
5730
|
+
@keyframes graph-flow-dash{to{stroke-dashoffset:-24}}
|
|
5731
|
+
.graph-flow-edge{animation:graph-flow-dash 1s linear infinite}
|
|
5732
|
+
`;
|
|
5372
5733
|
}
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5734
|
+
var init_graph_view = __esm({
|
|
5735
|
+
"src/dashboard/styles/graph-view.ts"() {
|
|
5736
|
+
"use strict";
|
|
5737
|
+
}
|
|
5738
|
+
});
|
|
5739
|
+
|
|
5740
|
+
// src/dashboard/styles/explorer.ts
|
|
5741
|
+
function getExplorerStyles() {
|
|
5742
|
+
return `
|
|
5743
|
+
/* Explorer sub-tabs */
|
|
5744
|
+
.explorer-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);padding:0 28px;background:var(--bg);position:sticky;top:0;z-index:2}
|
|
5745
|
+
.explorer-tab{padding:10px 16px;font-size:13px;font-weight:500;color:var(--text-muted);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px;font-family:var(--sans);white-space:nowrap}
|
|
5746
|
+
.explorer-tab:hover{color:var(--text)}
|
|
5747
|
+
.explorer-tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
5748
|
+
.explorer-tab-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 6px;border-radius:8px}
|
|
5749
|
+
.explorer-tab.active .explorer-tab-count{color:var(--accent);background:var(--accent-bg)}
|
|
5750
|
+
`;
|
|
5378
5751
|
}
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5752
|
+
var init_explorer = __esm({
|
|
5753
|
+
"src/dashboard/styles/explorer.ts"() {
|
|
5754
|
+
"use strict";
|
|
5755
|
+
}
|
|
5756
|
+
});
|
|
5757
|
+
|
|
5758
|
+
// src/dashboard/styles/insights.ts
|
|
5759
|
+
function getInsightsStyles() {
|
|
5760
|
+
return `
|
|
5761
|
+
/* Insights filter chips */
|
|
5762
|
+
.insights-filters{display:flex;gap:6px;padding:16px 28px;border-bottom:1px solid var(--border);background:var(--bg);position:sticky;top:0;z-index:2}
|
|
5763
|
+
.insights-chip{font-size:12px;font-weight:500;padding:5px 14px;border:1px solid var(--border);border-radius:20px;background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:5px;font-family:var(--sans)}
|
|
5764
|
+
.insights-chip:hover{border-color:var(--text-muted);color:var(--text)}
|
|
5765
|
+
.insights-chip.active{background:var(--accent);color:white;border-color:var(--accent)}
|
|
5766
|
+
.insights-chip-count{font-size:10px;font-family:var(--mono);background:rgba(0,0,0,.08);padding:1px 5px;border-radius:8px}
|
|
5767
|
+
.insights-chip.active .insights-chip-count{background:rgba(255,255,255,.25)}
|
|
5768
|
+
|
|
5769
|
+
/* Summary bar */
|
|
5770
|
+
.insights-summary{display:flex;gap:14px;padding:10px 28px;font-size:12px;font-weight:500;border-bottom:1px solid var(--border)}
|
|
5771
|
+
.insights-summary-stat{display:flex;align-items:center;gap:4px}
|
|
5772
|
+
.insights-summary-stat.critical{color:var(--red)}
|
|
5773
|
+
.insights-summary-stat.warning{color:var(--amber)}
|
|
5774
|
+
.insights-summary-stat.resolved{color:var(--green)}
|
|
5775
|
+
|
|
5776
|
+
/* AI hint */
|
|
5777
|
+
.insights-ai-hint{font-size:11px;color:var(--text-muted);padding:0 0 12px}
|
|
5778
|
+
.insights-ai-hint code{background:var(--bg-muted);padding:2px 6px;border-radius:4px;font-family:var(--mono);font-size:11px}
|
|
5779
|
+
|
|
5780
|
+
/* Insights card list */
|
|
5781
|
+
.insights-list{padding:16px 28px}
|
|
5782
|
+
|
|
5783
|
+
.insights-empty{display:flex;align-items:center;gap:10px;padding:24px;color:var(--green);font-size:14px;font-weight:500}
|
|
5784
|
+
.insights-empty-icon{font-size:18px}
|
|
5785
|
+
|
|
5786
|
+
.insights-card{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s;margin-bottom:6px}
|
|
5787
|
+
.insights-card:hover{border-color:var(--border-light);box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
|
5788
|
+
.insights-card.expanded{border-color:var(--border-light);box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
|
5789
|
+
.insights-card.resolved{opacity:.55}
|
|
5790
|
+
.insights-card.resolved:hover{opacity:.8}
|
|
5791
|
+
|
|
5792
|
+
.insights-card-left{flex-shrink:0;padding-top:2px}
|
|
5793
|
+
.insights-sev{width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:10px;border-radius:50%}
|
|
5794
|
+
.insights-sev.critical{background:var(--red-bg);color:var(--red)}
|
|
5795
|
+
.insights-sev.warning{background:var(--amber-bg);color:var(--amber)}
|
|
5796
|
+
.insights-sev.info{background:var(--blue-bg);color:var(--blue)}
|
|
5797
|
+
|
|
5798
|
+
.insights-card-body{flex:1;min-width:0}
|
|
5799
|
+
.insights-card-header{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:3px}
|
|
5800
|
+
.insights-card-title{font-size:13px;font-weight:600;color:var(--text)}
|
|
5801
|
+
.insights-card-title.resolved{text-decoration:line-through;color:var(--text-muted)}
|
|
5802
|
+
.insights-card-cat{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);background:var(--bg-muted);padding:1px 6px;border-radius:4px}
|
|
5803
|
+
.insights-card-count{font-size:11px;font-family:var(--mono);color:var(--text-muted)}
|
|
5804
|
+
.insights-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
|
|
5805
|
+
.insights-card-detail{font-size:11px;font-family:var(--mono);color:var(--text-muted);margin-top:6px;padding:6px 10px;background:var(--bg-muted);border:1px solid var(--border-subtle);border-radius:6px;line-height:1.5}
|
|
5806
|
+
.insights-card-progress{font-size:11px;color:var(--text-muted);margin-top:4px;font-family:var(--mono)}
|
|
5807
|
+
.insights-card-hint{font-size:12px;color:var(--text-dim);line-height:1.6;margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}
|
|
5808
|
+
|
|
5809
|
+
.insights-badge-regressed{font-size:9px;font-weight:700;color:var(--red);background:var(--red-bg);padding:1px 6px;border-radius:4px}
|
|
5810
|
+
.insights-badge-verifying{font-size:9px;font-weight:700;color:var(--amber);background:var(--amber-bg);padding:1px 6px;border-radius:4px}
|
|
5811
|
+
.insights-badge-resolved{font-size:9px;font-weight:700;color:var(--green);background:var(--green-bg);padding:1px 6px;border-radius:4px}
|
|
5812
|
+
|
|
5813
|
+
.insights-card-arrow{color:var(--text-muted);font-size:12px;flex-shrink:0;padding-top:2px;font-family:var(--mono);transition:transform .15s}
|
|
5814
|
+
|
|
5815
|
+
.insights-section{display:flex;align-items:center;gap:8px;padding:14px 0 8px;margin-top:4px;font-size:12px;font-weight:700;color:var(--text);text-transform:uppercase;letter-spacing:.5px;border-top:1px solid var(--border);user-select:none}
|
|
5816
|
+
.insights-section:first-child{border-top:none;margin-top:0}
|
|
5817
|
+
.insights-section-icon{font-size:11px;width:16px;text-align:center}
|
|
5818
|
+
.insights-section-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 7px;border-radius:8px;font-weight:500}
|
|
5819
|
+
.insights-section-regressed{color:var(--red)}
|
|
5820
|
+
.insights-section-regressed .insights-section-count{color:var(--red);background:var(--red-bg)}
|
|
5821
|
+
.insights-section-verifying{color:var(--amber)}
|
|
5822
|
+
.insights-section-verifying .insights-section-count{color:var(--amber);background:var(--amber-bg)}
|
|
5823
|
+
.insights-section-resolved{color:var(--green)}
|
|
5824
|
+
.insights-section-resolved .insights-section-count{color:var(--green);background:var(--green-bg)}
|
|
5825
|
+
.insights-section-dismissed{color:var(--text-muted);cursor:pointer}
|
|
5826
|
+
.insights-section-dismissed:hover{color:var(--text)}
|
|
5827
|
+
`;
|
|
5384
5828
|
}
|
|
5385
|
-
|
|
5386
|
-
|
|
5829
|
+
var init_insights3 = __esm({
|
|
5830
|
+
"src/dashboard/styles/insights.ts"() {
|
|
5831
|
+
"use strict";
|
|
5832
|
+
}
|
|
5833
|
+
});
|
|
5834
|
+
|
|
5835
|
+
// src/dashboard/styles.ts
|
|
5836
|
+
function getStyles() {
|
|
5837
|
+
return getBaseStyles() + getLayoutStyles() + getFlowStyles() + getRequestStyles() + getPerformanceStyles() + getOverviewStyles() + getSecurityStyles() + getTimelineStyles() + getGraphViewStyles() + getExplorerStyles() + getInsightsStyles();
|
|
5387
5838
|
}
|
|
5388
|
-
|
|
5389
|
-
|
|
5839
|
+
var init_styles = __esm({
|
|
5840
|
+
"src/dashboard/styles.ts"() {
|
|
5841
|
+
"use strict";
|
|
5842
|
+
init_base();
|
|
5843
|
+
init_layout();
|
|
5844
|
+
init_flows();
|
|
5845
|
+
init_requests();
|
|
5846
|
+
init_graph2();
|
|
5847
|
+
init_overview();
|
|
5848
|
+
init_security2();
|
|
5849
|
+
init_timeline();
|
|
5850
|
+
init_graph_view();
|
|
5851
|
+
init_explorer();
|
|
5852
|
+
init_insights3();
|
|
5853
|
+
}
|
|
5854
|
+
});
|
|
5855
|
+
|
|
5856
|
+
// src/dashboard/layout.ts
|
|
5857
|
+
function getLayoutHtml() {
|
|
5858
|
+
return `<bk-dashboard></bk-dashboard>`;
|
|
5390
5859
|
}
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
if (ms < t[0]) return `<${t[0]}ms`;
|
|
5395
|
-
for (let i = 1; i < t.length; i++) {
|
|
5396
|
-
if (ms < t[i]) return `${t[i - 1]}-${t[i]}ms`;
|
|
5860
|
+
var init_layout2 = __esm({
|
|
5861
|
+
"src/dashboard/layout.ts"() {
|
|
5862
|
+
"use strict";
|
|
5397
5863
|
}
|
|
5398
|
-
|
|
5864
|
+
});
|
|
5865
|
+
|
|
5866
|
+
// src/dashboard/page.ts
|
|
5867
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5868
|
+
import { resolve as resolve3, dirname } from "path";
|
|
5869
|
+
import { fileURLToPath } from "url";
|
|
5870
|
+
function getClientBundle() {
|
|
5871
|
+
if (clientBundle) return clientBundle;
|
|
5872
|
+
const bundlePath = resolve3(__dirname, "../dashboard-client.global.js");
|
|
5873
|
+
clientBundle = readFileSync4(bundlePath, "utf-8");
|
|
5874
|
+
return clientBundle;
|
|
5399
5875
|
}
|
|
5400
|
-
function
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
}
|
|
5416
|
-
const now = Date.now();
|
|
5417
|
-
sendToPosthog(TELEMETRY_EVENT_SESSION, {
|
|
5418
|
-
sdk: "node",
|
|
5419
|
-
framework: session.framework,
|
|
5420
|
-
package_manager: session.packageManager,
|
|
5421
|
-
is_custom_command: session.isCustomCommand,
|
|
5422
|
-
first_session: isFirstSession,
|
|
5423
|
-
adapters_detected: session.adapters,
|
|
5424
|
-
request_count: session.requestCount,
|
|
5425
|
-
error_count: services.errorStore.getAll().length,
|
|
5426
|
-
query_count: services.queryStore.getAll().length,
|
|
5427
|
-
fetch_count: services.fetchStore.getAll().length,
|
|
5428
|
-
insight_count: insights.length,
|
|
5429
|
-
finding_count: findings.length,
|
|
5430
|
-
insight_types: [...session.insightTypes],
|
|
5431
|
-
rules_triggered: [...session.rulesTriggered],
|
|
5432
|
-
endpoint_count: live.length,
|
|
5433
|
-
avg_duration_ms: totalRequests > 0 ? Math.round(totalDuration / totalRequests) : 0,
|
|
5434
|
-
slowest_endpoint_bucket: speedBucket(slowestP95),
|
|
5435
|
-
tabs_viewed: [...session.tabsViewed],
|
|
5436
|
-
dashboard_opened: session.dashboardOpened,
|
|
5437
|
-
explain_used: session.explainUsed,
|
|
5438
|
-
session_duration_s: Math.round((now - session.startTime) / 1e3),
|
|
5439
|
-
// Enhanced fields
|
|
5440
|
-
setup_succeeded: session.setupSucceeded,
|
|
5441
|
-
setup_duration_ms: session.setupDurationMs,
|
|
5442
|
-
framework_detection_candidates: session.frameworkCandidates,
|
|
5443
|
-
adapters_failed: session.adaptersFailed,
|
|
5444
|
-
time_to_first_request_ms: session.firstRequestAt ? session.firstRequestAt - session.startTime : null,
|
|
5445
|
-
time_to_dashboard_ms: session.dashboardOpenedAt ? session.dashboardOpenedAt - session.startTime : null,
|
|
5446
|
-
exit_reason: session.exitReason
|
|
5447
|
-
});
|
|
5448
|
-
getOrCreateConfig();
|
|
5876
|
+
function getDashboardHtml(config) {
|
|
5877
|
+
return `<!DOCTYPE html>
|
|
5878
|
+
<html lang="en">
|
|
5879
|
+
<head>
|
|
5880
|
+
<meta charset="UTF-8">
|
|
5881
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5882
|
+
<title>brakit</title>
|
|
5883
|
+
<style>${getStyles()}</style>
|
|
5884
|
+
</head>
|
|
5885
|
+
<body>
|
|
5886
|
+
${getLayoutHtml()}
|
|
5887
|
+
<script>window.__BRAKIT_CONFIG__={port:${config.proxyPort},version:"${VERSION}"};</script>
|
|
5888
|
+
<script>${getClientBundle()}</script>
|
|
5889
|
+
</body>
|
|
5890
|
+
</html>`;
|
|
5449
5891
|
}
|
|
5450
|
-
var
|
|
5451
|
-
var
|
|
5452
|
-
"src/
|
|
5892
|
+
var __dirname, clientBundle;
|
|
5893
|
+
var init_page = __esm({
|
|
5894
|
+
"src/dashboard/page.ts"() {
|
|
5453
5895
|
"use strict";
|
|
5454
5896
|
init_src();
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
|
|
5460
|
-
session = {
|
|
5461
|
-
startTime: 0,
|
|
5462
|
-
framework: "",
|
|
5463
|
-
packageManager: "",
|
|
5464
|
-
isCustomCommand: false,
|
|
5465
|
-
adapters: [],
|
|
5466
|
-
requestCount: 0,
|
|
5467
|
-
insightTypes: /* @__PURE__ */ new Set(),
|
|
5468
|
-
rulesTriggered: /* @__PURE__ */ new Set(),
|
|
5469
|
-
tabsViewed: /* @__PURE__ */ new Set(),
|
|
5470
|
-
dashboardOpened: false,
|
|
5471
|
-
explainUsed: false,
|
|
5472
|
-
frameworkCandidates: [],
|
|
5473
|
-
adaptersFailed: [],
|
|
5474
|
-
setupDurationMs: 0,
|
|
5475
|
-
setupSucceeded: false,
|
|
5476
|
-
firstRequestAt: 0,
|
|
5477
|
-
dashboardOpenedAt: 0,
|
|
5478
|
-
exitReason: "unknown"
|
|
5479
|
-
};
|
|
5897
|
+
init_styles();
|
|
5898
|
+
init_layout2();
|
|
5899
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
5900
|
+
clientBundle = null;
|
|
5480
5901
|
}
|
|
5481
5902
|
});
|
|
5482
5903
|
|
|
@@ -5514,7 +5935,7 @@ function createDashboardHandler(services) {
|
|
|
5514
5935
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
5515
5936
|
const tab = url.searchParams.get("tab");
|
|
5516
5937
|
if (tab && tab.length <= MAX_TAB_NAME_LENGTH && VALID_TABS.has(tab)) {
|
|
5517
|
-
recordTabViewed(tab);
|
|
5938
|
+
session.recordTabViewed(tab);
|
|
5518
5939
|
}
|
|
5519
5940
|
const event = url.searchParams.get("event");
|
|
5520
5941
|
if (event && event.length <= MAX_TAB_NAME_LENGTH) {
|
|
@@ -5532,7 +5953,7 @@ function createDashboardHandler(services) {
|
|
|
5532
5953
|
handler(req, res);
|
|
5533
5954
|
return;
|
|
5534
5955
|
}
|
|
5535
|
-
if (isTelemetryEnabled()) recordDashboardOpened();
|
|
5956
|
+
if (isTelemetryEnabled()) session.recordDashboardOpened();
|
|
5536
5957
|
res.writeHead(HTTP_OK, {
|
|
5537
5958
|
"content-type": "text/html; charset=utf-8",
|
|
5538
5959
|
"cache-control": "no-cache",
|
|
@@ -5552,7 +5973,7 @@ var init_router = __esm({
|
|
|
5552
5973
|
init_graph();
|
|
5553
5974
|
init_sse();
|
|
5554
5975
|
init_page();
|
|
5555
|
-
|
|
5976
|
+
init_telemetry2();
|
|
5556
5977
|
}
|
|
5557
5978
|
});
|
|
5558
5979
|
|
|
@@ -5691,7 +6112,7 @@ var init_telemetry_store = __esm({
|
|
|
5691
6112
|
constructor(maxEntries = MAX_TELEMETRY_ENTRIES) {
|
|
5692
6113
|
this.maxEntries = maxEntries;
|
|
5693
6114
|
this.entries = [];
|
|
5694
|
-
this.listeners =
|
|
6115
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
5695
6116
|
}
|
|
5696
6117
|
add(data) {
|
|
5697
6118
|
const entry = { id: randomUUID6(), ...data };
|
|
@@ -5710,11 +6131,10 @@ var init_telemetry_store = __esm({
|
|
|
5710
6131
|
this.entries.length = 0;
|
|
5711
6132
|
}
|
|
5712
6133
|
onEntry(fn) {
|
|
5713
|
-
this.listeners.
|
|
6134
|
+
this.listeners.add(fn);
|
|
5714
6135
|
}
|
|
5715
6136
|
offEntry(fn) {
|
|
5716
|
-
|
|
5717
|
-
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
6137
|
+
this.listeners.delete(fn);
|
|
5718
6138
|
}
|
|
5719
6139
|
};
|
|
5720
6140
|
}
|
|
@@ -6697,54 +7117,34 @@ import pc from "picocolors";
|
|
|
6697
7117
|
function print(line) {
|
|
6698
7118
|
process.stdout.write(line + "\n");
|
|
6699
7119
|
}
|
|
6700
|
-
function
|
|
6701
|
-
return
|
|
6702
|
-
}
|
|
6703
|
-
function colorTitle(severity, text) {
|
|
6704
|
-
const color = SEVERITY_COLOR[severity];
|
|
6705
|
-
return severity === "info" ? color(text) : color(pc.bold(text));
|
|
6706
|
-
}
|
|
6707
|
-
function truncate(s, max = TERMINAL_TRUNCATE_LENGTH) {
|
|
6708
|
-
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
6709
|
-
}
|
|
6710
|
-
function formatConsoleLine(issue, suffix) {
|
|
6711
|
-
const icon = severityIcon(issue.severity);
|
|
6712
|
-
const title = colorTitle(issue.severity, issue.title);
|
|
6713
|
-
const desc = pc.dim(truncate(issue.desc) + (suffix ?? ""));
|
|
6714
|
-
let line = ` ${icon} ${title} \u2014 ${desc}`;
|
|
6715
|
-
if (issue.detail) {
|
|
6716
|
-
line += `
|
|
6717
|
-
${pc.dim("\u2514 " + issue.detail)}`;
|
|
6718
|
-
}
|
|
6719
|
-
return line;
|
|
7120
|
+
function pluralize(n, word) {
|
|
7121
|
+
return n === 1 ? `${n} ${word}` : `${n} ${word}s`;
|
|
6720
7122
|
}
|
|
6721
7123
|
function startTerminalInsights(services, proxyPort) {
|
|
6722
7124
|
const bus = services.bus;
|
|
6723
|
-
const metricsStore = services.metricsStore;
|
|
6724
7125
|
const printedKeys = /* @__PURE__ */ new Set();
|
|
6725
7126
|
const resolvedKeys = /* @__PURE__ */ new Set();
|
|
6726
|
-
const dashUrl = `localhost:${proxyPort}${DASHBOARD_PREFIX}`;
|
|
7127
|
+
const dashUrl = `http://localhost:${proxyPort}${DASHBOARD_PREFIX}`;
|
|
7128
|
+
const prefix = ` ${pc.magenta(pc.bold("brakit"))} ${pc.dim(UNICODE_ARROW)}`;
|
|
7129
|
+
const actionHint = `${pc.underline(dashUrl)} ${pc.dim("or run")} ${pc.bold('"Fix brakit findings"')} ${pc.dim("in your AI tool")}`;
|
|
6727
7130
|
return bus.on("analysis:updated", ({ issues }) => {
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
7131
|
+
let newCount = 0;
|
|
7132
|
+
let resolvedCount = 0;
|
|
7133
|
+
let regressedCount = 0;
|
|
6731
7134
|
for (const si of issues) {
|
|
7135
|
+
if (si.aiStatus === "wont_fix") continue;
|
|
6732
7136
|
if (si.state === "resolved") {
|
|
6733
7137
|
if (resolvedKeys.has(si.issueId)) continue;
|
|
6734
7138
|
resolvedKeys.add(si.issueId);
|
|
6735
7139
|
printedKeys.delete(si.issueId);
|
|
6736
|
-
|
|
6737
|
-
const desc = pc.dim(truncate(si.issue.desc));
|
|
6738
|
-
resolvedLines.push(` ${title} \u2014 ${desc} ${pc.green("resolved")}`);
|
|
7140
|
+
resolvedCount++;
|
|
6739
7141
|
continue;
|
|
6740
7142
|
}
|
|
6741
7143
|
if (si.state === "regressed") {
|
|
6742
7144
|
if (!printedKeys.has(si.issueId)) {
|
|
6743
7145
|
printedKeys.add(si.issueId);
|
|
6744
7146
|
resolvedKeys.delete(si.issueId);
|
|
6745
|
-
|
|
6746
|
-
const desc = pc.dim(truncate(si.issue.desc));
|
|
6747
|
-
regressedLines.push(` ${title} \u2014 ${desc} ${pc.red("regressed")}`);
|
|
7147
|
+
regressedCount++;
|
|
6748
7148
|
}
|
|
6749
7149
|
continue;
|
|
6750
7150
|
}
|
|
@@ -6752,52 +7152,27 @@ function startTerminalInsights(services, proxyPort) {
|
|
|
6752
7152
|
if (si.issue.severity === "info") continue;
|
|
6753
7153
|
if (printedKeys.has(si.issueId)) continue;
|
|
6754
7154
|
printedKeys.add(si.issueId);
|
|
6755
|
-
|
|
6756
|
-
if (si.issue.rule === "slow") {
|
|
6757
|
-
const endpoint = si.issue.endpoint;
|
|
6758
|
-
if (endpoint) {
|
|
6759
|
-
const ep = metricsStore.getEndpoint(endpoint);
|
|
6760
|
-
if (ep && ep.sessions.length > 1) {
|
|
6761
|
-
const prev = ep.sessions[ep.sessions.length - 2];
|
|
6762
|
-
suffix = ` (\u2191 from ${prev.p95DurationMs < 1e3 ? prev.p95DurationMs + "ms" : (prev.p95DurationMs / 1e3).toFixed(1) + "s"})`;
|
|
6763
|
-
}
|
|
6764
|
-
}
|
|
6765
|
-
}
|
|
6766
|
-
newLines.push(formatConsoleLine(si.issue, suffix));
|
|
7155
|
+
newCount++;
|
|
6767
7156
|
}
|
|
6768
|
-
if (
|
|
6769
|
-
print("");
|
|
6770
|
-
for (const line of newLines) print(line);
|
|
7157
|
+
if (newCount > 0) {
|
|
6771
7158
|
print("");
|
|
6772
|
-
print(
|
|
7159
|
+
print(`${prefix} ${pc.yellow(pluralize(newCount, "new issue"))} ${pc.dim(UNICODE_EM_DASH)} ${actionHint}`);
|
|
6773
7160
|
}
|
|
6774
|
-
if (
|
|
7161
|
+
if (regressedCount > 0) {
|
|
6775
7162
|
print("");
|
|
6776
|
-
|
|
6777
|
-
print("");
|
|
6778
|
-
print(` ${pc.magenta(pc.bold("brakit"))} ${pc.dim("\u2192")} ${pc.red("Issues came back after being resolved!")}`);
|
|
7163
|
+
print(`${prefix} ${pc.red(pluralize(regressedCount, "issue") + " regressed")} ${pc.dim(UNICODE_EM_DASH)} ${actionHint}`);
|
|
6779
7164
|
}
|
|
6780
|
-
if (
|
|
6781
|
-
print("");
|
|
6782
|
-
for (const line of resolvedLines) print(line);
|
|
7165
|
+
if (resolvedCount > 0) {
|
|
6783
7166
|
print("");
|
|
6784
|
-
print(
|
|
7167
|
+
print(`${prefix} ${pc.green(pluralize(resolvedCount, "issue") + " resolved " + UNICODE_CHECK_MARK)}`);
|
|
6785
7168
|
}
|
|
6786
7169
|
});
|
|
6787
7170
|
}
|
|
6788
|
-
var SEVERITY_COLOR;
|
|
6789
7171
|
var init_terminal = __esm({
|
|
6790
7172
|
"src/output/terminal.ts"() {
|
|
6791
7173
|
"use strict";
|
|
6792
7174
|
init_src();
|
|
6793
7175
|
init_constants();
|
|
6794
|
-
init_config();
|
|
6795
|
-
init_labels();
|
|
6796
|
-
SEVERITY_COLOR = {
|
|
6797
|
-
critical: pc.red,
|
|
6798
|
-
warning: pc.yellow,
|
|
6799
|
-
info: pc.dim
|
|
6800
|
-
};
|
|
6801
7176
|
}
|
|
6802
7177
|
});
|
|
6803
7178
|
|
|
@@ -6873,125 +7248,6 @@ var init_guard = __esm({
|
|
|
6873
7248
|
}
|
|
6874
7249
|
});
|
|
6875
7250
|
|
|
6876
|
-
// src/runtime/capture.ts
|
|
6877
|
-
import { gunzip, brotliDecompress, inflate } from "zlib";
|
|
6878
|
-
function outgoingToIncoming(headers2) {
|
|
6879
|
-
const result = {};
|
|
6880
|
-
for (const [key, value] of Object.entries(headers2)) {
|
|
6881
|
-
if (value === void 0) continue;
|
|
6882
|
-
if (Array.isArray(value)) {
|
|
6883
|
-
result[key] = value.map(String);
|
|
6884
|
-
} else {
|
|
6885
|
-
result[key] = String(value);
|
|
6886
|
-
}
|
|
6887
|
-
}
|
|
6888
|
-
return result;
|
|
6889
|
-
}
|
|
6890
|
-
function getDecompressor(encoding) {
|
|
6891
|
-
if (encoding === CONTENT_ENCODING_GZIP) return gunzip;
|
|
6892
|
-
if (encoding === CONTENT_ENCODING_BR) return brotliDecompress;
|
|
6893
|
-
if (encoding === CONTENT_ENCODING_DEFLATE) return inflate;
|
|
6894
|
-
return null;
|
|
6895
|
-
}
|
|
6896
|
-
function decompressAsync(body, encoding) {
|
|
6897
|
-
const decompressor = getDecompressor(encoding);
|
|
6898
|
-
if (!decompressor) return Promise.resolve(body);
|
|
6899
|
-
return new Promise((resolve6) => {
|
|
6900
|
-
decompressor(body, (err, result) => {
|
|
6901
|
-
resolve6(err ? body : result);
|
|
6902
|
-
});
|
|
6903
|
-
});
|
|
6904
|
-
}
|
|
6905
|
-
function toBuffer(chunk) {
|
|
6906
|
-
if (Buffer.isBuffer(chunk)) return chunk;
|
|
6907
|
-
if (chunk instanceof Uint8Array) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
6908
|
-
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
6909
|
-
return null;
|
|
6910
|
-
}
|
|
6911
|
-
function captureInProcess(req, res, requestId, requestStore, isChild = false) {
|
|
6912
|
-
const startTime = performance.now();
|
|
6913
|
-
const method = req.method ?? "GET";
|
|
6914
|
-
const resChunks = [];
|
|
6915
|
-
let resSize = 0;
|
|
6916
|
-
const originalWrite = res.write;
|
|
6917
|
-
const originalEnd = res.end;
|
|
6918
|
-
let truncated = false;
|
|
6919
|
-
res.write = function(...args) {
|
|
6920
|
-
try {
|
|
6921
|
-
const chunk = args[0];
|
|
6922
|
-
if (chunk != null && typeof chunk !== "function") {
|
|
6923
|
-
if (resSize < DEFAULT_MAX_BODY_CAPTURE) {
|
|
6924
|
-
const buf = toBuffer(chunk);
|
|
6925
|
-
if (buf) {
|
|
6926
|
-
resChunks.push(buf);
|
|
6927
|
-
resSize += buf.length;
|
|
6928
|
-
}
|
|
6929
|
-
} else {
|
|
6930
|
-
truncated = true;
|
|
6931
|
-
}
|
|
6932
|
-
}
|
|
6933
|
-
} catch (e) {
|
|
6934
|
-
brakitDebug(`capture write: ${getErrorMessage(e)}`);
|
|
6935
|
-
}
|
|
6936
|
-
return originalWrite.apply(this, args);
|
|
6937
|
-
};
|
|
6938
|
-
res.end = function(...args) {
|
|
6939
|
-
try {
|
|
6940
|
-
const chunk = typeof args[0] !== "function" ? args[0] : void 0;
|
|
6941
|
-
if (chunk != null && resSize < DEFAULT_MAX_BODY_CAPTURE) {
|
|
6942
|
-
const buf = toBuffer(chunk);
|
|
6943
|
-
if (buf) {
|
|
6944
|
-
resChunks.push(buf);
|
|
6945
|
-
}
|
|
6946
|
-
}
|
|
6947
|
-
} catch (e) {
|
|
6948
|
-
brakitDebug(`capture end: ${getErrorMessage(e)}`);
|
|
6949
|
-
}
|
|
6950
|
-
const result = originalEnd.apply(this, args);
|
|
6951
|
-
const endTime = performance.now();
|
|
6952
|
-
const encoding = String(res.getHeader("content-encoding") ?? "").toLowerCase();
|
|
6953
|
-
const statusCode = res.statusCode;
|
|
6954
|
-
const responseHeaders = outgoingToIncoming(res.getHeaders());
|
|
6955
|
-
const responseContentType = String(res.getHeader("content-type") ?? "");
|
|
6956
|
-
const capturedChunks = resChunks.slice();
|
|
6957
|
-
if (!isChild) {
|
|
6958
|
-
void (async () => {
|
|
6959
|
-
try {
|
|
6960
|
-
let body = capturedChunks.length > 0 ? Buffer.concat(capturedChunks) : null;
|
|
6961
|
-
if (body && encoding && !truncated) {
|
|
6962
|
-
body = await decompressAsync(body, encoding);
|
|
6963
|
-
}
|
|
6964
|
-
requestStore.capture({
|
|
6965
|
-
requestId,
|
|
6966
|
-
method,
|
|
6967
|
-
url: req.url ?? "/",
|
|
6968
|
-
requestHeaders: req.headers,
|
|
6969
|
-
requestBody: null,
|
|
6970
|
-
statusCode,
|
|
6971
|
-
responseHeaders,
|
|
6972
|
-
responseBody: body,
|
|
6973
|
-
responseContentType,
|
|
6974
|
-
startTime,
|
|
6975
|
-
endTime,
|
|
6976
|
-
config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
|
|
6977
|
-
});
|
|
6978
|
-
} catch (e) {
|
|
6979
|
-
brakitDebug(`capture store: ${getErrorMessage(e)}`);
|
|
6980
|
-
}
|
|
6981
|
-
})();
|
|
6982
|
-
}
|
|
6983
|
-
return result;
|
|
6984
|
-
};
|
|
6985
|
-
}
|
|
6986
|
-
var init_capture = __esm({
|
|
6987
|
-
"src/runtime/capture.ts"() {
|
|
6988
|
-
"use strict";
|
|
6989
|
-
init_constants();
|
|
6990
|
-
init_log();
|
|
6991
|
-
init_type_guards();
|
|
6992
|
-
}
|
|
6993
|
-
});
|
|
6994
|
-
|
|
6995
7251
|
// src/runtime/interceptor.ts
|
|
6996
7252
|
import http from "http";
|
|
6997
7253
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -7064,13 +7320,46 @@ var init_interceptor = __esm({
|
|
|
7064
7320
|
}
|
|
7065
7321
|
});
|
|
7066
7322
|
|
|
7323
|
+
// src/detect/runtime.ts
|
|
7324
|
+
function detectJsRuntime() {
|
|
7325
|
+
if (globalThis.Bun) return "bun";
|
|
7326
|
+
if (globalThis.Deno) return "deno";
|
|
7327
|
+
return "node";
|
|
7328
|
+
}
|
|
7329
|
+
function extractLoadedPackages() {
|
|
7330
|
+
try {
|
|
7331
|
+
const cache = __require.cache;
|
|
7332
|
+
if (!cache) return [];
|
|
7333
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7334
|
+
const segment = NODE_MODULES_SEGMENT;
|
|
7335
|
+
const segmentWin = segment.replaceAll("/", "\\");
|
|
7336
|
+
for (const key of Object.keys(cache)) {
|
|
7337
|
+
const nm = Math.max(key.lastIndexOf(segment), key.lastIndexOf(segmentWin));
|
|
7338
|
+
if (nm === -1) continue;
|
|
7339
|
+
const after = key.slice(nm + segment.length);
|
|
7340
|
+
const parts = after.split(/[/\\]/);
|
|
7341
|
+
const pkgName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
7342
|
+
if (KNOWN_DEPENDENCY_SET.has(pkgName)) seen.add(pkgName);
|
|
7343
|
+
}
|
|
7344
|
+
return [...seen];
|
|
7345
|
+
} catch {
|
|
7346
|
+
return [];
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7349
|
+
var init_runtime = __esm({
|
|
7350
|
+
"src/detect/runtime.ts"() {
|
|
7351
|
+
"use strict";
|
|
7352
|
+
init_detection();
|
|
7353
|
+
}
|
|
7354
|
+
});
|
|
7355
|
+
|
|
7067
7356
|
// src/runtime/setup.ts
|
|
7068
7357
|
var setup_exports = {};
|
|
7069
7358
|
__export(setup_exports, {
|
|
7070
7359
|
setup: () => setup
|
|
7071
7360
|
});
|
|
7072
7361
|
import { readFile as readFile5, mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
7073
|
-
import { existsSync as existsSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
7362
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync6 } from "fs";
|
|
7074
7363
|
import { resolve as resolve5 } from "path";
|
|
7075
7364
|
function setup() {
|
|
7076
7365
|
if (initPromise) return initPromise;
|
|
@@ -7102,21 +7391,25 @@ function installHooks(bus) {
|
|
|
7102
7391
|
adapterRegistry.patchAll(telemetryEmit);
|
|
7103
7392
|
const cwd = process.cwd();
|
|
7104
7393
|
let framework = "unknown";
|
|
7105
|
-
let
|
|
7394
|
+
let detectedDependencies = [];
|
|
7395
|
+
let configFilesDetected = [];
|
|
7106
7396
|
try {
|
|
7107
7397
|
const pkg = JSON.parse(
|
|
7108
|
-
|
|
7398
|
+
readFileSync6(resolve5(cwd, "package.json"), "utf-8")
|
|
7109
7399
|
);
|
|
7110
7400
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7111
7401
|
framework = detectFrameworkFromDeps(allDeps);
|
|
7112
|
-
|
|
7402
|
+
detectedDependencies = KNOWN_DEPENDENCY_NAMES.filter((dep) => dep in allDeps);
|
|
7403
|
+
configFilesDetected = detectConfigFiles(cwd);
|
|
7113
7404
|
} catch {
|
|
7114
7405
|
}
|
|
7115
7406
|
return {
|
|
7116
7407
|
framework,
|
|
7117
7408
|
adapterNames: adapterRegistry.getActive().map((a) => a.name),
|
|
7118
7409
|
adaptersFailed: [...adapterRegistry.getFailed()],
|
|
7119
|
-
|
|
7410
|
+
detectedDependencies,
|
|
7411
|
+
configFilesDetected,
|
|
7412
|
+
jsRuntime: detectJsRuntime()
|
|
7120
7413
|
};
|
|
7121
7414
|
}
|
|
7122
7415
|
function startAnalysis(bus, stores, dataDir, services) {
|
|
@@ -7145,14 +7438,12 @@ function registerLifecycle(allServices, stores, services, cwd) {
|
|
|
7145
7438
|
const sendTelemetry = () => {
|
|
7146
7439
|
if (telemetrySent) return;
|
|
7147
7440
|
telemetrySent = true;
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
services.analysisEngine.getInsights().map((i) => i.type)
|
|
7151
|
-
);
|
|
7152
|
-
recordRulesTriggered(
|
|
7441
|
+
session.recordCounts(
|
|
7442
|
+
stores.requestStore.getAll().length,
|
|
7443
|
+
services.analysisEngine.getInsights().map((i) => i.type),
|
|
7153
7444
|
services.analysisEngine.getFindings().map((f) => f.rule)
|
|
7154
7445
|
);
|
|
7155
|
-
|
|
7446
|
+
flushSession(allServices);
|
|
7156
7447
|
};
|
|
7157
7448
|
let teardownCalled = false;
|
|
7158
7449
|
const runTeardown = () => {
|
|
@@ -7172,13 +7463,14 @@ function registerLifecycle(allServices, stores, services, cwd) {
|
|
|
7172
7463
|
};
|
|
7173
7464
|
health.setTeardown(runTeardown);
|
|
7174
7465
|
process.on("SIGINT", () => {
|
|
7175
|
-
recordExitReason(EXIT_REASON_SIGINT);
|
|
7466
|
+
session.recordExitReason(EXIT_REASON_SIGINT);
|
|
7176
7467
|
});
|
|
7177
7468
|
process.on("SIGTERM", () => {
|
|
7178
|
-
recordExitReason(EXIT_REASON_SIGTERM);
|
|
7469
|
+
session.recordExitReason(EXIT_REASON_SIGTERM);
|
|
7179
7470
|
});
|
|
7180
|
-
process.on("beforeExit", () => {
|
|
7181
|
-
|
|
7471
|
+
process.on("beforeExit", async () => {
|
|
7472
|
+
await drainPendingCaptures();
|
|
7473
|
+
session.recordExitReason(EXIT_REASON_CLEAN);
|
|
7182
7474
|
sendTelemetry();
|
|
7183
7475
|
});
|
|
7184
7476
|
process.on("exit", () => {
|
|
@@ -7195,17 +7487,19 @@ async function doSetup() {
|
|
|
7195
7487
|
bus,
|
|
7196
7488
|
...stores
|
|
7197
7489
|
};
|
|
7198
|
-
const
|
|
7199
|
-
|
|
7490
|
+
const detection = installHooks(bus);
|
|
7491
|
+
session.init(detection.framework, detectPackageManager(cwd), false, detection.adapterNames);
|
|
7200
7492
|
const setupDurationMs = Date.now() - setupStart;
|
|
7201
|
-
|
|
7493
|
+
session.recordSetup(detection, setupDurationMs);
|
|
7202
7494
|
trackEvent(TELEMETRY_EVENT_SETUP_COMPLETED, {
|
|
7203
|
-
framework,
|
|
7204
|
-
framework_detection_candidates:
|
|
7205
|
-
adapters_detected: adapterNames,
|
|
7206
|
-
adapters_failed: adaptersFailed,
|
|
7207
|
-
hooks_installed: [
|
|
7208
|
-
setup_duration_ms: setupDurationMs
|
|
7495
|
+
framework: detection.framework,
|
|
7496
|
+
framework_detection_candidates: detection.detectedDependencies,
|
|
7497
|
+
adapters_detected: detection.adapterNames,
|
|
7498
|
+
adapters_failed: detection.adaptersFailed,
|
|
7499
|
+
hooks_installed: [...INSTALLED_HOOKS],
|
|
7500
|
+
setup_duration_ms: setupDurationMs,
|
|
7501
|
+
config_files_detected: detection.configFilesDetected,
|
|
7502
|
+
js_runtime: detection.jsRuntime
|
|
7209
7503
|
});
|
|
7210
7504
|
const graphBuilder = new GraphBuilder(bus, stores.requestStore);
|
|
7211
7505
|
graphBuilder.start();
|
|
@@ -7226,10 +7520,13 @@ async function doSetup() {
|
|
|
7226
7520
|
onFirstRequest(port) {
|
|
7227
7521
|
setBrakitPort(port);
|
|
7228
7522
|
brakitDebug(`[setup] onFirstRequest fired, port=${port}`);
|
|
7229
|
-
|
|
7523
|
+
const loadedPackages = extractLoadedPackages();
|
|
7524
|
+
session.recordFirstRequest(loadedPackages);
|
|
7230
7525
|
trackEvent(TELEMETRY_EVENT_FIRST_REQUEST, {
|
|
7231
7526
|
port,
|
|
7232
|
-
time_to_first_request_ms: Date.now() - setupStart
|
|
7527
|
+
time_to_first_request_ms: Date.now() - setupStart,
|
|
7528
|
+
loaded_packages: loadedPackages,
|
|
7529
|
+
loaded_package_count: loadedPackages.length
|
|
7233
7530
|
});
|
|
7234
7531
|
void (async () => {
|
|
7235
7532
|
try {
|
|
@@ -7270,6 +7567,7 @@ var init_setup = __esm({
|
|
|
7270
7567
|
"src/runtime/setup.ts"() {
|
|
7271
7568
|
"use strict";
|
|
7272
7569
|
init_fetch();
|
|
7570
|
+
init_capture();
|
|
7273
7571
|
init_console();
|
|
7274
7572
|
init_errors();
|
|
7275
7573
|
init_adapters();
|
|
@@ -7284,13 +7582,15 @@ var init_setup = __esm({
|
|
|
7284
7582
|
init_terminal();
|
|
7285
7583
|
init_src();
|
|
7286
7584
|
init_constants();
|
|
7585
|
+
init_detection();
|
|
7287
7586
|
init_health();
|
|
7288
7587
|
init_interceptor();
|
|
7289
7588
|
init_log();
|
|
7290
7589
|
init_type_guards();
|
|
7291
7590
|
init_fs();
|
|
7292
7591
|
init_project();
|
|
7293
|
-
|
|
7592
|
+
init_runtime();
|
|
7593
|
+
init_telemetry2();
|
|
7294
7594
|
initPromise = null;
|
|
7295
7595
|
}
|
|
7296
7596
|
});
|