react-anti-pattern-sniffer 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 (97) hide show
  1. package/.snifferrc.json +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +289 -0
  4. package/bin/react-sniff.js +3 -0
  5. package/dist/src/cli/arg-parser.d.ts +10 -0
  6. package/dist/src/cli/arg-parser.d.ts.map +1 -0
  7. package/dist/src/cli/arg-parser.js +81 -0
  8. package/dist/src/cli/arg-parser.js.map +1 -0
  9. package/dist/src/cli/config-loader.d.ts +11 -0
  10. package/dist/src/cli/config-loader.d.ts.map +1 -0
  11. package/dist/src/cli/config-loader.js +140 -0
  12. package/dist/src/cli/config-loader.js.map +1 -0
  13. package/dist/src/cli/help.d.ts +3 -0
  14. package/dist/src/cli/help.d.ts.map +1 -0
  15. package/dist/src/cli/help.js +59 -0
  16. package/dist/src/cli/help.js.map +1 -0
  17. package/dist/src/cli/index.d.ts +2 -0
  18. package/dist/src/cli/index.d.ts.map +1 -0
  19. package/dist/src/cli/index.js +107 -0
  20. package/dist/src/cli/index.js.map +1 -0
  21. package/dist/src/core/file-discoverer.d.ts +8 -0
  22. package/dist/src/core/file-discoverer.d.ts.map +1 -0
  23. package/dist/src/core/file-discoverer.js +151 -0
  24. package/dist/src/core/file-discoverer.js.map +1 -0
  25. package/dist/src/core/orchestrator.d.ts +13 -0
  26. package/dist/src/core/orchestrator.d.ts.map +1 -0
  27. package/dist/src/core/orchestrator.js +176 -0
  28. package/dist/src/core/orchestrator.js.map +1 -0
  29. package/dist/src/core/sniffer-ignore.d.ts +25 -0
  30. package/dist/src/core/sniffer-ignore.d.ts.map +1 -0
  31. package/dist/src/core/sniffer-ignore.js +91 -0
  32. package/dist/src/core/sniffer-ignore.js.map +1 -0
  33. package/dist/src/core/sniffer-registry.d.ts +8 -0
  34. package/dist/src/core/sniffer-registry.d.ts.map +1 -0
  35. package/dist/src/core/sniffer-registry.js +64 -0
  36. package/dist/src/core/sniffer-registry.js.map +1 -0
  37. package/dist/src/core/worker-pool.d.ts +27 -0
  38. package/dist/src/core/worker-pool.d.ts.map +1 -0
  39. package/dist/src/core/worker-pool.js +176 -0
  40. package/dist/src/core/worker-pool.js.map +1 -0
  41. package/dist/src/core/worker-runner.d.ts +2 -0
  42. package/dist/src/core/worker-runner.d.ts.map +1 -0
  43. package/dist/src/core/worker-runner.js +52 -0
  44. package/dist/src/core/worker-runner.js.map +1 -0
  45. package/dist/src/output/formatter.d.ts +3 -0
  46. package/dist/src/output/formatter.d.ts.map +1 -0
  47. package/dist/src/output/formatter.js +13 -0
  48. package/dist/src/output/formatter.js.map +1 -0
  49. package/dist/src/output/json-renderer.d.ts +3 -0
  50. package/dist/src/output/json-renderer.d.ts.map +1 -0
  51. package/dist/src/output/json-renderer.js +49 -0
  52. package/dist/src/output/json-renderer.js.map +1 -0
  53. package/dist/src/output/markdown-renderer.d.ts +3 -0
  54. package/dist/src/output/markdown-renderer.d.ts.map +1 -0
  55. package/dist/src/output/markdown-renderer.js +70 -0
  56. package/dist/src/output/markdown-renderer.js.map +1 -0
  57. package/dist/src/plugins/plugin-loader.d.ts +7 -0
  58. package/dist/src/plugins/plugin-loader.d.ts.map +1 -0
  59. package/dist/src/plugins/plugin-loader.js +47 -0
  60. package/dist/src/plugins/plugin-loader.js.map +1 -0
  61. package/dist/src/plugins/plugin-sandbox.d.ts +3 -0
  62. package/dist/src/plugins/plugin-sandbox.d.ts.map +1 -0
  63. package/dist/src/plugins/plugin-sandbox.js +105 -0
  64. package/dist/src/plugins/plugin-sandbox.js.map +1 -0
  65. package/dist/src/plugins/plugin-validator.d.ts +14 -0
  66. package/dist/src/plugins/plugin-validator.d.ts.map +1 -0
  67. package/dist/src/plugins/plugin-validator.js +92 -0
  68. package/dist/src/plugins/plugin-validator.js.map +1 -0
  69. package/dist/src/sniffers/god-hook-sniffer.d.ts +12 -0
  70. package/dist/src/sniffers/god-hook-sniffer.d.ts.map +1 -0
  71. package/dist/src/sniffers/god-hook-sniffer.js +109 -0
  72. package/dist/src/sniffers/god-hook-sniffer.js.map +1 -0
  73. package/dist/src/sniffers/prop-drilling-sniffer.d.ts +5 -0
  74. package/dist/src/sniffers/prop-drilling-sniffer.d.ts.map +1 -0
  75. package/dist/src/sniffers/prop-drilling-sniffer.js +145 -0
  76. package/dist/src/sniffers/prop-drilling-sniffer.js.map +1 -0
  77. package/dist/src/sniffers/prop-explosion-sniffer.d.ts +4 -0
  78. package/dist/src/sniffers/prop-explosion-sniffer.d.ts.map +1 -0
  79. package/dist/src/sniffers/prop-explosion-sniffer.js +134 -0
  80. package/dist/src/sniffers/prop-explosion-sniffer.js.map +1 -0
  81. package/dist/src/sniffers/sniffer-interface.d.ts +88 -0
  82. package/dist/src/sniffers/sniffer-interface.d.ts.map +1 -0
  83. package/dist/src/sniffers/sniffer-interface.js +18 -0
  84. package/dist/src/sniffers/sniffer-interface.js.map +1 -0
  85. package/dist/src/tui/interactive-viewer.d.ts +7 -0
  86. package/dist/src/tui/interactive-viewer.d.ts.map +1 -0
  87. package/dist/src/tui/interactive-viewer.js +453 -0
  88. package/dist/src/tui/interactive-viewer.js.map +1 -0
  89. package/dist/src/utils/logger.d.ts +11 -0
  90. package/dist/src/utils/logger.d.ts.map +1 -0
  91. package/dist/src/utils/logger.js +90 -0
  92. package/dist/src/utils/logger.js.map +1 -0
  93. package/dist/src/utils/regex-helpers.d.ts +53 -0
  94. package/dist/src/utils/regex-helpers.d.ts.map +1 -0
  95. package/dist/src/utils/regex-helpers.js +275 -0
  96. package/dist/src/utils/regex-helpers.js.map +1 -0
  97. package/package.json +40 -0
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SnifferRegistry = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const plugin_loader_js_1 = require("../plugins/plugin-loader.js");
6
+ const logger_js_1 = require("../utils/logger.js");
7
+ const SNIFFERS_DIR = (0, node_path_1.join)(__dirname, '..', 'sniffers');
8
+ class SnifferRegistry {
9
+ sniffers = new Map();
10
+ registerBuiltIn() {
11
+ this.sniffers.set('prop-explosion', {
12
+ snifferPath: (0, node_path_1.join)(SNIFFERS_DIR, 'prop-explosion-sniffer.js'),
13
+ config: {},
14
+ });
15
+ this.sniffers.set('god-hook', {
16
+ snifferPath: (0, node_path_1.join)(SNIFFERS_DIR, 'god-hook-sniffer.js'),
17
+ config: {},
18
+ });
19
+ this.sniffers.set('prop-drilling', {
20
+ snifferPath: (0, node_path_1.join)(SNIFFERS_DIR, 'prop-drilling-sniffer.js'),
21
+ config: {},
22
+ });
23
+ }
24
+ registerPlugin(pluginEntry, basePath) {
25
+ try {
26
+ const loaded = (0, plugin_loader_js_1.loadPlugin)(pluginEntry, basePath);
27
+ const name = loaded.module.name;
28
+ if (this.sniffers.has(name)) {
29
+ (0, logger_js_1.warn)(`Plugin "${name}" overrides an existing sniffer with the same name`);
30
+ }
31
+ this.sniffers.set(name, {
32
+ snifferPath: loaded.snifferPath,
33
+ config: loaded.config,
34
+ });
35
+ }
36
+ catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ (0, logger_js_1.warn)(`Failed to register plugin "${pluginEntry.path}": ${message}`);
39
+ }
40
+ }
41
+ getEnabledSniffers(snifferConfig) {
42
+ const enabled = [];
43
+ for (const [name, entry] of this.sniffers) {
44
+ const userConfig = snifferConfig[name];
45
+ // If the sniffer is explicitly disabled, skip it
46
+ if (userConfig && userConfig.enabled === false) {
47
+ continue;
48
+ }
49
+ // Merge user config over defaults (excluding the 'enabled' flag)
50
+ const mergedConfig = {
51
+ ...entry.config,
52
+ ...(userConfig ?? {}),
53
+ };
54
+ enabled.push({
55
+ name,
56
+ snifferPath: entry.snifferPath,
57
+ config: mergedConfig,
58
+ });
59
+ }
60
+ return enabled;
61
+ }
62
+ }
63
+ exports.SnifferRegistry = SnifferRegistry;
64
+ //# sourceMappingURL=sniffer-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sniffer-registry.js","sourceRoot":"","sources":["../../../src/core/sniffer-registry.ts"],"names":[],"mappings":";;;AAAA,yCAAiC;AACjC,kEAAyD;AAEzD,kDAA0C;AAE1C,MAAM,YAAY,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAOvD,MAAa,eAAe;IAClB,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEpD,eAAe;QACb,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,EAAE;YAClC,WAAW,EAAE,IAAA,gBAAI,EAAC,YAAY,EAAE,2BAA2B,CAAC;YAC5D,MAAM,EAAE,EAAE;SACX,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE;YAC5B,WAAW,EAAE,IAAA,gBAAI,EAAC,YAAY,EAAE,qBAAqB,CAAC;YACtD,MAAM,EAAE,EAAE;SACX,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,EAAE;YACjC,WAAW,EAAE,IAAA,gBAAI,EAAC,YAAY,EAAE,0BAA0B,CAAC;YAC3D,MAAM,EAAE,EAAE;SACX,CAAC,CAAC;IACL,CAAC;IAED,cAAc,CAAC,WAAwB,EAAE,QAAgB;QACvD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAA,6BAAU,EAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YACjD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YAEhC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,IAAA,gBAAI,EAAC,WAAW,IAAI,oDAAoD,CAAC,CAAC;YAC5E,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE;gBACtB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAA,gBAAI,EAAC,8BAA8B,WAAW,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,kBAAkB,CAChB,aAAsD;QAEtD,MAAM,OAAO,GAAmB,EAAE,CAAC;QAEnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;YAEvC,iDAAiD;YACjD,IAAI,UAAU,IAAI,UAAU,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;gBAC/C,SAAS;YACX,CAAC;YAED,iEAAiE;YACjE,MAAM,YAAY,GAA4B;gBAC5C,GAAG,KAAK,CAAC,MAAM;gBACf,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;aACtB,CAAC;YAEF,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,MAAM,EAAE,YAAY;aACrB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AAnED,0CAmEC"}
@@ -0,0 +1,27 @@
1
+ import type { WorkerResultMessage } from '../sniffers/sniffer-interface.js';
2
+ /**
3
+ * A fixed-size worker thread pool for running sniffers in parallel.
4
+ * Pre-spawns workers, queues tasks when all workers are busy,
5
+ * and enforces per-task timeouts.
6
+ */
7
+ export declare class WorkerPool {
8
+ private workers;
9
+ private taskQueue;
10
+ private pendingTasks;
11
+ private workerScript;
12
+ private poolSize;
13
+ private destroyed;
14
+ constructor(workerScript: string, poolSize: number);
15
+ private createWorker;
16
+ private processQueue;
17
+ private assignTask;
18
+ /**
19
+ * Submit a task to the pool. Returns a promise that resolves with the result.
20
+ */
21
+ runTask(snifferPath: string, fileContent: string, filePath: string, config: Record<string, unknown>, timeoutMs: number): Promise<WorkerResultMessage>;
22
+ /**
23
+ * Shut down all workers and reject any pending tasks.
24
+ */
25
+ destroy(): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=worker-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../../src/core/worker-pool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,mBAAmB,EAAsB,MAAM,kCAAkC,CAAC;AAe5G;;;;GAIG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,YAAY,CAAgE;IACpF,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;gBAEd,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IASlD,OAAO,CAAC,YAAY;IA0EpB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,UAAU;IA4BlB;;OAEG;IACH,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,CAAC;IA2B/B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAwB/B"}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkerPool = void 0;
4
+ const node_worker_threads_1 = require("node:worker_threads");
5
+ const node_crypto_1 = require("node:crypto");
6
+ /**
7
+ * A fixed-size worker thread pool for running sniffers in parallel.
8
+ * Pre-spawns workers, queues tasks when all workers are busy,
9
+ * and enforces per-task timeouts.
10
+ */
11
+ class WorkerPool {
12
+ workers = [];
13
+ taskQueue = [];
14
+ pendingTasks = new Map();
15
+ workerScript;
16
+ poolSize;
17
+ destroyed = false;
18
+ constructor(workerScript, poolSize) {
19
+ this.workerScript = workerScript;
20
+ this.poolSize = Math.max(1, poolSize);
21
+ for (let i = 0; i < this.poolSize; i++) {
22
+ this.workers.push(this.createWorker());
23
+ }
24
+ }
25
+ createWorker() {
26
+ const worker = new node_worker_threads_1.Worker(this.workerScript, {
27
+ resourceLimits: {
28
+ maxOldGenerationSizeMb: 128,
29
+ maxYoungGenerationSizeMb: 32,
30
+ codeRangeSizeMb: 16,
31
+ },
32
+ });
33
+ const entry = { worker, busy: false, currentTaskId: null };
34
+ worker.on('message', (msg) => {
35
+ const taskId = msg.taskId;
36
+ const pending = this.pendingTasks.get(taskId);
37
+ if (!pending)
38
+ return;
39
+ // Clear timeout
40
+ if (pending.timer)
41
+ clearTimeout(pending.timer);
42
+ this.pendingTasks.delete(taskId);
43
+ // Mark worker as available
44
+ entry.busy = false;
45
+ entry.currentTaskId = null;
46
+ if (msg.type === 'sniffer-result') {
47
+ pending.resolve(msg);
48
+ }
49
+ else {
50
+ pending.reject(new Error(msg.error));
51
+ }
52
+ // Process next queued task
53
+ this.processQueue();
54
+ });
55
+ worker.on('error', (err) => {
56
+ // Worker crashed — reject current task and replace worker
57
+ if (entry.currentTaskId) {
58
+ const pending = this.pendingTasks.get(entry.currentTaskId);
59
+ if (pending) {
60
+ if (pending.timer)
61
+ clearTimeout(pending.timer);
62
+ this.pendingTasks.delete(entry.currentTaskId);
63
+ pending.reject(new Error(`Worker crashed: ${err.message}`));
64
+ }
65
+ }
66
+ // Replace the crashed worker
67
+ const idx = this.workers.indexOf(entry);
68
+ if (idx !== -1 && !this.destroyed) {
69
+ this.workers[idx] = this.createWorker();
70
+ this.processQueue();
71
+ }
72
+ });
73
+ worker.on('exit', (code) => {
74
+ if (code !== 0 && entry.currentTaskId) {
75
+ const pending = this.pendingTasks.get(entry.currentTaskId);
76
+ if (pending) {
77
+ if (pending.timer)
78
+ clearTimeout(pending.timer);
79
+ this.pendingTasks.delete(entry.currentTaskId);
80
+ pending.reject(new Error(`Worker exited with code ${code}`));
81
+ }
82
+ }
83
+ // Replace exited worker if not shutting down
84
+ const idx = this.workers.indexOf(entry);
85
+ if (idx !== -1 && !this.destroyed) {
86
+ this.workers[idx] = this.createWorker();
87
+ this.processQueue();
88
+ }
89
+ });
90
+ return entry;
91
+ }
92
+ processQueue() {
93
+ if (this.taskQueue.length === 0)
94
+ return;
95
+ const idle = this.workers.find(w => !w.busy);
96
+ if (!idle)
97
+ return;
98
+ const queueEntry = this.taskQueue.shift();
99
+ this.assignTask(idle, queueEntry);
100
+ }
101
+ assignTask(workerEntry, queueEntry) {
102
+ workerEntry.busy = true;
103
+ workerEntry.currentTaskId = queueEntry.task.taskId;
104
+ this.pendingTasks.set(queueEntry.task.taskId, { ...queueEntry, workerEntry });
105
+ // Set timeout
106
+ const timer = setTimeout(() => {
107
+ const pending = this.pendingTasks.get(queueEntry.task.taskId);
108
+ if (pending) {
109
+ this.pendingTasks.delete(queueEntry.task.taskId);
110
+ pending.reject(new Error(`Sniffer timed out after ${queueEntry.task.timeoutMs}ms`));
111
+ // Terminate the timed-out worker and replace it
112
+ workerEntry.worker.terminate();
113
+ const idx = this.workers.indexOf(workerEntry);
114
+ if (idx !== -1 && !this.destroyed) {
115
+ this.workers[idx] = this.createWorker();
116
+ this.processQueue();
117
+ }
118
+ }
119
+ }, queueEntry.task.timeoutMs);
120
+ queueEntry.timer = timer;
121
+ workerEntry.worker.postMessage(queueEntry.task);
122
+ }
123
+ /**
124
+ * Submit a task to the pool. Returns a promise that resolves with the result.
125
+ */
126
+ runTask(snifferPath, fileContent, filePath, config, timeoutMs) {
127
+ if (this.destroyed) {
128
+ return Promise.reject(new Error('Worker pool has been destroyed'));
129
+ }
130
+ const task = {
131
+ type: 'run-sniffer',
132
+ taskId: (0, node_crypto_1.randomUUID)(),
133
+ snifferPath,
134
+ fileContent,
135
+ filePath,
136
+ config,
137
+ timeoutMs,
138
+ };
139
+ return new Promise((resolve, reject) => {
140
+ const queueEntry = { task, resolve, reject, timer: null };
141
+ const idle = this.workers.find(w => !w.busy);
142
+ if (idle) {
143
+ this.assignTask(idle, queueEntry);
144
+ }
145
+ else {
146
+ this.taskQueue.push(queueEntry);
147
+ }
148
+ });
149
+ }
150
+ /**
151
+ * Shut down all workers and reject any pending tasks.
152
+ */
153
+ async destroy() {
154
+ this.destroyed = true;
155
+ // Reject queued tasks
156
+ for (const entry of this.taskQueue) {
157
+ if (entry.timer)
158
+ clearTimeout(entry.timer);
159
+ entry.reject(new Error('Worker pool destroyed'));
160
+ }
161
+ this.taskQueue = [];
162
+ // Reject pending tasks
163
+ for (const [, entry] of this.pendingTasks) {
164
+ if (entry.timer)
165
+ clearTimeout(entry.timer);
166
+ entry.reject(new Error('Worker pool destroyed'));
167
+ }
168
+ this.pendingTasks.clear();
169
+ // Terminate all workers
170
+ const terminatePromises = this.workers.map(({ worker }) => worker.terminate().catch(() => { }));
171
+ await Promise.all(terminatePromises);
172
+ this.workers = [];
173
+ }
174
+ }
175
+ exports.WorkerPool = WorkerPool;
176
+ //# sourceMappingURL=worker-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-pool.js","sourceRoot":"","sources":["../../../src/core/worker-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worker-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/core/worker-runner.ts"],"names":[],"mappings":""}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_worker_threads_1 = require("node:worker_threads");
4
+ if (!node_worker_threads_1.parentPort) {
5
+ throw new Error('worker-runner.ts must be run as a worker thread');
6
+ }
7
+ const port = node_worker_threads_1.parentPort;
8
+ port.on('message', (msg) => {
9
+ if (msg.type === 'shutdown') {
10
+ process.exit(0);
11
+ }
12
+ if (msg.type === 'run-sniffer') {
13
+ const { taskId, snifferPath, fileContent, filePath, config } = msg;
14
+ const startTime = Date.now();
15
+ try {
16
+ // Load the sniffer module
17
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
18
+ const raw = require(snifferPath);
19
+ const snifferModule = raw.default ?? raw;
20
+ // Freeze config to prevent mutation by plugins
21
+ const frozenConfig = Object.freeze({ ...config });
22
+ // Run detection
23
+ const detections = snifferModule.detect(fileContent, filePath, frozenConfig);
24
+ // Validate return value
25
+ if (!Array.isArray(detections)) {
26
+ throw new Error(`Sniffer "${snifferModule.name}" did not return an array`);
27
+ }
28
+ const result = {
29
+ snifferName: snifferModule.name,
30
+ filePath,
31
+ detections,
32
+ durationMs: Date.now() - startTime,
33
+ error: null,
34
+ };
35
+ const response = {
36
+ type: 'sniffer-result',
37
+ taskId,
38
+ result,
39
+ };
40
+ port.postMessage(response);
41
+ }
42
+ catch (err) {
43
+ const response = {
44
+ type: 'sniffer-error',
45
+ taskId,
46
+ error: err instanceof Error ? err.message : String(err),
47
+ };
48
+ port.postMessage(response);
49
+ }
50
+ }
51
+ });
52
+ //# sourceMappingURL=worker-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-runner.js","sourceRoot":"","sources":["../../../src/core/worker-runner.ts"],"names":[],"mappings":";;AAAA,6DAAiD;AAGjD,IAAI,CAAC,gCAAU,EAAE,CAAC;IAChB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,IAAI,GAAG,gCAAU,CAAC;AAExB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAsC,EAAE,EAAE;IAC5D,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAC/B,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,0BAA0B;YAC1B,8DAA8D;YAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;YACjC,MAAM,aAAa,GAAkB,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;YAExD,+CAA+C;YAC/C,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;YAElD,gBAAgB;YAChB,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;YAE7E,wBAAwB;YACxB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,YAAY,aAAa,CAAC,IAAI,2BAA2B,CAAC,CAAC;YAC7E,CAAC;YAED,MAAM,MAAM,GAAkB;gBAC5B,WAAW,EAAE,aAAa,CAAC,IAAI;gBAC/B,QAAQ;gBACR,UAAU;gBACV,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAClC,KAAK,EAAE,IAAI;aACZ,CAAC;YAEF,MAAM,QAAQ,GAAwB;gBACpC,IAAI,EAAE,gBAAgB;gBACtB,MAAM;gBACN,MAAM;aACP,CAAC;YAEF,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,QAAQ,GAAuB;gBACnC,IAAI,EAAE,eAAe;gBACrB,MAAM;gBACN,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC;YAEF,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SnifferResult, SnifferConfig } from '../sniffers/sniffer-interface.js';
2
+ export declare function formatOutput(results: Map<string, SnifferResult[]>, config: SnifferConfig): string;
3
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../../src/output/formatter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAErF,wBAAgB,YAAY,CAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EACrC,MAAM,EAAE,aAAa,GACpB,MAAM,CAMR"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatOutput = formatOutput;
4
+ const markdown_renderer_js_1 = require("./markdown-renderer.js");
5
+ const json_renderer_js_1 = require("./json-renderer.js");
6
+ function formatOutput(results, config) {
7
+ const configRecord = config;
8
+ if (config.outputFormat === 'json') {
9
+ return (0, json_renderer_js_1.renderJson)(results, configRecord);
10
+ }
11
+ return (0, markdown_renderer_js_1.renderMarkdown)(results, configRecord);
12
+ }
13
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../../src/output/formatter.ts"],"names":[],"mappings":";;AAIA,oCASC;AAbD,iEAAwD;AACxD,yDAAgD;AAGhD,SAAgB,YAAY,CAC1B,OAAqC,EACrC,MAAqB;IAErB,MAAM,YAAY,GAAG,MAA4C,CAAC;IAClE,IAAI,MAAM,CAAC,YAAY,KAAK,MAAM,EAAE,CAAC;QACnC,OAAO,IAAA,6BAAU,EAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,IAAA,qCAAc,EAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SnifferResult } from '../sniffers/sniffer-interface.js';
2
+ export declare function renderJson(results: Map<string, SnifferResult[]>, _config: Record<string, unknown>): string;
3
+ //# sourceMappingURL=json-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-renderer.d.ts","sourceRoot":"","sources":["../../../src/output/json-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,kCAAkC,CAAC;AAcjF,wBAAgB,UAAU,CACxB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAkDR"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderJson = renderJson;
4
+ function renderJson(results, _config) {
5
+ const sniffersRun = new Set();
6
+ const files = {};
7
+ const summary = {};
8
+ const errors = [];
9
+ let totalIssues = 0;
10
+ for (const [, snifferResults] of results) {
11
+ for (const result of snifferResults) {
12
+ sniffersRun.add(result.snifferName);
13
+ // Collect errors
14
+ if (result.error) {
15
+ errors.push({
16
+ snifferName: result.snifferName,
17
+ filePath: result.filePath,
18
+ error: result.error,
19
+ });
20
+ }
21
+ // Collect detections grouped by file
22
+ for (const detection of result.detections) {
23
+ if (!files[detection.filePath]) {
24
+ files[detection.filePath] = [];
25
+ }
26
+ files[detection.filePath].push(detection);
27
+ totalIssues++;
28
+ // Build summary
29
+ if (!summary[detection.snifferName]) {
30
+ summary[detection.snifferName] = { count: 0, severity: detection.severity };
31
+ }
32
+ summary[detection.snifferName].count++;
33
+ }
34
+ }
35
+ }
36
+ const report = {
37
+ meta: {
38
+ fileCount: results.size,
39
+ totalIssues,
40
+ date: new Date().toISOString().split('T')[0],
41
+ sniffersRun: [...sniffersRun],
42
+ },
43
+ files,
44
+ summary,
45
+ errors,
46
+ };
47
+ return JSON.stringify(report, null, 2);
48
+ }
49
+ //# sourceMappingURL=json-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-renderer.js","sourceRoot":"","sources":["../../../src/output/json-renderer.ts"],"names":[],"mappings":";;AAcA,gCAqDC;AArDD,SAAgB,UAAU,CACxB,OAAqC,EACrC,OAAgC;IAEhC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,MAAM,KAAK,GAAgC,EAAE,CAAC;IAC9C,MAAM,OAAO,GAAwD,EAAE,CAAC;IACxE,MAAM,MAAM,GAAoE,EAAE,CAAC;IACnF,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,KAAK,MAAM,CAAC,EAAE,cAAc,CAAC,IAAI,OAAO,EAAE,CAAC;QACzC,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;YACpC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAEpC,iBAAiB;YACjB,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,CAAC;oBACV,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,KAAK,EAAE,MAAM,CAAC,KAAK;iBACpB,CAAC,CAAC;YACL,CAAC;YAED,qCAAqC;YACrC,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1C,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC/B,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACjC,CAAC;gBACD,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC1C,WAAW,EAAE,CAAC;gBAEd,gBAAgB;gBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;oBACpC,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC;gBAC9E,CAAC;gBACD,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAC;YACzC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAe;QACzB,IAAI,EAAE;YACJ,SAAS,EAAE,OAAO,CAAC,IAAI;YACvB,WAAW;YACX,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5C,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC;SAC9B;QACD,KAAK;QACL,OAAO;QACP,MAAM;KACP,CAAC;IAEF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SnifferResult } from '../sniffers/sniffer-interface.js';
2
+ export declare function renderMarkdown(results: Map<string, SnifferResult[]>, _config: Record<string, unknown>): string;
3
+ //# sourceMappingURL=markdown-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-renderer.d.ts","sourceRoot":"","sources":["../../../src/output/markdown-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,kCAAkC,CAAC;AAEjF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAwER"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderMarkdown = renderMarkdown;
4
+ function renderMarkdown(results, _config) {
5
+ const lines = [];
6
+ // Collect all detections grouped by file, and build summary
7
+ const fileDetections = new Map();
8
+ const summary = new Map();
9
+ let totalIssues = 0;
10
+ for (const [, snifferResults] of results) {
11
+ for (const result of snifferResults) {
12
+ for (const detection of result.detections) {
13
+ const existing = fileDetections.get(detection.filePath) ?? [];
14
+ existing.push(detection);
15
+ fileDetections.set(detection.filePath, existing);
16
+ totalIssues++;
17
+ const summaryEntry = summary.get(detection.snifferName) ?? {
18
+ count: 0,
19
+ severity: detection.severity,
20
+ };
21
+ summaryEntry.count++;
22
+ summary.set(detection.snifferName, summaryEntry);
23
+ }
24
+ }
25
+ }
26
+ const fileCount = results.size;
27
+ const date = new Date().toISOString().split('T')[0];
28
+ lines.push('# React Anti-Pattern Report');
29
+ lines.push('');
30
+ lines.push(`**Scanned**: ${fileCount} files | **Issues found**: ${totalIssues} | **Date**: ${date}`);
31
+ if (totalIssues === 0) {
32
+ lines.push('');
33
+ lines.push('✅ No anti-patterns detected!');
34
+ return lines.join('\n');
35
+ }
36
+ lines.push('');
37
+ lines.push('---');
38
+ // Render detections per file
39
+ for (const [filePath, detections] of fileDetections) {
40
+ lines.push('');
41
+ lines.push(`## \`${filePath}\``);
42
+ for (const detection of detections) {
43
+ lines.push('');
44
+ lines.push(`### ⚠ ${formatSnifferName(detection.snifferName)} (line ${detection.line})`);
45
+ lines.push(detection.message);
46
+ lines.push('');
47
+ lines.push('**Suggestion:**');
48
+ lines.push(`> ${detection.suggestion}`);
49
+ }
50
+ lines.push('');
51
+ lines.push('---');
52
+ }
53
+ // Summary table
54
+ lines.push('');
55
+ lines.push('## Summary');
56
+ lines.push('');
57
+ lines.push('| Sniffer | Issues | Severity |');
58
+ lines.push('|---------|--------|----------|');
59
+ for (const [snifferName, entry] of summary) {
60
+ lines.push(`| ${snifferName} | ${entry.count} | ${entry.severity} |`);
61
+ }
62
+ return lines.join('\n');
63
+ }
64
+ function formatSnifferName(name) {
65
+ return name
66
+ .split('-')
67
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
68
+ .join(' ');
69
+ }
70
+ //# sourceMappingURL=markdown-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-renderer.js","sourceRoot":"","sources":["../../../src/output/markdown-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { PluginEntry, SnifferExport } from '../sniffers/sniffer-interface.js';
2
+ export declare function loadPlugin(pluginEntry: PluginEntry, basePath: string): {
3
+ snifferPath: string;
4
+ config: Record<string, unknown>;
5
+ module: SnifferExport;
6
+ };
7
+ //# sourceMappingURL=plugin-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-loader.d.ts","sourceRoot":"","sources":["../../../src/plugins/plugin-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAGnF,wBAAgB,UAAU,CACxB,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,MAAM,GACf;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAgDjF"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadPlugin = loadPlugin;
4
+ const node_path_1 = require("node:path");
5
+ const node_fs_1 = require("node:fs");
6
+ const plugin_validator_js_1 = require("./plugin-validator.js");
7
+ const logger_js_1 = require("../utils/logger.js");
8
+ function loadPlugin(pluginEntry, basePath) {
9
+ // Resolve plugin path (relative to basePath or absolute)
10
+ const pluginPath = (0, node_path_1.resolve)(basePath, pluginEntry.path);
11
+ // Check file exists
12
+ if (!(0, node_fs_1.existsSync)(pluginPath)) {
13
+ throw new Error(`Plugin file not found: ${pluginPath}`);
14
+ }
15
+ // Run security validation (log warnings but don't block)
16
+ const securityResult = (0, plugin_validator_js_1.validatePluginSecurity)(pluginPath);
17
+ if (!securityResult.safe) {
18
+ for (const warning of securityResult.warnings) {
19
+ (0, logger_js_1.warn)(`[plugin:security] ${pluginPath}: ${warning}`);
20
+ }
21
+ }
22
+ // require() the module
23
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
24
+ const pluginModule = require(pluginPath);
25
+ // Run export validation (throw if invalid)
26
+ const exportResult = (0, plugin_validator_js_1.validatePluginExports)(pluginModule);
27
+ if (!exportResult.valid) {
28
+ throw new Error(`Plugin "${pluginPath}" has invalid exports:\n - ${exportResult.errors.join('\n - ')}`);
29
+ }
30
+ const validModule = pluginModule;
31
+ // Run smoke test (throw if fails)
32
+ const smokeResult = (0, plugin_validator_js_1.runPluginSmokeTest)(validModule);
33
+ if (!smokeResult.passed) {
34
+ throw new Error(`Plugin "${pluginPath}" failed smoke test: ${smokeResult.error}`);
35
+ }
36
+ // Merge config: plugin's defaultConfig as base, overlaid with entry config
37
+ const mergedConfig = {
38
+ ...validModule.meta.defaultConfig,
39
+ ...(pluginEntry.config ?? {}),
40
+ };
41
+ return {
42
+ snifferPath: pluginPath,
43
+ config: mergedConfig,
44
+ module: validModule,
45
+ };
46
+ }
47
+ //# sourceMappingURL=plugin-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../../src/plugins/plugin-loader.ts"],"names":[],"mappings":";;AAMA,gCAmDC;AAzDD,yCAAoC;AACpC,qCAAqC;AACrC,+DAA0G;AAE1G,kDAA0C;AAE1C,SAAgB,UAAU,CACxB,WAAwB,EACxB,QAAgB;IAEhB,yDAAyD;IACzD,MAAM,UAAU,GAAG,IAAA,mBAAO,EAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAEvD,oBAAoB;IACpB,IAAI,CAAC,IAAA,oBAAU,EAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,0BAA0B,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,yDAAyD;IACzD,MAAM,cAAc,GAAG,IAAA,4CAAsB,EAAC,UAAU,CAAC,CAAC;IAC1D,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACzB,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,QAAQ,EAAE,CAAC;YAC9C,IAAA,gBAAI,EAAC,qBAAqB,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,8DAA8D;IAC9D,MAAM,YAAY,GAAY,OAAO,CAAC,UAAU,CAAC,CAAC;IAElD,2CAA2C;IAC3C,MAAM,YAAY,GAAG,IAAA,2CAAqB,EAAC,YAAY,CAAC,CAAC;IACzD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,WAAW,UAAU,+BAA+B,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,YAA6B,CAAC;IAElD,kCAAkC;IAClC,MAAM,WAAW,GAAG,IAAA,wCAAkB,EAAC,WAAW,CAAC,CAAC;IACpD,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,WAAW,UAAU,wBAAwB,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,2EAA2E;IAC3E,MAAM,YAAY,GAA4B;QAC5C,GAAG,WAAW,CAAC,IAAI,CAAC,aAAa;QACjC,GAAG,CAAC,WAAW,CAAC,MAAM,IAAI,EAAE,CAAC;KAC9B,CAAC;IAEF,OAAO;QACL,WAAW,EAAE,UAAU;QACvB,MAAM,EAAE,YAAY;QACpB,MAAM,EAAE,WAAW;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SnifferExport } from '../sniffers/sniffer-interface.js';
2
+ export declare function wrapSnifferForSandbox(snifferModule: SnifferExport): SnifferExport;
3
+ //# sourceMappingURL=plugin-sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-sandbox.d.ts","sourceRoot":"","sources":["../../../src/plugins/plugin-sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,kCAAkC,CAAC;AA+CjF,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,aAAa,GAAG,aAAa,CA+DjF"}