@testomatio/reporter 2.3.9-beta-bin-fix → 2.4.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.
- package/README.md +3 -2
- package/lib/adapter/codecept.js +12 -9
- package/lib/bin/cli.js +40 -11
- package/lib/bin/reportXml.js +5 -2
- package/lib/client.d.ts +1 -11
- package/lib/client.js +57 -152
- package/lib/data-storage.d.ts +1 -1
- package/lib/helpers.d.ts +1 -0
- package/lib/helpers.js +4 -0
- package/lib/junit-adapter/csharp.d.ts +0 -1
- package/lib/junit-adapter/csharp.js +43 -7
- package/lib/junit-adapter/nunit-parser.d.ts +82 -0
- package/lib/junit-adapter/nunit-parser.js +433 -0
- package/lib/pipe/bitbucket.js +5 -5
- package/lib/pipe/coverage.d.ts +82 -0
- package/lib/pipe/coverage.js +373 -0
- package/lib/pipe/gitlab.js +4 -4
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.d.ts +3 -2
- package/lib/pipe/testomatio.js +44 -18
- package/lib/reporter-functions.js +14 -12
- package/lib/reporter.d.ts +31 -21
- package/lib/reporter.js +40 -5
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/links.d.ts +1 -1
- package/lib/services/logger.d.ts +1 -1
- package/lib/uploader.js +4 -0
- package/lib/utils/log-formatter.d.ts +28 -0
- package/lib/utils/log-formatter.js +127 -0
- package/lib/utils/pipe_utils.d.ts +15 -0
- package/lib/utils/pipe_utils.js +44 -2
- package/lib/utils/utils.d.ts +6 -0
- package/lib/utils/utils.js +260 -25
- package/lib/xmlReader.d.ts +32 -26
- package/lib/xmlReader.js +121 -52
- package/package.json +12 -7
- package/src/adapter/codecept.js +19 -19
- package/src/adapter/mocha.js +1 -1
- package/src/adapter/playwright.js +2 -2
- package/src/bin/cli.js +51 -13
- package/src/bin/reportXml.js +5 -2
- package/src/client.js +69 -130
- package/src/helpers.js +1 -0
- package/src/junit-adapter/csharp.js +48 -6
- package/src/junit-adapter/nunit-parser.js +474 -0
- package/src/pipe/bitbucket.js +5 -5
- package/src/pipe/coverage.js +440 -0
- package/src/pipe/debug.js +1 -2
- package/src/pipe/gitlab.js +4 -4
- package/src/pipe/index.js +2 -0
- package/src/pipe/testomatio.js +109 -85
- package/src/reporter-functions.js +15 -12
- package/src/reporter.js +6 -4
- package/src/services/links.js +1 -1
- package/src/uploader.js +5 -0
- package/src/utils/log-formatter.js +113 -0
- package/src/utils/pipe_utils.js +52 -3
- package/src/utils/utils.js +277 -22
- package/src/xmlReader.js +144 -46
- package/types/types.d.ts +364 -0
- package/types/vitest.types.d.ts +93 -0
package/src/client.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
|
-
import createCallsiteRecord from 'callsite-record';
|
|
3
|
-
import { minimatch } from 'minimatch';
|
|
4
2
|
import fs from 'fs';
|
|
5
3
|
import pc from 'picocolors';
|
|
6
|
-
import { randomUUID } from 'crypto';
|
|
7
4
|
import { APP_PREFIX, STATUS } from './constants.js';
|
|
8
5
|
import { pipesFactory } from './pipe/index.js';
|
|
9
6
|
import { glob } from 'glob';
|
|
10
|
-
import path
|
|
7
|
+
import path from 'path';
|
|
11
8
|
import { fileURLToPath } from 'node:url';
|
|
12
9
|
import { S3Uploader } from './uploader.js';
|
|
13
|
-
import {
|
|
10
|
+
import { readLatestRunId, storeRunId, validateSuiteId, transformEnvVarToBoolean } from './utils/utils.js';
|
|
14
11
|
import { filesize as prettyBytes } from 'filesize';
|
|
12
|
+
import { formatLogs, formatError, stripColors } from './utils/log-formatter.js';
|
|
15
13
|
|
|
16
14
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
17
15
|
|
|
@@ -67,35 +65,43 @@ class Client {
|
|
|
67
65
|
* array containing the prepared execution list,
|
|
68
66
|
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
69
67
|
*/
|
|
68
|
+
|
|
70
69
|
async prepareRun(params) {
|
|
71
|
-
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
72
70
|
const { pipe, pipeOptions } = params;
|
|
71
|
+
|
|
72
|
+
// ❗ Validation: pipe is required
|
|
73
|
+
if (!pipe || !pipeOptions) {
|
|
74
|
+
console.warn(`❗ No valid pipe found in filter cmd. Expected format: <pipe>:<options>
|
|
75
|
+
Examples:
|
|
76
|
+
--filter "testomatio:tag-name=frontend"
|
|
77
|
+
--filter "coverage:file=coverage.yml"
|
|
78
|
+
--filter-list "coverage:file=coverage.yml"
|
|
79
|
+
Received: "${params}"`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
84
|
+
|
|
73
85
|
// all pipes disabled, skipping
|
|
74
86
|
if (!this.pipes.some(p => p.isEnabled)) {
|
|
75
87
|
return Promise.resolve();
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
try {
|
|
79
|
-
const
|
|
91
|
+
const p = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
92
|
+
// const p = this.pipes.find(p => p.id === `${pipe.toLowerCase()}`); TODO: as future updates
|
|
80
93
|
|
|
81
|
-
if (!
|
|
82
|
-
// TODO:for the future for the another pipes
|
|
94
|
+
if (!p?.isEnabled) {
|
|
83
95
|
console.warn(
|
|
84
96
|
APP_PREFIX,
|
|
85
|
-
|
|
97
|
+
"🚫 No active pipes were found in the system. Execution aborted!"
|
|
86
98
|
);
|
|
87
99
|
return;
|
|
88
100
|
}
|
|
89
101
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
95
|
-
|
|
96
|
-
if (!result || result.length === 0) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
102
|
+
// Run only the selected pipe
|
|
103
|
+
const rawResult = await p.prepareRun(pipeOptions);
|
|
104
|
+
const result = Array.isArray(rawResult) ? rawResult : [];
|
|
99
105
|
|
|
100
106
|
debug('Execution tests list', result);
|
|
101
107
|
|
|
@@ -110,7 +116,7 @@ class Client {
|
|
|
110
116
|
*
|
|
111
117
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
112
118
|
*/
|
|
113
|
-
async createRun(params) {
|
|
119
|
+
async createRun(params = {}) {
|
|
114
120
|
if (!this.pipes || !this.pipes.length)
|
|
115
121
|
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
116
122
|
debug('Creating run...');
|
|
@@ -118,7 +124,7 @@ class Client {
|
|
|
118
124
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
119
125
|
|
|
120
126
|
this.queue = this.queue
|
|
121
|
-
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
127
|
+
.then(() => Promise.all(this.pipes.map(p => p.createRun(params))))
|
|
122
128
|
.catch(err => console.log(APP_PREFIX, err))
|
|
123
129
|
.then(() => {
|
|
124
130
|
const runId = this.pipeStore?.runId;
|
|
@@ -139,19 +145,6 @@ class Client {
|
|
|
139
145
|
* @returns {Promise<PipeResult[]>}
|
|
140
146
|
*/
|
|
141
147
|
async addTestRun(status, testData) {
|
|
142
|
-
if (!this.pipes || !this.pipes.length)
|
|
143
|
-
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
144
|
-
|
|
145
|
-
// all pipes disabled, skipping
|
|
146
|
-
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
147
|
-
|
|
148
|
-
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
149
|
-
|
|
150
|
-
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
151
|
-
debug('Skipping test from report', testData?.title);
|
|
152
|
-
return []; // do not log skipped tests
|
|
153
|
-
}
|
|
154
|
-
|
|
155
148
|
if (!testData)
|
|
156
149
|
testData = {
|
|
157
150
|
title: 'Unknown test',
|
|
@@ -166,18 +159,19 @@ class Client {
|
|
|
166
159
|
/**
|
|
167
160
|
* @type {TestData}
|
|
168
161
|
*/
|
|
162
|
+
const { rid, error = null, steps: originalSteps, title, suite_title } = testData;
|
|
163
|
+
let steps = originalSteps;
|
|
164
|
+
|
|
165
|
+
const uploadedFiles = [];
|
|
166
|
+
const stackArtifactsEnabled = transformEnvVarToBoolean(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
167
|
+
|
|
169
168
|
const {
|
|
170
|
-
rid,
|
|
171
|
-
error = null,
|
|
172
169
|
time = 0,
|
|
173
170
|
example = null,
|
|
174
171
|
files = [],
|
|
175
172
|
filesBuffers = [],
|
|
176
|
-
steps,
|
|
177
173
|
code = null,
|
|
178
|
-
title,
|
|
179
174
|
file,
|
|
180
|
-
suite_title,
|
|
181
175
|
suite_id,
|
|
182
176
|
test_id,
|
|
183
177
|
timestamp,
|
|
@@ -188,7 +182,6 @@ class Client {
|
|
|
188
182
|
} = testData;
|
|
189
183
|
let { message = '', meta = {} } = testData;
|
|
190
184
|
|
|
191
|
-
// stringify meta values and limit keys and values length to 255
|
|
192
185
|
meta = Object.entries(meta)
|
|
193
186
|
.filter(([, value]) => value !== null && value !== undefined)
|
|
194
187
|
.reduce((acc, [key, value]) => {
|
|
@@ -196,22 +189,46 @@ class Client {
|
|
|
196
189
|
return acc;
|
|
197
190
|
}, {});
|
|
198
191
|
|
|
199
|
-
// Get links from storage using the test context
|
|
200
192
|
const testContext = suite_title ? `${suite_title} ${title}` : title;
|
|
201
193
|
|
|
202
194
|
let errorFormatted = '';
|
|
203
195
|
if (error) {
|
|
204
|
-
errorFormatted +=
|
|
196
|
+
errorFormatted += formatError(error) || '';
|
|
205
197
|
message = error?.message;
|
|
206
198
|
}
|
|
207
199
|
|
|
208
|
-
|
|
209
|
-
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
200
|
+
let fullLogs = formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
210
201
|
|
|
211
|
-
|
|
212
|
-
|
|
202
|
+
if (stackArtifactsEnabled && fullLogs?.trim()?.length > 0) {
|
|
203
|
+
uploadedFiles.push(
|
|
204
|
+
this.uploader.uploadFileAsBuffer(Buffer.from(stripColors(fullLogs), 'utf8'), [
|
|
205
|
+
this.runId,
|
|
206
|
+
rid,
|
|
207
|
+
`logs_${+new Date()}.log`,
|
|
208
|
+
]),
|
|
209
|
+
);
|
|
210
|
+
fullLogs = '';
|
|
211
|
+
steps = null;
|
|
212
|
+
}
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
if (!this.pipes || !this.pipes.length)
|
|
215
|
+
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
216
|
+
|
|
217
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) {
|
|
218
|
+
if (uploadedFiles.length > 0) {
|
|
219
|
+
await Promise.all(uploadedFiles);
|
|
220
|
+
}
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (isTestShouldBeExcludedFromReport(testData)) return [];
|
|
225
|
+
|
|
226
|
+
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
227
|
+
debug('Skipping test from report', testData?.title);
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
215
232
|
|
|
216
233
|
for (let f of files) {
|
|
217
234
|
if (!f) continue; // f === null
|
|
@@ -308,7 +325,7 @@ class Client {
|
|
|
308
325
|
const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
|
|
309
326
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
310
327
|
link: file.link,
|
|
311
|
-
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
328
|
+
sizePretty: file.size == null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
|
|
312
329
|
}));
|
|
313
330
|
|
|
314
331
|
uploadedArtifacts.forEach(upload => {
|
|
@@ -330,7 +347,7 @@ class Client {
|
|
|
330
347
|
);
|
|
331
348
|
const failedUploads = this.uploader.failedUploads.map(file => ({
|
|
332
349
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
333
|
-
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
350
|
+
sizePretty: file.size == null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
|
|
334
351
|
}));
|
|
335
352
|
|
|
336
353
|
const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
@@ -380,84 +397,6 @@ class Client {
|
|
|
380
397
|
|
|
381
398
|
return this.queue;
|
|
382
399
|
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
386
|
-
* @returns {string}
|
|
387
|
-
*/
|
|
388
|
-
formatLogs({ error, steps, logs }) {
|
|
389
|
-
error = error?.trim();
|
|
390
|
-
logs = logs?.trim().split('\n').map(l => truncate(l)).join('\n');
|
|
391
|
-
|
|
392
|
-
if (Array.isArray(steps)) {
|
|
393
|
-
steps = steps
|
|
394
|
-
.map(step => formatStep(step))
|
|
395
|
-
.flat()
|
|
396
|
-
.join('\n');
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
let testLogs = '';
|
|
400
|
-
if (steps) testLogs += `${pc.bold(pc.blue('################[ Steps ]################'))}\n${steps}\n\n`;
|
|
401
|
-
if (logs) testLogs += `${pc.bold(pc.gray('################[ Logs ]################'))}\n${logs}\n\n`;
|
|
402
|
-
if (error) testLogs += `${pc.bold(pc.red('################[ Failure ]################'))}\n${error}`;
|
|
403
|
-
return testLogs;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
formatError(error, message) {
|
|
407
|
-
if (!message) message = error.message;
|
|
408
|
-
if (error.inspect) message = error.inspect() || '';
|
|
409
|
-
|
|
410
|
-
let stack = '';
|
|
411
|
-
if (error.name) stack += `${pc.red(error.name)}`;
|
|
412
|
-
if (error.operator) stack += ` (${pc.red(error.operator)})`;
|
|
413
|
-
// add new line if something was added to stack
|
|
414
|
-
if (stack) stack += ': ';
|
|
415
|
-
|
|
416
|
-
stack += `${message}\n`;
|
|
417
|
-
|
|
418
|
-
if (error.diff) {
|
|
419
|
-
// diff for vitest
|
|
420
|
-
stack += error.diff;
|
|
421
|
-
stack += '\n\n';
|
|
422
|
-
} else if (error.actual && error.expected && error.actual !== error.expected) {
|
|
423
|
-
// diffs for mocha, cypress, codeceptjs style
|
|
424
|
-
stack += `\n\n${pc.bold(pc.green('+ expected'))} ${pc.bold(pc.red('- actual'))}`;
|
|
425
|
-
stack += `\n${pc.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
|
|
426
|
-
stack += `\n${pc.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
|
|
427
|
-
stack += '\n\n';
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
431
|
-
|
|
432
|
-
try {
|
|
433
|
-
let hasFrame = false;
|
|
434
|
-
const record = createCallsiteRecord({
|
|
435
|
-
forError: error,
|
|
436
|
-
isCallsiteFrame: frame => {
|
|
437
|
-
if (customFilter && minimatch(frame.fileName, customFilter)) return false;
|
|
438
|
-
if (hasFrame) return false;
|
|
439
|
-
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
440
|
-
return hasFrame;
|
|
441
|
-
},
|
|
442
|
-
});
|
|
443
|
-
// @ts-ignore
|
|
444
|
-
if (record && !record.filename.startsWith('http')) {
|
|
445
|
-
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
446
|
-
}
|
|
447
|
-
return stack;
|
|
448
|
-
} catch (e) {
|
|
449
|
-
console.log(e);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function isNotInternalFrame(frame) {
|
|
455
|
-
return (
|
|
456
|
-
frame.getFileName() &&
|
|
457
|
-
frame.getFileName().includes(sep) &&
|
|
458
|
-
!frame.getFileName().includes('node_modules') &&
|
|
459
|
-
!frame.getFileName().includes('internal')
|
|
460
|
-
);
|
|
461
400
|
}
|
|
462
401
|
|
|
463
402
|
/**
|
|
@@ -465,7 +404,7 @@ function isNotInternalFrame(frame) {
|
|
|
465
404
|
* @param {TestData} testData
|
|
466
405
|
* @returns boolean
|
|
467
406
|
*/
|
|
468
|
-
function
|
|
407
|
+
function isTestShouldBeExcludedFromReport(testData) {
|
|
469
408
|
// const fileName = path.basename(test.location?.file || '');
|
|
470
409
|
const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
|
|
471
410
|
if (!globExcludeFilesPattern) return false;
|
|
@@ -475,12 +414,12 @@ function isTestShouldBeExculedFromReport(testData) {
|
|
|
475
414
|
return false;
|
|
476
415
|
}
|
|
477
416
|
|
|
478
|
-
const
|
|
417
|
+
const excludePatternsList = globExcludeFilesPattern.split(';');
|
|
479
418
|
|
|
480
419
|
// as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
|
|
481
420
|
if (!listOfTestFilesToExcludeFromReport) {
|
|
482
421
|
// list of files with relative paths
|
|
483
|
-
listOfTestFilesToExcludeFromReport = glob.sync(
|
|
422
|
+
listOfTestFilesToExcludeFromReport = glob.sync(excludePatternsList, { ignore: '**/node_modules/**' });
|
|
484
423
|
debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
|
|
485
424
|
}
|
|
486
425
|
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isPlaywright = Boolean(process.env.PLAYWRIGHT_TEST || process.env.PLAYWRIGHT);
|
|
@@ -3,18 +3,53 @@ import Adapter from './adapter.js';
|
|
|
3
3
|
|
|
4
4
|
class CSharpAdapter extends Adapter {
|
|
5
5
|
formatTest(t) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// Extract example from title if not already present
|
|
7
|
+
if (!t.example) {
|
|
8
|
+
const exampleMatch = t.title.match(/\((.*?)\)/);
|
|
9
|
+
if (exampleMatch) {
|
|
10
|
+
// Extract parameters as object with numeric keys for API
|
|
11
|
+
const params = exampleMatch[1]
|
|
12
|
+
.split(',')
|
|
13
|
+
.map(param => param.trim())
|
|
14
|
+
.filter(param => param !== '');
|
|
15
|
+
t.example = {};
|
|
16
|
+
params.forEach((param, index) => {
|
|
17
|
+
t.example[index] = param;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Remove parameters from title to avoid duplicates in Test Suite
|
|
23
|
+
// The example field will be used for grouping on import
|
|
24
|
+
t.title = t.title.replace(/\(.*?\)/, '').trim();
|
|
25
|
+
|
|
9
26
|
const suite = t.suite_title.split('.');
|
|
10
27
|
t.suite_title = suite.pop();
|
|
11
28
|
t.file = namespaceToFileName(t.file);
|
|
12
|
-
t.title = title.trim();
|
|
13
29
|
return t;
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
getFilePath(t) {
|
|
17
|
-
|
|
33
|
+
if (!t.file) return null;
|
|
34
|
+
|
|
35
|
+
// Normalize path separators for cross-platform compatibility
|
|
36
|
+
let filePath = t.file.replace(/\\/g, '/');
|
|
37
|
+
|
|
38
|
+
// If file already has .cs extension, use it directly
|
|
39
|
+
if (filePath.endsWith('.cs')) {
|
|
40
|
+
// Make relative path if it's absolute
|
|
41
|
+
if (path.isAbsolute(filePath)) {
|
|
42
|
+
// Try to find project-relative path
|
|
43
|
+
const cwd = process.cwd().replace(/\\/g, '/');
|
|
44
|
+
if (filePath.startsWith(cwd)) {
|
|
45
|
+
filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return filePath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Convert namespace path to file path
|
|
52
|
+
const fileName = namespaceToFileName(filePath);
|
|
18
53
|
return fileName;
|
|
19
54
|
}
|
|
20
55
|
}
|
|
@@ -22,7 +57,14 @@ class CSharpAdapter extends Adapter {
|
|
|
22
57
|
export default CSharpAdapter;
|
|
23
58
|
|
|
24
59
|
function namespaceToFileName(fileName) {
|
|
60
|
+
if (!fileName) return '';
|
|
61
|
+
|
|
62
|
+
// If already a .cs file path, clean it up
|
|
63
|
+
if (fileName.endsWith('.cs')) {
|
|
64
|
+
return fileName.replace(/\\/g, '/');
|
|
65
|
+
}
|
|
66
|
+
|
|
25
67
|
const fileParts = fileName.split('.');
|
|
26
68
|
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
27
|
-
return `${fileParts.join(
|
|
69
|
+
return `${fileParts.join('/')}.cs`;
|
|
28
70
|
}
|