opencons 0.1.0

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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +382 -0
  3. package/opencons.d.ts +55 -0
  4. package/package.json +73 -0
  5. package/scripts/vendor-d3.js +22 -0
  6. package/src/core/context.js +44 -0
  7. package/src/core/index.js +198 -0
  8. package/src/core/tracer.js +252 -0
  9. package/src/drivers/db-language.js +207 -0
  10. package/src/drivers/detect.js +62 -0
  11. package/src/drivers/drizzle.js +87 -0
  12. package/src/drivers/index.js +43 -0
  13. package/src/drivers/mongoose.js +89 -0
  14. package/src/drivers/mysql2.js +116 -0
  15. package/src/drivers/pg.js +130 -0
  16. package/src/drivers/prisma.js +109 -0
  17. package/src/drivers/record.js +158 -0
  18. package/src/index.js +28 -0
  19. package/src/integrations/nest-lifecycle.js +357 -0
  20. package/src/integrations/nest.js +89 -0
  21. package/src/interceptors/express.js +270 -0
  22. package/src/interceptors/require-hook.js +109 -0
  23. package/src/lib/config.js +139 -0
  24. package/src/lib/errors.js +54 -0
  25. package/src/lib/http-response.js +37 -0
  26. package/src/lib/logger.js +69 -0
  27. package/src/lib/serialize.js +22 -0
  28. package/src/server/static.js +165 -0
  29. package/src/server/ws.js +62 -0
  30. package/src/store/source-cache.js +120 -0
  31. package/src/store/trace-store.js +117 -0
  32. package/src/transform/ast.js +255 -0
  33. package/src/transform/natural-language.js +146 -0
  34. package/src/transform/probe.js +161 -0
  35. package/src/transform/register.js +44 -0
  36. package/src/utils/label.js +26 -0
  37. package/src/utils/observable.js +103 -0
  38. package/widget/app.js +356 -0
  39. package/widget/db-language.js +90 -0
  40. package/widget/graph.js +1167 -0
  41. package/widget/index.html +132 -0
  42. package/widget/styles.css +773 -0
  43. package/widget/timeline.js +57 -0
  44. package/widget/vendor/d3.min.js +2 -0
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const sourceCache = require('../store/source-cache');
7
+ const { logger } = require('../lib/logger');
8
+ const { WidgetServerError } = require('../lib/errors');
9
+ const { sendText, sendJsonError, sendJson } = require('../lib/http-response');
10
+
11
+ /** @type {http.Server | null} */
12
+ let server = null;
13
+
14
+ /** @type {number | null} */
15
+ let listeningPort = null;
16
+
17
+ const WIDGET_ROOT = path.join(__dirname, '..', '..', 'widget');
18
+
19
+ const MIME_TYPES = {
20
+ '.html': 'text/html; charset=utf-8',
21
+ '.css': 'text/css; charset=utf-8',
22
+ '.js': 'application/javascript; charset=utf-8',
23
+ '.json': 'application/json; charset=utf-8',
24
+ '.svg': 'image/svg+xml',
25
+ '.png': 'image/png',
26
+ '.ico': 'image/x-icon',
27
+ };
28
+
29
+ /**
30
+ * @param {import('http').IncomingMessage} req
31
+ * @param {import('http').ServerResponse} res
32
+ */
33
+ function handleWidgetRequest(req, res) {
34
+ try {
35
+ if (req.url && req.url.startsWith('/api/source')) {
36
+ return handleSourceApi(req, res);
37
+ }
38
+
39
+ const urlPath = req.url === '/' ? '/index.html' : req.url.split('?')[0];
40
+ const filePath = path.normalize(path.join(WIDGET_ROOT, urlPath));
41
+
42
+ if (!filePath.startsWith(WIDGET_ROOT)) {
43
+ sendText(res, 403, 'Forbidden');
44
+ return;
45
+ }
46
+
47
+ fs.readFile(filePath, (err, data) => {
48
+ if (err) {
49
+ sendText(res, 404, 'Not found');
50
+ return;
51
+ }
52
+
53
+ const ext = path.extname(filePath);
54
+ res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' });
55
+ res.end(data);
56
+ });
57
+ } catch (err) {
58
+ logger.error('Widget request handler failed', err);
59
+ sendJsonError(res, 500, { error: 'Internal server error', code: 'WIDGET_REQUEST_ERROR' });
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @param {number} port
65
+ * @param {number} [maxAttempts]
66
+ * @returns {Promise<{ server: http.Server, port: number }>}
67
+ */
68
+ function createStaticServer(port, maxAttempts = 10) {
69
+ if (server && listeningPort) {
70
+ return Promise.resolve({ server, port: listeningPort });
71
+ }
72
+
73
+ return new Promise((resolve, reject) => {
74
+ let attempt = 0;
75
+
76
+ const tryListen = (candidatePort) => {
77
+ const httpServer = http.createServer(handleWidgetRequest);
78
+
79
+ const onError = (err) => {
80
+ httpServer.removeListener('error', onError);
81
+ httpServer.close(() => {});
82
+
83
+ if (err.code === 'EADDRINUSE' && attempt < maxAttempts - 1) {
84
+ attempt += 1;
85
+ const nextPort = port + attempt;
86
+
87
+ if (attempt === 1) {
88
+ logger.warn(
89
+ `Port ${candidatePort} is in use — trying ${nextPort}. ` +
90
+ 'Kill the old process or set opencons({ port: N }).'
91
+ );
92
+ }
93
+
94
+ tryListen(nextPort);
95
+ return;
96
+ }
97
+
98
+ reject(new WidgetServerError(`Failed to bind widget server on port ${candidatePort}`, err));
99
+ };
100
+
101
+ httpServer.once('error', onError);
102
+ httpServer.listen(candidatePort, () => {
103
+ httpServer.removeListener('error', onError);
104
+ server = httpServer;
105
+ listeningPort = candidatePort;
106
+
107
+ logger.info(`Widget → http://localhost:${candidatePort}`);
108
+
109
+ if (candidatePort !== port) {
110
+ logger.warn(
111
+ `Port ${port} was busy. Open http://localhost:${candidatePort} (not ${port}).`
112
+ );
113
+ }
114
+
115
+ resolve({ server: httpServer, port: candidatePort });
116
+ });
117
+ };
118
+
119
+ tryListen(port);
120
+ });
121
+ }
122
+
123
+ /**
124
+ * @returns {http.Server | null}
125
+ */
126
+ function getServer() {
127
+ return server;
128
+ }
129
+
130
+ /**
131
+ * @returns {number | null}
132
+ */
133
+ function getListeningPort() {
134
+ return listeningPort;
135
+ }
136
+
137
+ /**
138
+ * @param {import('http').IncomingMessage} req
139
+ * @param {import('http').ServerResponse} res
140
+ */
141
+ function handleSourceApi(req, res) {
142
+ const url = new URL(req.url, 'http://localhost');
143
+ const file = url.searchParams.get('file');
144
+ const line = Number(url.searchParams.get('line')) || 1;
145
+
146
+ if (!file) {
147
+ sendJsonError(res, 400, { error: 'file query param required', code: 'MISSING_FILE_PARAM' });
148
+ return;
149
+ }
150
+
151
+ const snippet = sourceCache.getSnippet(file, line);
152
+
153
+ if (!snippet) {
154
+ sendJsonError(res, 404, { error: 'source not found', code: 'SOURCE_NOT_FOUND' });
155
+ return;
156
+ }
157
+
158
+ sendJson(res, snippet);
159
+ }
160
+
161
+ module.exports = {
162
+ createStaticServer,
163
+ getServer,
164
+ getListeningPort,
165
+ };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const { WebSocketServer } = require('ws');
4
+ const { getServer, getListeningPort } = require('./static');
5
+ const { logger } = require('../lib/logger');
6
+ const { WebSocketError } = require('../lib/errors');
7
+
8
+ /** @type {import('ws').WebSocketServer | null} */
9
+ let wss = null;
10
+
11
+ /**
12
+ * @param {ReturnType<import('../store/trace-store').createTraceStore>} traceStore
13
+ */
14
+ function createWebSocketServer(traceStore) {
15
+ if (wss) return wss;
16
+
17
+ const httpServer = getServer();
18
+ const port = getListeningPort();
19
+
20
+ if (!httpServer || !port) {
21
+ throw new WebSocketError('HTTP server must be listening before WebSocket server');
22
+ }
23
+
24
+ wss = new WebSocketServer({ server: httpServer });
25
+
26
+ wss.on('connection', (socket) => {
27
+ traceStore.subscribe(socket);
28
+
29
+ socket.on('message', (raw) => {
30
+ try {
31
+ const message = JSON.parse(raw.toString());
32
+
33
+ if (message.type === 'get_history') {
34
+ const history = traceStore.getAll(message.limit || 50);
35
+ socket.send(JSON.stringify({ type: 'history', payload: history }));
36
+ return;
37
+ }
38
+
39
+ logger.debug(`Ignoring unknown WebSocket message type: ${message.type}`);
40
+ } catch (err) {
41
+ logger.debug('Ignoring malformed WebSocket message', err);
42
+ }
43
+ });
44
+
45
+ socket.on('close', () => {
46
+ traceStore.unsubscribe(socket);
47
+ });
48
+
49
+ socket.on('error', (err) => {
50
+ logger.debug('WebSocket client error', err);
51
+ traceStore.unsubscribe(socket);
52
+ });
53
+ });
54
+
55
+ logger.info(`WebSocket → ws://localhost:${port}`);
56
+
57
+ return wss;
58
+ }
59
+
60
+ module.exports = {
61
+ createWebSocketServer,
62
+ };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /** @type {Map<string, { source: string, map: object | null, filename: string }>} */
7
+ const cache = new Map();
8
+
9
+ /** @type {string | null} */
10
+ let projectRoot = null;
11
+
12
+ /**
13
+ * @param {string} root
14
+ */
15
+ function setProjectRoot(root) {
16
+ projectRoot = path.normalize(root);
17
+ }
18
+
19
+ /**
20
+ * @param {string} filename
21
+ * @param {string} source
22
+ * @param {object | null} map
23
+ */
24
+ function store(filename, source, map) {
25
+ const key = normalizeKey(filename);
26
+ cache.set(key, {
27
+ filename,
28
+ source,
29
+ map,
30
+ });
31
+ }
32
+
33
+ /**
34
+ * @param {string} filename
35
+ */
36
+ function storeOriginal(filename) {
37
+ const key = normalizeKey(filename);
38
+
39
+ if (cache.has(key)) return;
40
+
41
+ try {
42
+ const source = fs.readFileSync(filename, 'utf8');
43
+ cache.set(key, { filename, source, map: null });
44
+ } catch {
45
+ // unreadable source — widget peek will return 404
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Resolve a cache entry by project-relative path, absolute path, or basename.
51
+ *
52
+ * @param {string} fileKey
53
+ */
54
+ function get(fileKey) {
55
+ const direct = cache.get(fileKey);
56
+ if (direct) return direct;
57
+
58
+ const normalized = normalizeKey(fileKey);
59
+ const byNormalized = cache.get(normalized);
60
+ if (byNormalized) return byNormalized;
61
+
62
+ const basename = path.basename(fileKey);
63
+ if (basename !== normalized) {
64
+ return cache.get(basename) || null;
65
+ }
66
+
67
+ return null;
68
+ }
69
+
70
+ /**
71
+ * Prefer project-relative paths to avoid basename collisions across folders.
72
+ *
73
+ * @param {string} filename
74
+ */
75
+ function normalizeKey(filename) {
76
+ const resolved = path.resolve(filename);
77
+
78
+ if (projectRoot) {
79
+ const relative = path.relative(projectRoot, resolved).replace(/\\/g, '/');
80
+ if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
81
+ return relative;
82
+ }
83
+ }
84
+
85
+ return path.basename(resolved);
86
+ }
87
+
88
+ /**
89
+ * @param {string} fileKey
90
+ * @param {number} line
91
+ * @param {number} [contextLines]
92
+ */
93
+ function getSnippet(fileKey, line, contextLines = 4) {
94
+ const entry = get(fileKey);
95
+ if (!entry) return null;
96
+
97
+ const lines = entry.source.split('\n');
98
+ const start = Math.max(0, line - contextLines - 1);
99
+ const end = Math.min(lines.length, line + contextLines);
100
+
101
+ return {
102
+ file: entry.filename,
103
+ line,
104
+ startLine: start + 1,
105
+ lines: lines.slice(start, end).map((text, index) => ({
106
+ number: start + index + 1,
107
+ text,
108
+ highlight: start + index + 1 === line,
109
+ })),
110
+ };
111
+ }
112
+
113
+ module.exports = {
114
+ setProjectRoot,
115
+ store,
116
+ storeOriginal,
117
+ get,
118
+ getSnippet,
119
+ normalizeKey,
120
+ };
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ const { logger } = require('../lib/logger');
4
+
5
+ /**
6
+ * In-memory store for active and completed trace graphs.
7
+ * @param {number} maxTraces
8
+ */
9
+ function createTraceStore(maxTraces = 100) {
10
+ /** @type {Map<string, import('../core/tracer').TraceGraph>} */
11
+ const active = new Map();
12
+
13
+ /** @type {import('../core/tracer').TraceGraph[]} */
14
+ const completed = [];
15
+
16
+ /** @type {Set<import('ws').WebSocket>} */
17
+ const subscribers = new Set();
18
+
19
+ /**
20
+ * @param {string} type
21
+ * @param {import('../core/tracer').TraceGraph} trace
22
+ */
23
+ function broadcast(type, trace) {
24
+ const message = JSON.stringify({ type, payload: trace });
25
+
26
+ for (const client of subscribers) {
27
+ if (client.readyState !== 1) continue;
28
+
29
+ try {
30
+ client.send(message);
31
+ } catch (err) {
32
+ logger.debug('WebSocket send failed; removing subscriber', err);
33
+ subscribers.delete(client);
34
+ }
35
+ }
36
+ }
37
+
38
+ return {
39
+ /**
40
+ * Broadcast a newly discovered in-flight request.
41
+ * @param {import('../core/tracer').TraceGraph} trace
42
+ */
43
+ start(trace) {
44
+ active.set(trace.id, trace);
45
+ broadcast('trace_start', trace);
46
+ },
47
+
48
+ /**
49
+ * Stream live progress for an active request.
50
+ * @param {import('../core/tracer').TraceGraph} trace
51
+ */
52
+ update(trace) {
53
+ if (!active.has(trace.id)) return;
54
+ active.set(trace.id, trace);
55
+ broadcast('trace_update', trace);
56
+ },
57
+
58
+ /**
59
+ * Finalise and archive a completed request trace.
60
+ * @param {import('../core/tracer').TraceGraph} trace
61
+ */
62
+ complete(trace) {
63
+ active.delete(trace.id);
64
+ completed.unshift(trace);
65
+
66
+ if (completed.length > maxTraces) {
67
+ completed.length = maxTraces;
68
+ }
69
+
70
+ broadcast('trace', trace);
71
+ },
72
+
73
+ /**
74
+ * @deprecated Use complete() — kept for internal compatibility.
75
+ * @param {import('../core/tracer').TraceGraph} trace
76
+ */
77
+ add(trace) {
78
+ this.complete(trace);
79
+ },
80
+
81
+ /**
82
+ * @param {number} [limit]
83
+ */
84
+ getAll(limit = 100) {
85
+ const activeTraces = Array.from(active.values()).sort((a, b) => b.timestamp - a.timestamp);
86
+ const merged = [...activeTraces, ...completed];
87
+ return merged.slice(0, limit);
88
+ },
89
+
90
+ /**
91
+ * @param {string} id
92
+ * @returns {import('../core/tracer').TraceGraph | undefined}
93
+ */
94
+ getById(id) {
95
+ if (active.has(id)) return active.get(id);
96
+ return completed.find((trace) => trace.id === id);
97
+ },
98
+
99
+ /**
100
+ * @param {import('ws').WebSocket} client
101
+ */
102
+ subscribe(client) {
103
+ subscribers.add(client);
104
+ },
105
+
106
+ /**
107
+ * @param {import('ws').WebSocket} client
108
+ */
109
+ unsubscribe(client) {
110
+ subscribers.delete(client);
111
+ },
112
+ };
113
+ }
114
+
115
+ module.exports = {
116
+ createTraceStore,
117
+ };
@@ -0,0 +1,255 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const parser = require('@babel/parser');
5
+ const traverse = require('@babel/traverse').default;
6
+ const generate = require('@babel/generator').default;
7
+ const t = require('@babel/types');
8
+
9
+ const PROBE_MODULE = path.join(__dirname, 'probe.js');
10
+
11
+ /**
12
+ * @param {string} filename
13
+ * @param {number} line
14
+ * @param {string} kind
15
+ * @param {string} [projectRoot]
16
+ */
17
+ function probeLabel(filename, line, kind, projectRoot) {
18
+ const resolved = path.resolve(filename);
19
+ let relative = path.basename(resolved);
20
+
21
+ if (projectRoot) {
22
+ const fromRoot = path.relative(projectRoot, resolved).replace(/\\/g, '/');
23
+ if (fromRoot && !fromRoot.startsWith('..') && !path.isAbsolute(fromRoot)) {
24
+ relative = fromRoot;
25
+ }
26
+ }
27
+
28
+ return `${kind}:${relative}:${line}`;
29
+ }
30
+
31
+ /**
32
+ * @param {string} source
33
+ * @param {import('@babel/types').Node} node
34
+ */
35
+ function sliceFromSource(source, node) {
36
+ if (typeof node.start === 'number' && typeof node.end === 'number') {
37
+ return source.slice(node.start, node.end).trim();
38
+ }
39
+
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * @param {string} label
45
+ * @param {import('@babel/types').Expression} expression
46
+ * @param {string | null} conditionText
47
+ * @param {boolean} [hasElse]
48
+ */
49
+ function probeCall(label, expression, conditionText, hasElse = false) {
50
+ const args = [t.stringLiteral(label), expression];
51
+
52
+ if (conditionText) {
53
+ args.push(t.stringLiteral(conditionText));
54
+ }
55
+
56
+ args.push(t.booleanLiteral(hasElse));
57
+
58
+ return t.callExpression(t.identifier('__rg_probe'), args);
59
+ }
60
+
61
+ /**
62
+ * @param {string} label
63
+ */
64
+ function elseProbeStatement(label) {
65
+ return t.expressionStatement(
66
+ t.callExpression(t.identifier('__rg_else_probe'), [t.stringLiteral(label)])
67
+ );
68
+ }
69
+
70
+ /**
71
+ * @param {string} source
72
+ * @param {string} filename
73
+ * @param {{ projectRoot?: string }} [options]
74
+ * @returns {{ code: string, map: object | null, skipped: boolean, reason?: string }}
75
+ */
76
+ function transformSource(source, filename, options = {}) {
77
+ const projectRoot = options.projectRoot;
78
+ if (source.includes('opencons-skip') || source.includes('routegrapher-skip')) {
79
+ return { code: source, map: null, skipped: true, reason: 'opencons-skip' };
80
+ }
81
+
82
+ if (isLikelyMinified(source)) {
83
+ return { code: source, map: null, skipped: true, reason: 'minified' };
84
+ }
85
+
86
+ let ast;
87
+
88
+ try {
89
+ ast = parser.parse(source, {
90
+ sourceType: 'script',
91
+ plugins: ['jsx', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
92
+ errorRecovery: true,
93
+ ranges: true,
94
+ });
95
+ } catch (err) {
96
+ return { code: source, map: null, skipped: true, reason: `parse-error: ${err.message}` };
97
+ }
98
+
99
+ const probeImportPath = PROBE_MODULE.replace(/\\/g, '/');
100
+
101
+ traverse(ast, {
102
+ Program(programPath) {
103
+ const body = programPath.node.body;
104
+
105
+ if (!hasProbeImport(body)) {
106
+ body.unshift(
107
+ t.variableDeclaration('const', [
108
+ t.variableDeclarator(
109
+ t.objectPattern([
110
+ t.objectProperty(t.identifier('__rg_probe'), t.identifier('__rg_probe'), false, true),
111
+ t.objectProperty(
112
+ t.identifier('__rg_else_probe'),
113
+ t.identifier('__rg_else_probe'),
114
+ false,
115
+ true
116
+ ),
117
+ t.objectProperty(
118
+ t.identifier('__rg_catch_probe'),
119
+ t.identifier('__rg_catch_probe'),
120
+ false,
121
+ true
122
+ ),
123
+ ]),
124
+ t.callExpression(t.identifier('require'), [t.stringLiteral(probeImportPath)])
125
+ ),
126
+ ])
127
+ );
128
+ }
129
+ },
130
+
131
+ IfStatement(ifPath) {
132
+ const line = ifPath.node.loc?.start.line || 0;
133
+ const label = probeLabel(filename, line, 'if', projectRoot);
134
+ const test = ifPath.node.test;
135
+ const conditionText = sliceFromSource(source, test);
136
+ const hasElse = Boolean(ifPath.node.alternate);
137
+ ifPath.node.test = probeCall(label, test, conditionText, hasElse);
138
+
139
+ if (ifPath.node.alternate) {
140
+ if (t.isBlockStatement(ifPath.node.alternate)) {
141
+ ifPath.node.alternate.body.unshift(elseProbeStatement(label));
142
+ } else {
143
+ ifPath.node.alternate = t.blockStatement([
144
+ elseProbeStatement(label),
145
+ t.isStatement(ifPath.node.alternate)
146
+ ? ifPath.node.alternate
147
+ : t.expressionStatement(ifPath.node.alternate),
148
+ ]);
149
+ }
150
+ }
151
+ },
152
+
153
+ ConditionalExpression(condPath) {
154
+ const line = condPath.node.loc?.start.line || 0;
155
+ const label = probeLabel(filename, line, 'ternary', projectRoot);
156
+ const test = condPath.node.test;
157
+ condPath.node.test = probeCall(label, test, sliceFromSource(source, test), true);
158
+ },
159
+
160
+ SwitchStatement(switchPath) {
161
+ const line = switchPath.node.loc?.start.line || 0;
162
+ const label = probeLabel(filename, line, 'switch', projectRoot);
163
+ const discriminant = switchPath.node.discriminant;
164
+ switchPath.node.discriminant = probeCall(
165
+ label,
166
+ discriminant,
167
+ sliceFromSource(source, discriminant),
168
+ false
169
+ );
170
+ },
171
+
172
+ WhileStatement(whilePath) {
173
+ const line = whilePath.node.loc?.start.line || 0;
174
+ const label = probeLabel(filename, line, 'while', projectRoot);
175
+ const test = whilePath.node.test;
176
+ whilePath.node.test = probeCall(label, test, sliceFromSource(source, test), false);
177
+ },
178
+
179
+ DoWhileStatement(doWhilePath) {
180
+ const line = doWhilePath.node.loc?.start.line || 0;
181
+ const label = probeLabel(filename, line, 'while', projectRoot);
182
+ const test = doWhilePath.node.test;
183
+ doWhilePath.node.test = probeCall(label, test, sliceFromSource(source, test), false);
184
+ },
185
+
186
+ ForStatement(forPath) {
187
+ if (!forPath.node.test) return;
188
+ const line = forPath.node.loc?.start.line || 0;
189
+ const label = probeLabel(filename, line, 'for', projectRoot);
190
+ const test = forPath.node.test;
191
+ forPath.node.test = probeCall(label, test, sliceFromSource(source, test), false);
192
+ },
193
+
194
+ CatchClause(catchPath) {
195
+ const line = catchPath.node.loc?.start.line || 0;
196
+ const label = probeLabel(filename, line, 'catch', projectRoot);
197
+ const paramName = t.isIdentifier(catchPath.node.param)
198
+ ? catchPath.node.param.name
199
+ : 'err';
200
+
201
+ catchPath.node.body.body.unshift(
202
+ t.expressionStatement(
203
+ t.callExpression(t.identifier('__rg_catch_probe'), [
204
+ t.stringLiteral(label),
205
+ t.identifier(paramName),
206
+ ])
207
+ )
208
+ );
209
+ },
210
+ });
211
+
212
+ const output = generate(ast, {
213
+ sourceMaps: true,
214
+ sourceFileName: filename,
215
+ }, source);
216
+
217
+ return {
218
+ code: output.code,
219
+ map: output.map,
220
+ skipped: false,
221
+ };
222
+ }
223
+
224
+ /**
225
+ * @param {import('@babel/types').Statement[]} body
226
+ */
227
+ function hasProbeImport(body) {
228
+ return body.some(
229
+ (stmt) =>
230
+ t.isVariableDeclaration(stmt) &&
231
+ stmt.declarations.some(
232
+ (decl) =>
233
+ t.isObjectPattern(decl.id) &&
234
+ decl.id.properties.some(
235
+ (prop) => t.isObjectProperty(prop) && t.isIdentifier(prop.key, { name: '__rg_probe' })
236
+ )
237
+ )
238
+ );
239
+ }
240
+
241
+ /**
242
+ * @param {string} source
243
+ */
244
+ function isLikelyMinified(source) {
245
+ const lines = source.split('\n');
246
+ if (lines.length < 3 && source.length > 500) return true;
247
+
248
+ const avgLine = source.length / Math.max(lines.length, 1);
249
+ return avgLine > 300;
250
+ }
251
+
252
+ module.exports = {
253
+ transformSource,
254
+ probeLabel,
255
+ };