@reproapp/node-sdk 0.0.45 → 0.0.46

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/index.d.ts CHANGED
@@ -52,6 +52,7 @@ export type TraceEventForFilter = {
52
52
  fn?: string;
53
53
  wrapperClass?: string | null;
54
54
  file?: string | null;
55
+ sourceFile?: string | null;
55
56
  line?: number | null;
56
57
  depth?: number;
57
58
  library?: string | null;
package/dist/index.js CHANGED
@@ -332,6 +332,7 @@ function refreshDisabledFunctionTraceRules() {
332
332
  let disabledFunctionTraceRules = computeDisabledFunctionTraceRules();
333
333
  let disabledFunctionTypePatterns = [];
334
334
  let disabledTraceFilePatterns = [];
335
+ let excludedInstrumentationFilePatterns = [];
335
336
  let __TRACE_LOG_PREF = null;
336
337
  let __FULL_VALUE_CAPTURE_ENABLED = true;
337
338
  function setInterceptorTracingEnabled(enabled) {
@@ -415,7 +416,7 @@ function matchesRule(rule, event) {
415
416
  const wrapperPattern = rule.wrapper ?? rule.wrapperClass ?? rule.className ?? rule.owner;
416
417
  if (!matchesPattern(event.wrapperClass, wrapperPattern))
417
418
  return false;
418
- if (!matchesPattern(event.file, rule.file))
419
+ if (!matchesTraceFile(event, rule.file))
419
420
  return false;
420
421
  if (!matchesPattern(event.line == null ? null : String(event.line), rule.line))
421
422
  return false;
@@ -430,9 +431,23 @@ function matchesRule(rule, event) {
430
431
  return false;
431
432
  return true;
432
433
  }
434
+ function matchesTraceFile(event, pattern, defaultWhenEmpty = true) {
435
+ if (pattern === undefined || pattern === null)
436
+ return defaultWhenEmpty;
437
+ const candidates = [event.file, event.sourceFile].filter((value) => typeof value === 'string' && value.length > 0);
438
+ if (!candidates.length) {
439
+ return matchesPattern(null, pattern, defaultWhenEmpty);
440
+ }
441
+ return candidates.some((value) => matchesPattern(value, pattern, false));
442
+ }
433
443
  function shouldDropTraceEvent(event) {
434
444
  if (disabledTraceFilePatterns.length) {
435
- if (matchesPattern(event.file, disabledTraceFilePatterns, false)) {
445
+ if (matchesTraceFile(event, disabledTraceFilePatterns, false)) {
446
+ return true;
447
+ }
448
+ }
449
+ if (excludedInstrumentationFilePatterns.length) {
450
+ if (matchesTraceFile(event, excludedInstrumentationFilePatterns, false)) {
436
451
  return true;
437
452
  }
438
453
  }
@@ -599,6 +614,9 @@ function initReproTracing(opts) {
599
614
  if (hasOwn(options, 'disableTraceFiles')) {
600
615
  setDisabledTraceFiles(options.disableTraceFiles ?? null);
601
616
  }
617
+ if (hasOwn(options, 'exclude')) {
618
+ excludedInstrumentationFilePatterns = normalizePatternArray(options.exclude);
619
+ }
602
620
  if (hasOwn(options, 'logFunctionCalls') && typeof options.logFunctionCalls === 'boolean') {
603
621
  setReproTraceLogsEnabled(options.logFunctionCalls);
604
622
  }
@@ -611,7 +629,17 @@ function initReproTracing(opts) {
611
629
  __TRACER__ = tracerPkg;
612
630
  applyTraceLogPreference(tracerPkg);
613
631
  const { disableFunctionTraces: _disableFunctionTraces, disableFunctionTypes: _disableFunctionTypes, disableTraceFiles: _disableTraceFiles, fullValueCapture: _fullValueCapture, logFunctionCalls: _logFunctionCalls, traceInterceptors: _traceInterceptors, ...rest } = options;
614
- const initOpts = { ...defaultTracerInitOpts(), ...rest };
632
+ const defaults = defaultTracerInitOpts();
633
+ const overrides = rest;
634
+ const initOpts = {
635
+ ...defaults,
636
+ ...overrides,
637
+ include: overrides.include ?? defaults.include,
638
+ exclude: [
639
+ ...(defaults.exclude ?? []),
640
+ ...(overrides.exclude ?? []),
641
+ ],
642
+ };
615
643
  tracerPkg.init?.(initOpts);
616
644
  tracerPkg.patchHttp?.();
617
645
  applyTraceLogPreference(tracerPkg);
@@ -736,6 +764,7 @@ function traceEventCandidateFromRecord(event) {
736
764
  fn: event.fn,
737
765
  wrapperClass: inferWrapperClassFromFn(event.fn),
738
766
  file: event.file ?? null,
767
+ sourceFile: sourceFileForLibrary,
739
768
  line: event.line ?? null,
740
769
  depth: event.depth,
741
770
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? event.file),
@@ -6336,6 +6365,7 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
6336
6365
  fn: raw.fn,
6337
6366
  wrapperClass: inferWrapperClassFromFn(raw.fn),
6338
6367
  file: raw.file ?? null,
6368
+ sourceFile: sourceFileForLibrary,
6339
6369
  line: typeof raw.line === 'number' ? raw.line : null,
6340
6370
  depth: typeof raw.depth === 'number' ? raw.depth : undefined,
6341
6371
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
@@ -6441,6 +6471,7 @@ function buildPendingKafkaTraceEvent(raw) {
6441
6471
  fn: raw.fn,
6442
6472
  wrapperClass: inferWrapperClassFromFn(raw.fn),
6443
6473
  file: raw.file ?? null,
6474
+ sourceFile: sourceFileForLibrary,
6444
6475
  line: typeof raw.line === 'number' ? raw.line : null,
6445
6476
  depth: typeof raw.depth === 'number' ? raw.depth : undefined,
6446
6477
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "build": "tsc -p tsconfig.json",
13
13
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
14
14
  "prepublishOnly": "npm run build",
15
- "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
15
+ "test": "npm run build && node test/trace-source-exclude.test.js && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
16
16
  "test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
17
17
  },
18
18
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -341,6 +341,7 @@ export type TraceEventForFilter = {
341
341
  fn?: string;
342
342
  wrapperClass?: string | null;
343
343
  file?: string | null;
344
+ sourceFile?: string | null;
344
345
  line?: number | null;
345
346
  depth?: number;
346
347
  library?: string | null;
@@ -606,6 +607,7 @@ function refreshDisabledFunctionTraceRules() {
606
607
  let disabledFunctionTraceRules: DisableFunctionTraceConfig[] = computeDisabledFunctionTraceRules();
607
608
  let disabledFunctionTypePatterns: Array<string | number | RegExp> = [];
608
609
  let disabledTraceFilePatterns: Array<string | number | RegExp> = [];
610
+ let excludedInstrumentationFilePatterns: Array<string | number | RegExp> = [];
609
611
  let __TRACE_LOG_PREF: boolean | null = null;
610
612
  let __FULL_VALUE_CAPTURE_ENABLED = true;
611
613
 
@@ -684,7 +686,7 @@ function matchesRule(rule: DisableFunctionTraceRule, event: TraceEventForFilter)
684
686
  const wrapperPattern = rule.wrapper ?? rule.wrapperClass ?? rule.className ?? rule.owner;
685
687
  if (!matchesPattern(event.wrapperClass, wrapperPattern)) return false;
686
688
 
687
- if (!matchesPattern(event.file, rule.file)) return false;
689
+ if (!matchesTraceFile(event, rule.file)) return false;
688
690
  if (!matchesPattern(event.line == null ? null : String(event.line), rule.line)) return false;
689
691
 
690
692
  const libPattern = rule.lib ?? rule.library;
@@ -699,9 +701,29 @@ function matchesRule(rule: DisableFunctionTraceRule, event: TraceEventForFilter)
699
701
  return true;
700
702
  }
701
703
 
704
+ function matchesTraceFile(
705
+ event: Pick<TraceEventForFilter, 'file' | 'sourceFile'>,
706
+ pattern?: TraceRulePattern,
707
+ defaultWhenEmpty: boolean = true,
708
+ ): boolean {
709
+ if (pattern === undefined || pattern === null) return defaultWhenEmpty;
710
+ const candidates = [event.file, event.sourceFile].filter(
711
+ (value): value is string => typeof value === 'string' && value.length > 0,
712
+ );
713
+ if (!candidates.length) {
714
+ return matchesPattern(null, pattern, defaultWhenEmpty);
715
+ }
716
+ return candidates.some((value) => matchesPattern(value, pattern, false));
717
+ }
718
+
702
719
  function shouldDropTraceEvent(event: TraceEventForFilter): boolean {
703
720
  if (disabledTraceFilePatterns.length) {
704
- if (matchesPattern(event.file, disabledTraceFilePatterns, false)) {
721
+ if (matchesTraceFile(event, disabledTraceFilePatterns, false)) {
722
+ return true;
723
+ }
724
+ }
725
+ if (excludedInstrumentationFilePatterns.length) {
726
+ if (matchesTraceFile(event, excludedInstrumentationFilePatterns, false)) {
705
727
  return true;
706
728
  }
707
729
  }
@@ -948,6 +970,9 @@ export function initReproTracing(opts?: ReproTracingInitOptions) {
948
970
  if (hasOwn(options, 'disableTraceFiles')) {
949
971
  setDisabledTraceFiles(options.disableTraceFiles ?? null);
950
972
  }
973
+ if (hasOwn(options, 'exclude')) {
974
+ excludedInstrumentationFilePatterns = normalizePatternArray(options.exclude);
975
+ }
951
976
  if (hasOwn(options, 'logFunctionCalls') && typeof options.logFunctionCalls === 'boolean') {
952
977
  setReproTraceLogsEnabled(options.logFunctionCalls);
953
978
  }
@@ -971,7 +996,17 @@ export function initReproTracing(opts?: ReproTracingInitOptions) {
971
996
  traceInterceptors: _traceInterceptors,
972
997
  ...rest
973
998
  } = options;
974
- const initOpts = { ...defaultTracerInitOpts(), ...(rest as TracerInitOpts) };
999
+ const defaults = defaultTracerInitOpts();
1000
+ const overrides = rest as TracerInitOpts;
1001
+ const initOpts = {
1002
+ ...defaults,
1003
+ ...overrides,
1004
+ include: overrides.include ?? defaults.include,
1005
+ exclude: [
1006
+ ...(defaults.exclude ?? []),
1007
+ ...(overrides.exclude ?? []),
1008
+ ],
1009
+ };
975
1010
  tracerPkg.init?.(initOpts);
976
1011
  tracerPkg.patchHttp?.();
977
1012
  applyTraceLogPreference(tracerPkg);
@@ -1094,6 +1129,7 @@ function traceEventCandidateFromRecord(event: PendingTraceEventRecord): TraceEve
1094
1129
  fn: event.fn,
1095
1130
  wrapperClass: inferWrapperClassFromFn(event.fn),
1096
1131
  file: event.file ?? null,
1132
+ sourceFile: sourceFileForLibrary,
1097
1133
  line: event.line ?? null,
1098
1134
  depth: event.depth,
1099
1135
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? event.file),
@@ -7595,6 +7631,7 @@ function recordKafkaTraceEvent(
7595
7631
  fn: raw.fn,
7596
7632
  wrapperClass: inferWrapperClassFromFn(raw.fn),
7597
7633
  file: raw.file ?? null,
7634
+ sourceFile: sourceFileForLibrary,
7598
7635
  line: typeof raw.line === 'number' ? raw.line : null,
7599
7636
  depth: typeof raw.depth === 'number' ? raw.depth : undefined,
7600
7637
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
@@ -7727,6 +7764,7 @@ function buildPendingKafkaTraceEvent(raw: any): PendingTraceEventRecord | null {
7727
7764
  fn: raw.fn,
7728
7765
  wrapperClass: inferWrapperClassFromFn(raw.fn),
7729
7766
  file: raw.file ?? null,
7767
+ sourceFile: sourceFileForLibrary,
7730
7768
  line: typeof raw.line === 'number' ? raw.line : null,
7731
7769
  depth: typeof raw.depth === 'number' ? raw.depth : undefined,
7732
7770
  library: inferLibraryNameFromFile(sourceFileForLibrary ?? raw.file),
@@ -0,0 +1,222 @@
1
+ 'use strict';
2
+
3
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
4
+ process.env.TRACE_IDLE_FLUSH_MS = '25';
5
+ process.env.SESSION_DRAIN_TIMEOUT_MS = '0';
6
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
7
+
8
+ const assert = require('node:assert');
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
11
+ const { EventEmitter } = require('node:events');
12
+ const {
13
+ initReproTracing,
14
+ reproMiddleware,
15
+ } = require('../dist');
16
+
17
+ const excludeTenantModule =
18
+ /src[\\/]modules[\\/]core[\\/]database[\\/]modules[\\/]tenant[\\/]tenant-database\.module\.ts$/;
19
+
20
+ function writeFixture(root) {
21
+ const distRoot = path.join(root, 'dist');
22
+ const tenantDistDir = path.join(
23
+ distRoot,
24
+ 'modules/core/database/modules/tenant',
25
+ );
26
+ const tenantSourceFile = path.join(
27
+ root,
28
+ 'src/modules/core/database/modules/tenant/tenant-database.module.ts',
29
+ );
30
+ const tenantDistFile = path.join(tenantDistDir, 'tenant-database.module.js');
31
+ const tenantMapFile = `${tenantDistFile}.map`;
32
+ const callerFile = path.join(distRoot, 'caller.js');
33
+
34
+ fs.mkdirSync(tenantDistDir, { recursive: true });
35
+ fs.writeFileSync(
36
+ tenantDistFile,
37
+ [
38
+ "'use strict';",
39
+ 'function connectTenantDatabase() {',
40
+ " return 'connected';",
41
+ '}',
42
+ 'module.exports = { connectTenantDatabase };',
43
+ '//# sourceMappingURL=tenant-database.module.js.map',
44
+ '',
45
+ ].join('\n'),
46
+ );
47
+ fs.writeFileSync(
48
+ tenantMapFile,
49
+ JSON.stringify({
50
+ version: 3,
51
+ file: path.basename(tenantDistFile),
52
+ sourceRoot: '',
53
+ sources: [tenantSourceFile],
54
+ names: [],
55
+ mappings: '',
56
+ }),
57
+ );
58
+ fs.writeFileSync(
59
+ callerFile,
60
+ [
61
+ "'use strict';",
62
+ "const tenant = require('./modules/core/database/modules/tenant/tenant-database.module');",
63
+ 'function invokeTenantDatabase() {',
64
+ ' return tenant.connectTenantDatabase();',
65
+ '}',
66
+ 'module.exports = { invokeTenantDatabase, tenant };',
67
+ '',
68
+ ].join('\n'),
69
+ );
70
+
71
+ return { callerFile, tenantSourceFile };
72
+ }
73
+
74
+ function makeReqRes() {
75
+ const req = new EventEmitter();
76
+ req.method = 'GET';
77
+ req.url = '/source-exclude';
78
+ req.headers = {
79
+ 'x-bug-session-id': 'S_trace_source_exclude',
80
+ 'x-bug-action-id': 'A_trace_source_exclude',
81
+ 'x-bug-request-start': String(Date.now()),
82
+ };
83
+ req.params = {};
84
+ req.query = {};
85
+
86
+ const res = new EventEmitter();
87
+ res.statusCode = 200;
88
+ res.getHeader = () => undefined;
89
+ res.setHeader = () => {};
90
+ res.json = function json(body) {
91
+ this.body = body;
92
+ this.emit('finish');
93
+ return body;
94
+ };
95
+ res.send = res.json;
96
+ res.write = () => true;
97
+ res.end = () => {
98
+ res.emit('finish');
99
+ return true;
100
+ };
101
+
102
+ return { req, res };
103
+ }
104
+
105
+ function traceEventsFromPosts(posts) {
106
+ const traces = [];
107
+ const append = (value) => {
108
+ if (Array.isArray(value)) {
109
+ traces.push(...value);
110
+ return;
111
+ }
112
+ if (typeof value !== 'string') return;
113
+ try {
114
+ const parsed = JSON.parse(value);
115
+ if (Array.isArray(parsed)) traces.push(...parsed);
116
+ } catch {}
117
+ };
118
+
119
+ for (const post of posts) {
120
+ let payload;
121
+ try {
122
+ payload = JSON.parse(String(post.body || ''));
123
+ } catch {
124
+ continue;
125
+ }
126
+ for (const entry of Array.isArray(payload.entries) ? payload.entries : []) {
127
+ append(entry?.trace);
128
+ append(entry?.request?.trace);
129
+ }
130
+ for (const event of Array.isArray(payload.events) ? payload.events : []) {
131
+ append(event?.payload?.trace);
132
+ append(event?.payload?.request?.trace);
133
+ }
134
+ }
135
+
136
+ return traces;
137
+ }
138
+
139
+ async function main() {
140
+ const fixtureRoot = fs.mkdtempSync(path.join(__dirname, '.tmp-source-exclude-'));
141
+ const posts = [];
142
+ const originalFetch = global.fetch;
143
+
144
+ try {
145
+ const fixture = writeFixture(fixtureRoot);
146
+ const includeFixture = new RegExp(
147
+ `^${fixtureRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
148
+ );
149
+
150
+ global.fetch = async (url, options = {}) => {
151
+ posts.push({ url: String(url), body: options.body });
152
+ return {
153
+ ok: true,
154
+ status: 200,
155
+ json: async () => ({ ok: true }),
156
+ text: async () => '{"ok":true}',
157
+ };
158
+ };
159
+
160
+ initReproTracing({
161
+ include: [includeFixture],
162
+ exclude: [excludeTenantModule],
163
+ logFunctionCalls: false,
164
+ });
165
+
166
+ const caller = require(fixture.callerFile);
167
+ assert.strictEqual(
168
+ caller.tenant.connectTenantDatabase.__repro_instrumented,
169
+ undefined,
170
+ 'source-mapped excluded module must not be instrumented',
171
+ );
172
+ assert.strictEqual(
173
+ caller.invokeTenantDatabase.__repro_instrumented,
174
+ true,
175
+ 'non-excluded caller must remain instrumented',
176
+ );
177
+
178
+ const middleware = reproMiddleware({
179
+ appId: 'APP_trace_source_exclude',
180
+ tenantId: 'TENANT_trace_source_exclude',
181
+ appSecret: 'secret',
182
+ captureHeaders: false,
183
+ });
184
+ const { req, res } = makeReqRes();
185
+ await new Promise((resolve, reject) => {
186
+ middleware(req, res, () => {
187
+ try {
188
+ assert.strictEqual(caller.invokeTenantDatabase(), 'connected');
189
+ res.json({ ok: true });
190
+ resolve();
191
+ } catch (error) {
192
+ reject(error);
193
+ }
194
+ });
195
+ });
196
+ await new Promise((resolve) => setTimeout(resolve, 500));
197
+
198
+ const traces = traceEventsFromPosts(posts);
199
+ assert(
200
+ traces.some((event) => event?.fn === 'invokeTenantDatabase'),
201
+ `expected caller trace, received ${JSON.stringify(traces)}`,
202
+ );
203
+ assert(
204
+ !traces.some(
205
+ (event) =>
206
+ event?.fn === 'connectTenantDatabase' ||
207
+ event?.sourceFile === fixture.tenantSourceFile,
208
+ ),
209
+ `excluded source leaked into trace payload: ${JSON.stringify(traces)}`,
210
+ );
211
+
212
+ console.log('source-mapped tracing exclusion OK');
213
+ } finally {
214
+ global.fetch = originalFetch;
215
+ fs.rmSync(fixtureRoot, { recursive: true, force: true });
216
+ }
217
+ }
218
+
219
+ main().catch((error) => {
220
+ console.error(error);
221
+ process.exitCode = 1;
222
+ });
@@ -31,16 +31,19 @@ function tagExports(value, filename, seen = new WeakSet(), depth = 0, instrument
31
31
 
32
32
  if (typeof value === 'function') {
33
33
  try {
34
- if (!value[SYM_SRC_FILE]) {
34
+ const existingSourceFile = value[SYM_SRC_FILE];
35
+ const belongsToCurrentModule =
36
+ !existingSourceFile || toPosix(existingSourceFile) === toPosix(filename);
37
+ if (!existingSourceFile) {
35
38
  Object.defineProperty(value, SYM_SRC_FILE, { value: filename, configurable: true });
36
39
  }
37
- if (value[SYM_IS_APP] !== isApp) {
40
+ if (belongsToCurrentModule && value[SYM_IS_APP] !== isApp) {
38
41
  Object.defineProperty(value, SYM_IS_APP, { value: isApp, configurable: true });
39
42
  }
40
- if (instrumented && value.__repro_instrumented !== true) {
43
+ if (instrumented && belongsToCurrentModule && value.__repro_instrumented !== true) {
41
44
  Object.defineProperty(value, '__repro_instrumented', { value: true, configurable: true });
42
45
  }
43
- if (instrumented && value[SYM_BODY_TRACED] !== true) {
46
+ if (instrumented && belongsToCurrentModule && value[SYM_BODY_TRACED] !== true) {
44
47
  Object.defineProperty(value, SYM_BODY_TRACED, { value: true, configurable: true });
45
48
  }
46
49
  } catch {}
@@ -82,10 +85,24 @@ function installCJS({ include, exclude, parserPlugins } = {}) {
82
85
  : [ new RegExp('^' + escapeRx(CWD + '/')) ];
83
86
  const exc = Array.isArray(exclude) ? exclude : [];
84
87
 
85
- const shouldHandle = (f) => {
86
- const s = String(f || '').replace(/\\/g, '/');
87
- if (exc.some(rx => rx.test(s))) return false;
88
- return inc.some(rx => rx.test(s));
88
+ const matchesAny = (patterns, file) => {
89
+ const normalized = toPosix(file);
90
+ return patterns.some((rx) => {
91
+ try {
92
+ rx.lastIndex = 0;
93
+ const matched = rx.test(normalized);
94
+ rx.lastIndex = 0;
95
+ return matched;
96
+ } catch {
97
+ return false;
98
+ }
99
+ });
100
+ };
101
+
102
+ const shouldHandle = (physicalFile, sourceFile) => {
103
+ const candidates = [physicalFile, sourceFile].filter(Boolean);
104
+ if (candidates.some(file => matchesAny(exc, file))) return false;
105
+ return candidates.some(file => matchesAny(inc, file));
89
106
  };
90
107
 
91
108
  // ---- Global hook: intercept *all* compiles, regardless of how .ts was compiled ----
@@ -100,8 +117,7 @@ function installCJS({ include, exclude, parserPlugins } = {}) {
100
117
  if (transformedFiles.has(filename)) {
101
118
  // Already transformed once; avoid double-transforming code that may already include __repro_call wrappers.
102
119
  out = code;
103
- } else if (shouldHandle(filename) && isAppFile(filename)) {
104
- try { process.stderr.write(`[trace-debug] compile: ${filename}\n`); } catch {}
120
+ } else if (isAppFile(filename)) {
105
121
  const sourceInfo = getSourceInfo(code, filename);
106
122
  if (sourceInfo?.metaFilename) {
107
123
  metaFilename = sourceInfo.metaFilename;
@@ -109,35 +125,38 @@ function installCJS({ include, exclude, parserPlugins } = {}) {
109
125
  if (typeof sourceInfo?.mapOriginalPosition === 'function') {
110
126
  mapOriginalPosition = sourceInfo.mapOriginalPosition;
111
127
  }
112
- // Transform the already-compiled JS (keeps Nest’s decorator metadata intact)
113
- const res = babel.transformSync(code, {
114
- filename,
115
- sourceType: 'unambiguous',
116
- retainLines: true,
117
- sourceMaps: 'inline',
118
- parserOpts: {
128
+ if (shouldHandle(filename, metaFilename)) {
129
+ try { process.stderr.write(`[trace-debug] compile: ${filename}\n`); } catch {}
130
+ // Transform the already-compiled JS (keeps Nest’s decorator metadata intact).
131
+ const res = babel.transformSync(code, {
132
+ filename,
119
133
  sourceType: 'unambiguous',
120
- // We’re parsing JS here; TS was compiled by ts-node/Nest.
121
- plugins: parserPlugins || [
122
- 'jsx', 'classProperties', 'classPrivateProperties',
123
- 'classPrivateMethods', 'dynamicImport', 'topLevelAwait',
124
- 'optionalChaining', 'nullishCoalescingOperator',
134
+ retainLines: true,
135
+ sourceMaps: 'inline',
136
+ parserOpts: {
137
+ sourceType: 'unambiguous',
138
+ // We’re parsing JS here; TS was compiled by ts-node/Nest.
139
+ plugins: parserPlugins || [
140
+ 'jsx', 'classProperties', 'classPrivateProperties',
141
+ 'classPrivateMethods', 'dynamicImport', 'topLevelAwait',
142
+ 'optionalChaining', 'nullishCoalescingOperator',
143
+ ],
144
+ },
145
+ // only the wrap plugin; do NOT run TS transform here
146
+ plugins: [
147
+ [ makeWrap(metaFilename, {
148
+ mode: 'all',
149
+ wrapGettersSetters: false,
150
+ skipAnonymous: false,
151
+ mapOriginalPosition,
152
+ }) ],
125
153
  ],
126
- },
127
- // only the wrap plugin; do NOT run TS transform here
128
- plugins: [
129
- [ makeWrap(metaFilename, {
130
- mode: 'all',
131
- wrapGettersSetters: false,
132
- skipAnonymous: false,
133
- mapOriginalPosition,
134
- }) ],
135
- ],
136
- compact: false,
137
- comments: true,
138
- });
139
- out = res?.code || code;
140
- wasInstrumented = !!res?.code;
154
+ compact: false,
155
+ comments: true,
156
+ });
157
+ out = res?.code || code;
158
+ wasInstrumented = !!res?.code;
159
+ }
141
160
  transformedFiles.add(filename);
142
161
  }
143
162
  } catch (err) {