@arghajit/playwright-pulse-report 0.2.1 → 0.2.3

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.
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- // input_file_0.ts
3
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
3
  if (k2 === undefined) k2 = k;
5
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -38,7 +37,8 @@ exports.PlaywrightPulseReporter = void 0;
38
37
  const fs = __importStar(require("fs/promises"));
39
38
  const path = __importStar(require("path"));
40
39
  const crypto_1 = require("crypto");
41
- const attachment_utils_1 = require("./attachment-utils"); // Use relative path
40
+ const ua_parser_js_1 = require("ua-parser-js");
41
+ const os = __importStar(require("os"));
42
42
  const convertStatus = (status, testCase) => {
43
43
  if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
44
44
  return "failed";
@@ -105,9 +105,59 @@ class PlaywrightPulseReporter {
105
105
  .catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
106
106
  }
107
107
  onTestBegin(test) {
108
- // console.log(`Starting test: ${test.title}`);
108
+ console.log(`Starting test: ${test.title}`);
109
109
  }
110
- async processStep(step, testId, browserName, testCase) {
110
+ getBrowserDetails(test) {
111
+ var _a, _b, _c, _d;
112
+ const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
113
+ const projectConfig = project === null || project === void 0 ? void 0 : project.use;
114
+ const userAgent = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.userAgent;
115
+ const configuredBrowserType = (_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.browserName) === null || _b === void 0 ? void 0 : _b.toLowerCase();
116
+ const parser = new ua_parser_js_1.UAParser(userAgent);
117
+ const result = parser.getResult();
118
+ let browserName = result.browser.name;
119
+ const browserVersion = result.browser.version
120
+ ? ` v${result.browser.version.split(".")[0]}`
121
+ : "";
122
+ const osName = result.os.name ? ` on ${result.os.name}` : "";
123
+ const osVersion = result.os.version
124
+ ? ` ${result.os.version.split(".")[0]}`
125
+ : "";
126
+ const deviceType = result.device.type;
127
+ let finalString;
128
+ if (browserName === undefined) {
129
+ browserName = configuredBrowserType;
130
+ finalString = `${browserName}`;
131
+ }
132
+ else {
133
+ if (deviceType === "mobile" || deviceType === "tablet") {
134
+ if ((_c = result.os.name) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes("android")) {
135
+ if (browserName.toLowerCase().includes("chrome"))
136
+ browserName = "Chrome Mobile";
137
+ else if (browserName.toLowerCase().includes("firefox"))
138
+ browserName = "Firefox Mobile";
139
+ else if (result.engine.name === "Blink" && !result.browser.name)
140
+ browserName = "Android WebView";
141
+ else if (browserName &&
142
+ !browserName.toLowerCase().includes("mobile")) {
143
+ // Keep it as is
144
+ }
145
+ else {
146
+ browserName = "Android Browser";
147
+ }
148
+ }
149
+ else if ((_d = result.os.name) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes("ios")) {
150
+ browserName = "Mobile Safari";
151
+ }
152
+ }
153
+ else if (browserName === "Electron") {
154
+ browserName = "Electron App";
155
+ }
156
+ finalString = `${browserName}${browserVersion}${osName}${osVersion}`;
157
+ }
158
+ return finalString.trim();
159
+ }
160
+ async processStep(step, testId, browserDetails, testCase) {
111
161
  var _a, _b, _c, _d;
112
162
  let stepStatus = "passed";
113
163
  let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
@@ -124,15 +174,14 @@ class PlaywrightPulseReporter {
124
174
  if (step.location) {
125
175
  codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
126
176
  }
127
- let stepTitle = step.title;
128
177
  return {
129
178
  id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
130
- title: stepTitle,
179
+ title: step.title,
131
180
  status: stepStatus,
132
181
  duration: duration,
133
182
  startTime: startTime,
134
183
  endTime: endTime,
135
- browser: browserName,
184
+ browser: browserDetails,
136
185
  errorMessage: errorMessage,
137
186
  stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
138
187
  codeLocation: codeLocation || undefined,
@@ -146,21 +195,16 @@ class PlaywrightPulseReporter {
146
195
  };
147
196
  }
148
197
  async onTestEnd(test, result) {
149
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
198
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
150
199
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
151
- const browserName = ((_b = project === null || project === void 0 ? void 0 : project.use) === null || _b === void 0 ? void 0 : _b.defaultBrowserType) || (project === null || project === void 0 ? void 0 : project.name) || "unknown";
200
+ const browserDetails = this.getBrowserDetails(test);
152
201
  const testStatus = convertStatus(result.status, test);
153
202
  const startTime = new Date(result.startTime);
154
203
  const endTime = new Date(startTime.getTime() + result.duration);
155
- const testIdForFiles = test.id ||
156
- `${test
157
- .titlePath()
158
- .join("_")
159
- .replace(/[^a-zA-Z0-9]/g, "_")}_${startTime.getTime()}`;
160
204
  const processAllSteps = async (steps) => {
161
205
  let processed = [];
162
206
  for (const step of steps) {
163
- const processedStep = await this.processStep(step, testIdForFiles, browserName, test);
207
+ const processedStep = await this.processStep(step, test.id, browserDetails, test);
164
208
  processed.push(processedStep);
165
209
  if (step.steps && step.steps.length > 0) {
166
210
  processedStep.steps = await processAllSteps(step.steps);
@@ -170,7 +214,7 @@ class PlaywrightPulseReporter {
170
214
  };
171
215
  let codeSnippet = undefined;
172
216
  try {
173
- if (((_c = test.location) === null || _c === void 0 ? void 0 : _c.file) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.line) && ((_e = test.location) === null || _e === void 0 ? void 0 : _e.column)) {
217
+ if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) && ((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.column)) {
174
218
  const relativePath = path.relative(this.config.rootDir, test.location.file);
175
219
  codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
176
220
  }
@@ -178,48 +222,87 @@ class PlaywrightPulseReporter {
178
222
  catch (e) {
179
223
  console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
180
224
  }
181
- const stdoutMessages = [];
182
- if (result.stdout && result.stdout.length > 0) {
183
- result.stdout.forEach((item) => {
184
- stdoutMessages.push(typeof item === "string" ? item : item.toString());
185
- });
186
- }
187
- const stderrMessages = [];
188
- if (result.stderr && result.stderr.length > 0) {
189
- result.stderr.forEach((item) => {
190
- stderrMessages.push(typeof item === "string" ? item : item.toString());
191
- });
192
- }
193
- const uniqueTestId = test.id;
225
+ const stdoutMessages = result.stdout.map((item) => typeof item === "string" ? item : item.toString());
226
+ const stderrMessages = result.stderr.map((item) => typeof item === "string" ? item : item.toString());
227
+ const maxWorkers = this.config.workers;
228
+ let mappedWorkerId = result.workerIndex === -1
229
+ ? -1
230
+ : (result.workerIndex % (maxWorkers > 0 ? maxWorkers : 1)) + 1;
231
+ const testSpecificData = {
232
+ workerId: mappedWorkerId,
233
+ totalWorkers: maxWorkers,
234
+ configFile: this.config.configFile,
235
+ metadata: this.config.metadata
236
+ ? JSON.stringify(this.config.metadata)
237
+ : undefined,
238
+ };
194
239
  const pulseResult = {
195
- id: uniqueTestId,
240
+ id: test.id,
196
241
  runId: "TBD",
197
242
  name: test.titlePath().join(" > "),
198
- suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_f = this.config.projects[0]) === null || _f === void 0 ? void 0 : _f.name) || "Default Suite",
243
+ suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_e = this.config.projects[0]) === null || _e === void 0 ? void 0 : _e.name) || "Default Suite",
199
244
  status: testStatus,
200
245
  duration: result.duration,
201
246
  startTime: startTime,
202
247
  endTime: endTime,
203
- browser: browserName,
248
+ browser: browserDetails,
204
249
  retries: result.retry,
205
- steps: ((_g = result.steps) === null || _g === void 0 ? void 0 : _g.length) ? await processAllSteps(result.steps) : [],
206
- errorMessage: (_h = result.error) === null || _h === void 0 ? void 0 : _h.message,
207
- stackTrace: (_j = result.error) === null || _j === void 0 ? void 0 : _j.stack,
250
+ steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
251
+ errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
252
+ stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
208
253
  codeSnippet: codeSnippet,
209
254
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
210
255
  screenshots: [],
211
- videoPath: undefined,
256
+ videoPath: [],
212
257
  tracePath: undefined,
258
+ attachments: [],
213
259
  stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
214
260
  stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
261
+ ...testSpecificData,
215
262
  };
216
- try {
217
- (0, attachment_utils_1.attachFiles)(testIdForFiles, result, pulseResult, this.options);
218
- }
219
- catch (attachError) {
220
- console.error(`Pulse Reporter: Error processing attachments for test ${pulseResult.name} (ID: ${testIdForFiles}): ${attachError.message}`);
263
+ // --- CORRECTED ATTACHMENT PROCESSING LOGIC ---
264
+ for (const [index, attachment] of result.attachments.entries()) {
265
+ if (!attachment.path)
266
+ continue;
267
+ try {
268
+ // Create a sanitized, unique folder name for this specific test
269
+ const testSubfolder = test.id.replace(/[^a-zA-Z0-9_-]/g, "_");
270
+ // Sanitize the original attachment name to create a safe filename
271
+ const safeAttachmentName = path
272
+ .basename(attachment.path)
273
+ .replace(/[^a-zA-Z0-9_.-]/g, "_");
274
+ // Create a unique filename to prevent collisions, especially in retries
275
+ const uniqueFileName = `${index}-${Date.now()}-${safeAttachmentName}`;
276
+ // This is the relative path that will be stored in the JSON report
277
+ const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, testSubfolder, uniqueFileName);
278
+ // This is the absolute path used for the actual file system operation
279
+ const absoluteDestPath = path.join(this.outputDir, relativeDestPath);
280
+ // Ensure the unique, test-specific attachment directory exists
281
+ await this._ensureDirExists(path.dirname(absoluteDestPath));
282
+ await fs.copyFile(attachment.path, absoluteDestPath);
283
+ // Categorize the attachment based on its content type
284
+ if (attachment.contentType.startsWith("image/")) {
285
+ (_j = pulseResult.screenshots) === null || _j === void 0 ? void 0 : _j.push(relativeDestPath);
286
+ }
287
+ else if (attachment.contentType.startsWith("video/")) {
288
+ (_k = pulseResult.videoPath) === null || _k === void 0 ? void 0 : _k.push(relativeDestPath);
289
+ }
290
+ else if (attachment.name === "trace") {
291
+ pulseResult.tracePath = relativeDestPath;
292
+ }
293
+ else {
294
+ (_l = pulseResult.attachments) === null || _l === void 0 ? void 0 : _l.push({
295
+ name: attachment.name, // The original, human-readable name
296
+ path: relativeDestPath, // The safe, relative path for linking
297
+ contentType: attachment.contentType,
298
+ });
299
+ }
300
+ }
301
+ catch (err) {
302
+ console.error(`Pulse Reporter: Failed to process attachment "${attachment.name}" for test ${pulseResult.name}. Error: ${err.message}`);
303
+ }
221
304
  }
222
- const existingTestIndex = this.results.findIndex((r) => r.id === uniqueTestId);
305
+ const existingTestIndex = this.results.findIndex((r) => r.id === test.id);
223
306
  if (existingTestIndex !== -1) {
224
307
  if (pulseResult.retries >= this.results[existingTestIndex].retries) {
225
308
  this.results[existingTestIndex] = pulseResult;
@@ -236,6 +319,20 @@ class PlaywrightPulseReporter {
236
319
  console.error(error.stack);
237
320
  }
238
321
  }
322
+ _getEnvDetails() {
323
+ return {
324
+ host: os.hostname(),
325
+ os: `${os.platform()} ${os.release()}`,
326
+ cpu: {
327
+ model: os.cpus()[0] ? os.cpus()[0].model : "N/A",
328
+ cores: os.cpus().length,
329
+ },
330
+ memory: `${(os.totalmem() / 1024 ** 3).toFixed(2)}GB`,
331
+ node: process.version,
332
+ v8: process.versions.v8,
333
+ cwd: process.cwd(),
334
+ };
335
+ }
239
336
  async _writeShardResults() {
240
337
  if (this.shardIndex === undefined) {
241
338
  return;
@@ -322,14 +419,14 @@ class PlaywrightPulseReporter {
322
419
  }
323
420
  }
324
421
  async onEnd(result) {
325
- var _a, _b, _c;
326
422
  if (this.shardIndex !== undefined) {
327
423
  await this._writeShardResults();
328
424
  return;
329
425
  }
330
426
  const runEndTime = Date.now();
331
427
  const duration = runEndTime - this.runStartTime;
332
- const runId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
428
+ const runId = `run-${this.runStartTime}-581d5ad8-ce75-4ca5-94a6-ed29c466c815`;
429
+ const environmentDetails = this._getEnvDetails();
333
430
  const runData = {
334
431
  id: runId,
335
432
  timestamp: new Date(this.runStartTime),
@@ -338,10 +435,14 @@ class PlaywrightPulseReporter {
338
435
  failed: 0,
339
436
  skipped: 0,
340
437
  duration,
438
+ environment: environmentDetails,
341
439
  };
342
- let finalReport = undefined; // Initialize as undefined
440
+ let finalReport = undefined;
343
441
  if (this.isSharded) {
344
442
  finalReport = await this._mergeShardResults(runData);
443
+ if (finalReport && finalReport.run && !finalReport.run.environment) {
444
+ finalReport.run.environment = environmentDetails;
445
+ }
345
446
  }
346
447
  else {
347
448
  this.results.forEach((r) => (r.runId = runId));
@@ -366,66 +467,8 @@ class PlaywrightPulseReporter {
366
467
  }
367
468
  if (!finalReport) {
368
469
  console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
369
- const errorSummary = `
370
- PlaywrightPulseReporter: Run Finished
371
- -----------------------------------------
372
- Overall Status: ERROR (Report data missing)
373
- Total Tests: N/A
374
- Passed: N/A
375
- Failed: N/A
376
- Skipped: N/A
377
- Duration: N/A
378
- -----------------------------------------`;
379
- if (this.printsToStdio()) {
380
- console.log(errorSummary);
381
- }
382
- const errorReport = {
383
- run: {
384
- id: runId,
385
- timestamp: new Date(this.runStartTime),
386
- totalTests: 0,
387
- passed: 0,
388
- failed: 0,
389
- skipped: 0,
390
- duration: duration,
391
- },
392
- results: [],
393
- metadata: {
394
- generatedAt: new Date().toISOString(),
395
- },
396
- };
397
- const finalOutputPathOnError = path.join(this.outputDir, this.baseOutputFile);
398
- try {
399
- await this._ensureDirExists(this.outputDir);
400
- await fs.writeFile(finalOutputPathOnError, JSON.stringify(errorReport, null, 2));
401
- console.warn(`PlaywrightPulseReporter: Wrote an error report to ${finalOutputPathOnError} as finalReport was missing.`);
402
- }
403
- catch (writeError) {
404
- console.error(`PlaywrightPulseReporter: Failed to write error report: ${writeError.message}`);
405
- }
406
470
  return;
407
471
  }
408
- const reportRunData = finalReport.run;
409
- const finalRunStatus = ((_a = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed) !== null && _a !== void 0 ? _a : 0) > 0
410
- ? "failed"
411
- : ((_b = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) !== null && _b !== void 0 ? _b : 0) === 0 && result.status !== "passed"
412
- ? result.status === "interrupted"
413
- ? "interrupted"
414
- : "no tests or error"
415
- : "passed";
416
- const summary = `
417
- PlaywrightPulseReporter: Run Finished
418
- -----------------------------------------
419
- Overall Status: ${finalRunStatus.toUpperCase()}
420
- Total Tests: ${(reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) || 0}
421
- Passed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.passed}
422
- Failed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed}
423
- Skipped: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.skipped}
424
- Duration: ${(((_c = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.duration) !== null && _c !== void 0 ? _c : 0) / 1000).toFixed(2)}s
425
- -----------------------------------------`;
426
- if (this.printsToStdio()) {
427
- console.log(summary);
428
- }
429
472
  const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
430
473
  try {
431
474
  await this._ensureDirExists(this.outputDir);
@@ -32,10 +32,19 @@ export interface TestResult {
32
32
  runId: string;
33
33
  browser: string;
34
34
  screenshots?: string[];
35
- videoPath?: string;
35
+ videoPath?: string[];
36
36
  tracePath?: string;
37
+ attachments?: {
38
+ name: string;
39
+ path: string;
40
+ contentType: string;
41
+ }[];
37
42
  stdout?: string[];
38
43
  stderr?: string[];
44
+ workerId?: number;
45
+ totalWorkers?: number;
46
+ configFile?: string;
47
+ metadata?: string;
39
48
  }
40
49
  export interface TestRun {
41
50
  id: string;
@@ -45,6 +54,7 @@ export interface TestRun {
45
54
  failed: number;
46
55
  skipped: number;
47
56
  duration: number;
57
+ environment?: EnvDetails;
48
58
  }
49
59
  export interface TrendDataPoint {
50
60
  date: string;
@@ -63,3 +73,15 @@ export interface PlaywrightPulseReporterOptions {
63
73
  outputDir?: string;
64
74
  base64Images?: boolean;
65
75
  }
76
+ export interface EnvDetails {
77
+ host: string;
78
+ os: string;
79
+ cpu: {
80
+ model: string;
81
+ cores: number;
82
+ };
83
+ memory: string;
84
+ node: string;
85
+ v8: string;
86
+ cwd: string;
87
+ }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@arghajit/playwright-pulse-report",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.2.1",
4
+ "version": "0.2.3",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
+ "homepage": "https://playwright-pulse-report.netlify.app/",
6
7
  "keywords": [
7
8
  "playwright",
8
9
  "reporter",
@@ -11,10 +12,13 @@
11
12
  "reporting",
12
13
  "nextjs",
13
14
  "playwright-pulse",
15
+ "playwright-pulse-report",
14
16
  "report",
15
17
  "email-report",
16
18
  "send-report",
17
- "email"
19
+ "email",
20
+ "playwright-report",
21
+ "pulse"
18
22
  ],
19
23
  "main": "dist/reporter/index.js",
20
24
  "types": "dist/reporter/index.d.ts",
@@ -26,9 +30,11 @@
26
30
  "license": "MIT",
27
31
  "bin": {
28
32
  "generate-pulse-report": "./scripts/generate-static-report.mjs",
33
+ "generate-report": "./scripts/generate-report.mjs",
29
34
  "merge-pulse-report": "./scripts/merge-pulse-report.js",
30
- "send-email": "./scripts/sendReport.js",
31
- "generate-trend": "./scripts/generate-trend.mjs"
35
+ "send-email": "./scripts/sendReport.mjs",
36
+ "generate-trend": "./scripts/generate-trend.mjs",
37
+ "generate-email-report": "./scripts/generate-email-report.mjs"
32
38
  },
33
39
  "exports": {
34
40
  ".": {
@@ -37,78 +43,37 @@
37
43
  }
38
44
  },
39
45
  "scripts": {
40
- "dev": "next dev --turbopack -p 9002",
41
- "genkit:dev": "genkit start -- tsx src/ai/dev.ts",
42
- "genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts",
43
46
  "build:reporter": "tsc -p tsconfig.reporter.json",
44
- "build:next": "next build",
45
- "build": "npm run build:reporter && npm run build:next",
46
- "start": "next start",
47
- "lint": "next lint",
48
47
  "typecheck": "tsc --noEmit",
49
48
  "prepublishOnly": "npm run build:reporter",
50
49
  "report:static": "node ./scripts/generate-static-report.mjs",
50
+ "report:generate": "node ./scripts/generate-report.mjs",
51
51
  "report:merge": "node ./scripts/merge-pulse-report.js",
52
- "report:email": "node ./scripts/sendReport.js"
52
+ "report:email": "node ./scripts/sendReport.mjs",
53
+ "report:minify": "node ./scripts/generate-email-report.mjs",
54
+ "generate-trend": "node ./scripts/generate-trend.mjs"
53
55
  },
54
56
  "dependencies": {
55
- "@genkit-ai/googleai": "^1.6.2",
56
- "@genkit-ai/next": "^1.6.2",
57
- "@hookform/resolvers": "^4.1.3",
58
- "@radix-ui/react-accordion": "^1.2.3",
59
- "@radix-ui/react-alert-dialog": "^1.1.6",
60
- "@radix-ui/react-avatar": "^1.1.3",
61
- "@radix-ui/react-checkbox": "^1.1.4",
62
- "@radix-ui/react-dialog": "^1.1.6",
63
- "@radix-ui/react-dropdown-menu": "^2.1.6",
64
- "@radix-ui/react-label": "^2.1.2",
65
- "@radix-ui/react-menubar": "^1.1.6",
66
- "@radix-ui/react-popover": "^1.1.6",
67
- "@radix-ui/react-progress": "^1.1.2",
68
- "@radix-ui/react-radio-group": "^1.2.3",
69
- "@radix-ui/react-scroll-area": "^1.2.3",
70
- "@radix-ui/react-select": "^2.1.6",
71
- "@radix-ui/react-separator": "^1.1.2",
72
- "@radix-ui/react-slider": "^1.2.3",
73
- "@radix-ui/react-slot": "^1.1.2",
74
- "@radix-ui/react-switch": "^1.1.3",
75
- "@radix-ui/react-tabs": "^1.1.3",
76
- "@radix-ui/react-toast": "^1.2.6",
77
- "@radix-ui/react-tooltip": "^1.1.8",
78
- "@tanstack-query-firebase/react": "^1.0.5",
79
- "@tanstack/react-query": "^5.66.0",
80
57
  "archiver": "^7.0.1",
81
58
  "class-variance-authority": "^0.7.1",
82
59
  "clsx": "^2.1.1",
83
60
  "d3": "^7.9.0",
84
61
  "date-fns": "^3.6.0",
85
62
  "dotenv": "^16.5.0",
86
- "firebase": "^11.3.0",
87
- "genkit": "^1.6.2",
88
63
  "highcharts": "^12.2.0",
89
64
  "jsdom": "^26.1.0",
90
65
  "lucide-react": "^0.475.0",
91
- "next": "15.2.3",
66
+ "node-fetch": "^3.3.2",
92
67
  "nodemailer": "^7.0.3",
93
68
  "patch-package": "^8.0.0",
94
- "react": "^18.3.1",
95
- "react-day-picker": "^8.10.1",
96
- "react-dom": "^18.3.1",
97
- "react-hook-form": "^7.54.2",
98
69
  "recharts": "^2.15.1",
99
- "tailwind-merge": "^3.0.1",
100
- "tailwindcss-animate": "^1.0.7",
70
+ "ua-parser-js": "^2.0.3",
101
71
  "zod": "^3.24.2"
102
72
  },
103
73
  "devDependencies": {
104
74
  "@types/node": "^20",
105
- "@types/react": "^18",
106
- "@types/react-dom": "^18",
75
+ "@types/ua-parser-js": "^0.7.39",
107
76
  "eslint": "9.25.1",
108
- "eslint-config-next": "15.3.1",
109
- "genkit-cli": "^1.6.1",
110
- "postcss": "^8",
111
- "tailwindcss": "^3.4.1",
112
77
  "typescript": "^5"
113
78
  },
114
79
  "engines": {