mcp-perforce-server 2.0.1 → 2.1.1
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/LICENSE +20 -20
- package/MCP_CONFIG_EXAMPLES.md +222 -222
- package/README.md +130 -515
- package/dist/p4/config.d.ts +2 -0
- package/dist/p4/config.d.ts.map +1 -1
- package/dist/p4/config.js +60 -31
- package/dist/p4/config.js.map +1 -1
- package/dist/p4/parse.d.ts +56 -28
- package/dist/p4/parse.d.ts.map +1 -1
- package/dist/p4/parse.js +462 -29
- package/dist/p4/parse.js.map +1 -1
- package/dist/p4/runner.d.ts +2 -0
- package/dist/p4/runner.d.ts.map +1 -1
- package/dist/p4/runner.js +266 -35
- package/dist/p4/runner.js.map +1 -1
- package/dist/p4/security.d.ts +2 -0
- package/dist/p4/security.d.ts.map +1 -1
- package/dist/p4/security.js +61 -29
- package/dist/p4/security.js.map +1 -1
- package/dist/performance.d.ts +47 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +67 -0
- package/dist/performance.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +418 -135
- package/dist/server.js.map +1 -1
- package/dist/tools/advanced.js +1 -1
- package/dist/tools/advanced.js.map +1 -1
- package/dist/tools/basic.d.ts +58 -0
- package/dist/tools/basic.d.ts.map +1 -1
- package/dist/tools/basic.js +492 -60
- package/dist/tools/basic.js.map +1 -1
- package/dist/tools/changelist.d.ts +1 -1
- package/dist/tools/changelist.d.ts.map +1 -1
- package/dist/tools/changelist.js +36 -6
- package/dist/tools/changelist.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +24 -3
- package/dist/tools/utils.js.map +1 -1
- package/package.json +114 -114
package/dist/p4/parse.js
CHANGED
|
@@ -10,6 +10,8 @@ exports.parseChangesOutput = parseChangesOutput;
|
|
|
10
10
|
exports.parseFilelogOutput = parseFilelogOutput;
|
|
11
11
|
exports.parseClientsOutput = parseClientsOutput;
|
|
12
12
|
exports.parseDiffOutput = parseDiffOutput;
|
|
13
|
+
exports.parseDiff2Output = parseDiff2Output;
|
|
14
|
+
exports.parseDescribeOutput = parseDescribeOutput;
|
|
13
15
|
exports.parseSyncOutput = parseSyncOutput;
|
|
14
16
|
exports.parseResolveOutput = parseResolveOutput;
|
|
15
17
|
exports.parseShelveOutput = parseShelveOutput;
|
|
@@ -31,12 +33,21 @@ exports.parseLabelOutput = parseLabelOutput;
|
|
|
31
33
|
exports.parseSizesOutput = parseSizesOutput;
|
|
32
34
|
exports.parseHaveOutput = parseHaveOutput;
|
|
33
35
|
exports.parseWhereOutput = parseWhereOutput;
|
|
36
|
+
exports.parseFstatOutput = parseFstatOutput;
|
|
37
|
+
exports.parseStreamsOutput = parseStreamsOutput;
|
|
38
|
+
exports.parseStreamOutput = parseStreamOutput;
|
|
39
|
+
exports.parsePrintOutput = parsePrintOutput;
|
|
40
|
+
exports.parseIntegrateOutput = parseIntegrateOutput;
|
|
34
41
|
/**
|
|
35
42
|
* Parse p4 -ztag output into structured data
|
|
36
43
|
*/
|
|
37
44
|
function parseZtagOutput(output) {
|
|
38
45
|
const results = [];
|
|
39
|
-
|
|
46
|
+
// Handle non-string inputs safely
|
|
47
|
+
if (!output || typeof output !== 'string') {
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
const lines = getNormalizedLines(output);
|
|
40
51
|
let currentRecord = {};
|
|
41
52
|
for (const line of lines) {
|
|
42
53
|
const trimmedLine = line.trim();
|
|
@@ -66,7 +77,11 @@ function parseZtagOutput(output) {
|
|
|
66
77
|
*/
|
|
67
78
|
function parseInfoOutput(output) {
|
|
68
79
|
const result = {};
|
|
69
|
-
|
|
80
|
+
// Handle non-string inputs safely
|
|
81
|
+
if (!output || typeof output !== 'string') {
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
const lines = getNormalizedLines(output);
|
|
70
85
|
for (const line of lines) {
|
|
71
86
|
const trimmedLine = line.trim();
|
|
72
87
|
if (!trimmedLine)
|
|
@@ -87,7 +102,11 @@ function parseInfoOutput(output) {
|
|
|
87
102
|
*/
|
|
88
103
|
function parseOpenedOutput(output) {
|
|
89
104
|
const results = [];
|
|
90
|
-
|
|
105
|
+
// Handle non-string inputs safely
|
|
106
|
+
if (!output || typeof output !== 'string') {
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
91
110
|
for (const line of lines) {
|
|
92
111
|
// Format: "//depot/path#revision - action by user@client (change) type"
|
|
93
112
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+(\w+)\s+by\s+(.+?)@(.+?)\s+\((.+?)\)(?:\s+(.+))?/);
|
|
@@ -118,10 +137,16 @@ function parseOpenedOutput(output) {
|
|
|
118
137
|
*/
|
|
119
138
|
function parseChangesOutput(output) {
|
|
120
139
|
const results = [];
|
|
121
|
-
|
|
140
|
+
// Handle non-string inputs safely
|
|
141
|
+
if (!output || typeof output !== 'string') {
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
const lines = getNormalizedLines(output, { trim: true, removeEmpty: true });
|
|
122
145
|
for (const line of lines) {
|
|
146
|
+
// Remove "info: " prefix if present (from -s flag output)
|
|
147
|
+
const cleanLine = line.replace(/^info:\s*/, '');
|
|
123
148
|
// Format: "Change 12345 on 2023/01/01 by user@client 'Description...'"
|
|
124
|
-
const match =
|
|
149
|
+
const match = cleanLine.match(/^Change\s+(\d+)\s+on\s+(\S+)\s+by\s+(.+?)@(.+?)\s+'(.*)'/);
|
|
125
150
|
if (match) {
|
|
126
151
|
const [, change, date, user, client, description] = match;
|
|
127
152
|
results.push({
|
|
@@ -140,7 +165,11 @@ function parseChangesOutput(output) {
|
|
|
140
165
|
*/
|
|
141
166
|
function parseFilelogOutput(output) {
|
|
142
167
|
const results = [];
|
|
143
|
-
|
|
168
|
+
// Handle non-string inputs safely
|
|
169
|
+
if (!output || typeof output !== 'string') {
|
|
170
|
+
return results;
|
|
171
|
+
}
|
|
172
|
+
const lines = getNormalizedLines(output);
|
|
144
173
|
let currentFile = null;
|
|
145
174
|
let currentRevision = null;
|
|
146
175
|
for (const line of lines) {
|
|
@@ -187,7 +216,11 @@ function parseFilelogOutput(output) {
|
|
|
187
216
|
*/
|
|
188
217
|
function parseClientsOutput(output) {
|
|
189
218
|
const results = [];
|
|
190
|
-
|
|
219
|
+
// Handle non-string inputs safely
|
|
220
|
+
if (!output || typeof output !== 'string') {
|
|
221
|
+
return results;
|
|
222
|
+
}
|
|
223
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
191
224
|
for (const line of lines) {
|
|
192
225
|
// Format: "Client clientname 2023/01/01 root /path/to/root 'Description...'"
|
|
193
226
|
const match = line.match(/^Client\s+(\S+)\s+(\S+)\s+root\s+(.+?)\s+'(.*)'/);
|
|
@@ -207,7 +240,16 @@ function parseClientsOutput(output) {
|
|
|
207
240
|
* Parse p4 diff output into structured diff information
|
|
208
241
|
*/
|
|
209
242
|
function parseDiffOutput(output) {
|
|
210
|
-
|
|
243
|
+
// Handle non-string inputs safely
|
|
244
|
+
if (!output || typeof output !== 'string') {
|
|
245
|
+
return {
|
|
246
|
+
files: [],
|
|
247
|
+
totalFiles: 0,
|
|
248
|
+
totalAddedLines: 0,
|
|
249
|
+
totalRemovedLines: 0
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const lines = getNormalizedLines(output);
|
|
211
253
|
const files = [];
|
|
212
254
|
let currentFile = null;
|
|
213
255
|
let diffLines = [];
|
|
@@ -259,12 +301,173 @@ function parseDiffOutput(output) {
|
|
|
259
301
|
totalRemovedLines: files.reduce((sum, file) => sum + (file.removedLines || 0), 0),
|
|
260
302
|
};
|
|
261
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Parse p4 diff2 output for depot-to-depot comparisons
|
|
306
|
+
*/
|
|
307
|
+
function parseDiff2Output(output, summaryOnly = true) {
|
|
308
|
+
const emptyResult = {
|
|
309
|
+
differences: [],
|
|
310
|
+
totalDifferences: 0,
|
|
311
|
+
summaryOnly,
|
|
312
|
+
};
|
|
313
|
+
if (!output || typeof output !== 'string') {
|
|
314
|
+
return emptyResult;
|
|
315
|
+
}
|
|
316
|
+
const lines = getNormalizedLines(output);
|
|
317
|
+
const differences = [];
|
|
318
|
+
let currentDiff = null;
|
|
319
|
+
let currentDiffLines = [];
|
|
320
|
+
const finalizeCurrentDiff = () => {
|
|
321
|
+
if (!currentDiff)
|
|
322
|
+
return;
|
|
323
|
+
if (!summaryOnly) {
|
|
324
|
+
const diffText = currentDiffLines.join('\n').trim();
|
|
325
|
+
if (diffText) {
|
|
326
|
+
currentDiff.diff = diffText;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
differences.push(currentDiff);
|
|
330
|
+
currentDiff = null;
|
|
331
|
+
currentDiffLines = [];
|
|
332
|
+
};
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
const trimmed = line.trim();
|
|
335
|
+
if (!trimmed) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const headerMatch = trimmed.match(/^====\s+(.+?)\s+-\s+(.+?)\s+====(?:\s+(.*))?$/);
|
|
339
|
+
if (headerMatch) {
|
|
340
|
+
finalizeCurrentDiff();
|
|
341
|
+
const [, sourceRaw, targetRaw, differenceTypeRaw] = headerMatch;
|
|
342
|
+
const sourceInfo = parseDiff2Side(sourceRaw);
|
|
343
|
+
const targetInfo = parseDiff2Side(targetRaw);
|
|
344
|
+
currentDiff = {
|
|
345
|
+
sourceFile: sourceInfo.depotFile,
|
|
346
|
+
sourceRevision: sourceInfo.revision,
|
|
347
|
+
sourceType: sourceInfo.type,
|
|
348
|
+
targetFile: targetInfo.depotFile,
|
|
349
|
+
targetRevision: targetInfo.revision,
|
|
350
|
+
targetType: targetInfo.type,
|
|
351
|
+
differenceType: differenceTypeRaw ? differenceTypeRaw.trim() : 'content',
|
|
352
|
+
};
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (summaryOnly) {
|
|
356
|
+
const pairMatch = trimmed.match(/^(.+?#\S+(?:\s+\(.+?\))?)\s+-\s+(.+?#\S+(?:\s+\(.+?\))?)(?:\s+(.*))?$/);
|
|
357
|
+
if (pairMatch) {
|
|
358
|
+
finalizeCurrentDiff();
|
|
359
|
+
const [, sourceRaw, targetRaw, differenceTypeRaw] = pairMatch;
|
|
360
|
+
const sourceInfo = parseDiff2Side(sourceRaw);
|
|
361
|
+
const targetInfo = parseDiff2Side(targetRaw);
|
|
362
|
+
differences.push({
|
|
363
|
+
sourceFile: sourceInfo.depotFile,
|
|
364
|
+
sourceRevision: sourceInfo.revision,
|
|
365
|
+
sourceType: sourceInfo.type,
|
|
366
|
+
targetFile: targetInfo.depotFile,
|
|
367
|
+
targetRevision: targetInfo.revision,
|
|
368
|
+
targetType: targetInfo.type,
|
|
369
|
+
differenceType: differenceTypeRaw ? differenceTypeRaw.trim() : 'different',
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (!summaryOnly && currentDiff) {
|
|
375
|
+
currentDiffLines.push(line);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
finalizeCurrentDiff();
|
|
379
|
+
return {
|
|
380
|
+
differences: differences,
|
|
381
|
+
totalDifferences: differences.length,
|
|
382
|
+
summaryOnly,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Parse p4 describe -s output into structured changelist data
|
|
387
|
+
*/
|
|
388
|
+
function parseDescribeOutput(output) {
|
|
389
|
+
const result = {
|
|
390
|
+
files: [],
|
|
391
|
+
description: '',
|
|
392
|
+
rawText: '',
|
|
393
|
+
};
|
|
394
|
+
if (!output || typeof output !== 'string') {
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
const lines = getNormalizedLines(output);
|
|
398
|
+
result.rawText = lines
|
|
399
|
+
.filter((line) => line.trim())
|
|
400
|
+
.join('\n');
|
|
401
|
+
let inDescription = false;
|
|
402
|
+
let inFiles = false;
|
|
403
|
+
const descriptionLines = [];
|
|
404
|
+
const files = [];
|
|
405
|
+
for (const line of lines) {
|
|
406
|
+
const trimmed = line.trim();
|
|
407
|
+
if (!trimmed) {
|
|
408
|
+
if (inDescription && !inFiles && descriptionLines.length > 0 && descriptionLines[descriptionLines.length - 1] !== '') {
|
|
409
|
+
descriptionLines.push('');
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const headerMatch = trimmed.match(/^Change\s+(\d+)\s+by\s+(.+?)@(\S+)\s+on\s+(.+?)(?:\s+\*(\w+)\*)?$/);
|
|
414
|
+
if (headerMatch) {
|
|
415
|
+
const [, change, user, client, date, status] = headerMatch;
|
|
416
|
+
result.change = parseInt(change, 10);
|
|
417
|
+
result.user = user;
|
|
418
|
+
result.client = client;
|
|
419
|
+
result.date = date.trim();
|
|
420
|
+
result.status = status ? status.toLowerCase() : 'submitted';
|
|
421
|
+
inDescription = true;
|
|
422
|
+
inFiles = false;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (/^Affected files\s+\.\.\.$/i.test(trimmed)) {
|
|
426
|
+
inDescription = false;
|
|
427
|
+
inFiles = true;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (/^Differences\s+\.\.\.$/i.test(trimmed)) {
|
|
431
|
+
inDescription = false;
|
|
432
|
+
inFiles = false;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (inFiles && trimmed.startsWith('... ')) {
|
|
436
|
+
const fileMatch = trimmed.match(/^\.{3}\s+(.+?)#([^\s]+)\s+([^\s]+)(?:\s+\((.+?)\))?$/);
|
|
437
|
+
if (fileMatch) {
|
|
438
|
+
const [, depotFile, revisionRaw, action, type] = fileMatch;
|
|
439
|
+
const numericRevision = parseInt(revisionRaw, 10);
|
|
440
|
+
files.push({
|
|
441
|
+
depotFile,
|
|
442
|
+
revision: Number.isNaN(numericRevision) ? revisionRaw : numericRevision,
|
|
443
|
+
action,
|
|
444
|
+
type: type || undefined,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (inDescription) {
|
|
450
|
+
descriptionLines.push(trimmed);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
while (descriptionLines.length > 0 && descriptionLines[descriptionLines.length - 1] === '') {
|
|
454
|
+
descriptionLines.pop();
|
|
455
|
+
}
|
|
456
|
+
result.description = descriptionLines.join('\n');
|
|
457
|
+
result.files = files;
|
|
458
|
+
result.fileCount = files.length;
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
262
461
|
/**
|
|
263
462
|
* Parse p4 sync output into sync records
|
|
264
463
|
*/
|
|
265
464
|
function parseSyncOutput(output) {
|
|
266
465
|
const results = [];
|
|
267
|
-
|
|
466
|
+
// Handle non-string inputs safely
|
|
467
|
+
if (!output || typeof output !== 'string') {
|
|
468
|
+
return results;
|
|
469
|
+
}
|
|
470
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
268
471
|
for (const line of lines) {
|
|
269
472
|
// Format: "//depot/path#revision - updating /local/path"
|
|
270
473
|
// Format: "//depot/path#revision - added as /local/path"
|
|
@@ -287,7 +490,11 @@ function parseSyncOutput(output) {
|
|
|
287
490
|
*/
|
|
288
491
|
function parseResolveOutput(output) {
|
|
289
492
|
const results = [];
|
|
290
|
-
|
|
493
|
+
// Handle non-string inputs safely
|
|
494
|
+
if (!output || typeof output !== 'string') {
|
|
495
|
+
return results;
|
|
496
|
+
}
|
|
497
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
291
498
|
for (const line of lines) {
|
|
292
499
|
// Format: "C /path/to/file - merging //depot/path#123"
|
|
293
500
|
const match = line.match(/^([A-Z])\s+(.+?)\s+-\s+(.+)/);
|
|
@@ -308,7 +515,11 @@ function parseResolveOutput(output) {
|
|
|
308
515
|
*/
|
|
309
516
|
function parseShelveOutput(output) {
|
|
310
517
|
const results = [];
|
|
311
|
-
|
|
518
|
+
// Handle non-string inputs safely
|
|
519
|
+
if (!output || typeof output !== 'string') {
|
|
520
|
+
return results;
|
|
521
|
+
}
|
|
522
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
312
523
|
for (const line of lines) {
|
|
313
524
|
// Format: "change 12345 shelved"
|
|
314
525
|
const match = line.match(/^change\s+(\d+)\s+shelved/);
|
|
@@ -327,7 +538,11 @@ function parseShelveOutput(output) {
|
|
|
327
538
|
*/
|
|
328
539
|
function parseUnshelveOutput(output) {
|
|
329
540
|
const results = [];
|
|
330
|
-
|
|
541
|
+
// Handle non-string inputs safely
|
|
542
|
+
if (!output || typeof output !== 'string') {
|
|
543
|
+
return results;
|
|
544
|
+
}
|
|
545
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
331
546
|
for (const line of lines) {
|
|
332
547
|
// Format: "//depot/path#123 - unshelved"
|
|
333
548
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+unshelved/);
|
|
@@ -347,7 +562,11 @@ function parseUnshelveOutput(output) {
|
|
|
347
562
|
*/
|
|
348
563
|
function parseBlameOutput(output) {
|
|
349
564
|
const results = [];
|
|
350
|
-
|
|
565
|
+
// Handle non-string inputs safely
|
|
566
|
+
if (!output || typeof output !== 'string') {
|
|
567
|
+
return results;
|
|
568
|
+
}
|
|
569
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
351
570
|
for (const line of lines) {
|
|
352
571
|
// Format: "12345: user 2023/01/01 12:34:56: content"
|
|
353
572
|
const match = line.match(/^(\d+):\s+(.+?)\s+(\d{4}\/\d{2}\/\d{2})\s+(\d{2}:\d{2}:\d{2}):\s*(.*)/);
|
|
@@ -369,7 +588,11 @@ function parseBlameOutput(output) {
|
|
|
369
588
|
*/
|
|
370
589
|
function parseCopyOutput(output) {
|
|
371
590
|
const results = [];
|
|
372
|
-
|
|
591
|
+
// Handle non-string inputs safely
|
|
592
|
+
if (!output || typeof output !== 'string') {
|
|
593
|
+
return results;
|
|
594
|
+
}
|
|
595
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
373
596
|
for (const line of lines) {
|
|
374
597
|
// Format: "//depot/src#123 - copied to //depot/dst#124"
|
|
375
598
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+copied\s+to\s+(.+?)#(\d+)/);
|
|
@@ -391,7 +614,11 @@ function parseCopyOutput(output) {
|
|
|
391
614
|
*/
|
|
392
615
|
function parseMoveOutput(output) {
|
|
393
616
|
const results = [];
|
|
394
|
-
|
|
617
|
+
// Handle non-string inputs safely
|
|
618
|
+
if (!output || typeof output !== 'string') {
|
|
619
|
+
return results;
|
|
620
|
+
}
|
|
621
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
395
622
|
for (const line of lines) {
|
|
396
623
|
// Format: "//depot/old#123 - moved to //depot/new#124"
|
|
397
624
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+moved\s+to\s+(.+?)#(\d+)/);
|
|
@@ -413,7 +640,11 @@ function parseMoveOutput(output) {
|
|
|
413
640
|
*/
|
|
414
641
|
function parseGrepOutput(output) {
|
|
415
642
|
const results = [];
|
|
416
|
-
|
|
643
|
+
// Handle non-string inputs safely
|
|
644
|
+
if (!output || typeof output !== 'string') {
|
|
645
|
+
return results;
|
|
646
|
+
}
|
|
647
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
417
648
|
for (const line of lines) {
|
|
418
649
|
// Format: "//depot/path:123:matched text here"
|
|
419
650
|
const match = line.match(/^(.+?):(\d+):(.*)/);
|
|
@@ -433,7 +664,11 @@ function parseGrepOutput(output) {
|
|
|
433
664
|
*/
|
|
434
665
|
function parseFilesOutput(output) {
|
|
435
666
|
const results = [];
|
|
436
|
-
|
|
667
|
+
// Handle non-string inputs safely
|
|
668
|
+
if (!output || typeof output !== 'string') {
|
|
669
|
+
return results;
|
|
670
|
+
}
|
|
671
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
437
672
|
for (const line of lines) {
|
|
438
673
|
// Format: "//depot/path#123 - action change 456 (type text)"
|
|
439
674
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+(\w+)\s+change\s+(\d+)\s+\((.+?)\)/);
|
|
@@ -455,7 +690,11 @@ function parseFilesOutput(output) {
|
|
|
455
690
|
*/
|
|
456
691
|
function parseDirsOutput(output) {
|
|
457
692
|
const results = [];
|
|
458
|
-
|
|
693
|
+
// Handle non-string inputs safely
|
|
694
|
+
if (!output || typeof output !== 'string') {
|
|
695
|
+
return results;
|
|
696
|
+
}
|
|
697
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
459
698
|
for (const line of lines) {
|
|
460
699
|
// Directories are listed one per line
|
|
461
700
|
if (line.startsWith('//')) {
|
|
@@ -471,7 +710,11 @@ function parseDirsOutput(output) {
|
|
|
471
710
|
*/
|
|
472
711
|
function parseUsersOutput(output) {
|
|
473
712
|
const results = [];
|
|
474
|
-
|
|
713
|
+
// Handle non-string inputs safely
|
|
714
|
+
if (!output || typeof output !== 'string') {
|
|
715
|
+
return results;
|
|
716
|
+
}
|
|
717
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
475
718
|
for (const line of lines) {
|
|
476
719
|
// Format: "user <user@domain> (realname) accessed 2023/01/01 12:34:56"
|
|
477
720
|
const match = line.match(/^(\w+)\s+<([^>]+)>\s+\(([^)]+)\)\s+accessed\s+(\d{4}\/\d{2}\/\d{2})\s+(\d{2}:\d{2}:\d{2})/);
|
|
@@ -492,7 +735,11 @@ function parseUsersOutput(output) {
|
|
|
492
735
|
*/
|
|
493
736
|
function parseUserOutput(output) {
|
|
494
737
|
const result = {};
|
|
495
|
-
|
|
738
|
+
// Handle non-string inputs safely
|
|
739
|
+
if (!output || typeof output !== 'string') {
|
|
740
|
+
return result;
|
|
741
|
+
}
|
|
742
|
+
const lines = getNormalizedLines(output);
|
|
496
743
|
for (const line of lines) {
|
|
497
744
|
const colonIndex = line.indexOf(': ');
|
|
498
745
|
if (colonIndex > 0) {
|
|
@@ -508,7 +755,11 @@ function parseUserOutput(output) {
|
|
|
508
755
|
*/
|
|
509
756
|
function parseClientOutput(output) {
|
|
510
757
|
const result = {};
|
|
511
|
-
|
|
758
|
+
// Handle non-string inputs safely
|
|
759
|
+
if (!output || typeof output !== 'string') {
|
|
760
|
+
return result;
|
|
761
|
+
}
|
|
762
|
+
const lines = getNormalizedLines(output);
|
|
512
763
|
for (const line of lines) {
|
|
513
764
|
const colonIndex = line.indexOf(': ');
|
|
514
765
|
if (colonIndex > 0) {
|
|
@@ -524,7 +775,11 @@ function parseClientOutput(output) {
|
|
|
524
775
|
*/
|
|
525
776
|
function parseJobsOutput(output) {
|
|
526
777
|
const results = [];
|
|
527
|
-
|
|
778
|
+
// Handle non-string inputs safely
|
|
779
|
+
if (!output || typeof output !== 'string') {
|
|
780
|
+
return results;
|
|
781
|
+
}
|
|
782
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
528
783
|
for (const line of lines) {
|
|
529
784
|
// Format: "job000001 on 2023/01/01 by user *open* 'Job description'"
|
|
530
785
|
const match = line.match(/^(\w+)\s+on\s+(\d{4}\/\d{2}\/\d{2})\s+by\s+(\w+)\s+(\w+)\s+'(.*)'/);
|
|
@@ -546,7 +801,11 @@ function parseJobsOutput(output) {
|
|
|
546
801
|
*/
|
|
547
802
|
function parseJobOutput(output) {
|
|
548
803
|
const result = {};
|
|
549
|
-
|
|
804
|
+
// Handle non-string inputs safely
|
|
805
|
+
if (!output || typeof output !== 'string') {
|
|
806
|
+
return result;
|
|
807
|
+
}
|
|
808
|
+
const lines = getNormalizedLines(output);
|
|
550
809
|
for (const line of lines) {
|
|
551
810
|
const colonIndex = line.indexOf(': ');
|
|
552
811
|
if (colonIndex > 0) {
|
|
@@ -562,7 +821,11 @@ function parseJobOutput(output) {
|
|
|
562
821
|
*/
|
|
563
822
|
function parseFixesOutput(output) {
|
|
564
823
|
const results = [];
|
|
565
|
-
|
|
824
|
+
// Handle non-string inputs safely
|
|
825
|
+
if (!output || typeof output !== 'string') {
|
|
826
|
+
return results;
|
|
827
|
+
}
|
|
828
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
566
829
|
for (const line of lines) {
|
|
567
830
|
// Format: "job000001 fixed by change 12345 on 2023/01/01 by user@client"
|
|
568
831
|
const match = line.match(/^(\w+)\s+fixed\s+by\s+change\s+(\d+)\s+on\s+(\d{4}\/\d{2}\/\d{2})\s+by\s+(.+)$/);
|
|
@@ -583,7 +846,11 @@ function parseFixesOutput(output) {
|
|
|
583
846
|
*/
|
|
584
847
|
function parseLabelsOutput(output) {
|
|
585
848
|
const results = [];
|
|
586
|
-
|
|
849
|
+
// Handle non-string inputs safely
|
|
850
|
+
if (!output || typeof output !== 'string') {
|
|
851
|
+
return results;
|
|
852
|
+
}
|
|
853
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
587
854
|
for (const line of lines) {
|
|
588
855
|
// Format: "Label labelname 2023/01/01 'Label description'"
|
|
589
856
|
const match = line.match(/^Label\s+(\w+)\s+(\d{4}\/\d{2}\/\d{2})\s+'(.*)'/);
|
|
@@ -603,7 +870,11 @@ function parseLabelsOutput(output) {
|
|
|
603
870
|
*/
|
|
604
871
|
function parseLabelOutput(output) {
|
|
605
872
|
const result = {};
|
|
606
|
-
|
|
873
|
+
// Handle non-string inputs safely
|
|
874
|
+
if (!output || typeof output !== 'string') {
|
|
875
|
+
return result;
|
|
876
|
+
}
|
|
877
|
+
const lines = getNormalizedLines(output);
|
|
607
878
|
for (const line of lines) {
|
|
608
879
|
const colonIndex = line.indexOf(': ');
|
|
609
880
|
if (colonIndex > 0) {
|
|
@@ -618,8 +889,16 @@ function parseLabelOutput(output) {
|
|
|
618
889
|
* Parse p4 sizes output into size statistics
|
|
619
890
|
*/
|
|
620
891
|
function parseSizesOutput(output) {
|
|
892
|
+
// Handle non-string inputs safely
|
|
893
|
+
if (!output || typeof output !== 'string') {
|
|
894
|
+
return {
|
|
895
|
+
totalSize: 0,
|
|
896
|
+
fileCount: 0,
|
|
897
|
+
files: []
|
|
898
|
+
};
|
|
899
|
+
}
|
|
621
900
|
const result = {};
|
|
622
|
-
const lines = output
|
|
901
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
623
902
|
for (const line of lines) {
|
|
624
903
|
if (line.includes('files,')) {
|
|
625
904
|
// Format: "12345 files, 67890123 bytes"
|
|
@@ -637,7 +916,11 @@ function parseSizesOutput(output) {
|
|
|
637
916
|
*/
|
|
638
917
|
function parseHaveOutput(output) {
|
|
639
918
|
const results = [];
|
|
640
|
-
|
|
919
|
+
// Handle non-string inputs safely
|
|
920
|
+
if (!output || typeof output !== 'string') {
|
|
921
|
+
return results;
|
|
922
|
+
}
|
|
923
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
641
924
|
for (const line of lines) {
|
|
642
925
|
// Format: "//depot/path#123 - /local/path"
|
|
643
926
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+(.+)/);
|
|
@@ -657,7 +940,11 @@ function parseHaveOutput(output) {
|
|
|
657
940
|
*/
|
|
658
941
|
function parseWhereOutput(output) {
|
|
659
942
|
const results = [];
|
|
660
|
-
|
|
943
|
+
// Handle non-string inputs safely
|
|
944
|
+
if (!output || typeof output !== 'string') {
|
|
945
|
+
return results;
|
|
946
|
+
}
|
|
947
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
661
948
|
for (const line of lines) {
|
|
662
949
|
// Format: "//depot/path /local/path //depot/path"
|
|
663
950
|
const parts = line.split(/\s+/);
|
|
@@ -671,6 +958,105 @@ function parseWhereOutput(output) {
|
|
|
671
958
|
}
|
|
672
959
|
return results;
|
|
673
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* Parse p4 fstat output into records
|
|
963
|
+
*/
|
|
964
|
+
function parseFstatOutput(output) {
|
|
965
|
+
const records = parseZtagOutput(output);
|
|
966
|
+
return records.map((record) => ({ ...record }));
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Parse p4 streams output into stream records
|
|
970
|
+
*/
|
|
971
|
+
function parseStreamsOutput(output) {
|
|
972
|
+
const results = [];
|
|
973
|
+
if (!output || typeof output !== 'string') {
|
|
974
|
+
return results;
|
|
975
|
+
}
|
|
976
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
977
|
+
for (const line of lines) {
|
|
978
|
+
// Format: "Stream //Depot/main 2025/01/01 owner 'description'"
|
|
979
|
+
const match = line.match(/^Stream\s+(\S+)\s+(\S+)\s+(\S+)\s+'(.*)'$/);
|
|
980
|
+
if (match) {
|
|
981
|
+
const [, stream, date, owner, description] = match;
|
|
982
|
+
results.push({
|
|
983
|
+
stream,
|
|
984
|
+
date,
|
|
985
|
+
owner,
|
|
986
|
+
description,
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
else if (line.startsWith('//')) {
|
|
990
|
+
results.push({ stream: line.trim() });
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return results;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Parse p4 stream output into a stream spec key/value object
|
|
997
|
+
*/
|
|
998
|
+
function parseStreamOutput(output) {
|
|
999
|
+
const result = {};
|
|
1000
|
+
if (!output || typeof output !== 'string') {
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
const lines = getNormalizedLines(output);
|
|
1004
|
+
for (const line of lines) {
|
|
1005
|
+
const colonIndex = line.indexOf(':');
|
|
1006
|
+
if (colonIndex > 0) {
|
|
1007
|
+
const key = line.substring(0, colonIndex).trim();
|
|
1008
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
1009
|
+
if (key) {
|
|
1010
|
+
result[key] = value;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return result;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Parse p4 print output content
|
|
1018
|
+
*/
|
|
1019
|
+
function parsePrintOutput(output) {
|
|
1020
|
+
if (!output || typeof output !== 'string') {
|
|
1021
|
+
return { content: '' };
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
content: getNormalizedLines(output).join('\n').trim(),
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Parse p4 integrate/merge output into operation records
|
|
1029
|
+
*/
|
|
1030
|
+
function parseIntegrateOutput(output) {
|
|
1031
|
+
const results = [];
|
|
1032
|
+
if (!output || typeof output !== 'string') {
|
|
1033
|
+
return results;
|
|
1034
|
+
}
|
|
1035
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
1036
|
+
for (const line of lines) {
|
|
1037
|
+
// Common formats:
|
|
1038
|
+
// //depot/target#5 - integrate from //depot/source#7
|
|
1039
|
+
// //depot/target#none - branch from //depot/source#1
|
|
1040
|
+
const match = line.match(/^(.+?)#([^\s]+)\s+-\s+(.+?)(?:\s+from\s+(.+?)#([^\s]+))?$/);
|
|
1041
|
+
if (match) {
|
|
1042
|
+
const [, targetFile, targetRevisionRaw, action, sourceFile, sourceRevisionRaw] = match;
|
|
1043
|
+
const targetRevisionNum = parseInt(targetRevisionRaw, 10);
|
|
1044
|
+
const sourceRevisionNum = sourceRevisionRaw ? parseInt(sourceRevisionRaw, 10) : NaN;
|
|
1045
|
+
results.push({
|
|
1046
|
+
targetFile,
|
|
1047
|
+
targetRevision: Number.isNaN(targetRevisionNum) ? targetRevisionRaw : targetRevisionNum,
|
|
1048
|
+
action: action.trim(),
|
|
1049
|
+
sourceFile: sourceFile || undefined,
|
|
1050
|
+
sourceRevision: sourceRevisionRaw
|
|
1051
|
+
? Number.isNaN(sourceRevisionNum)
|
|
1052
|
+
? sourceRevisionRaw
|
|
1053
|
+
: sourceRevisionNum
|
|
1054
|
+
: undefined,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return results;
|
|
1059
|
+
}
|
|
674
1060
|
/**
|
|
675
1061
|
* Helper function to convert resolve status to action
|
|
676
1062
|
*/
|
|
@@ -683,6 +1069,53 @@ function getResolveAction(status) {
|
|
|
683
1069
|
default: return 'unknown';
|
|
684
1070
|
}
|
|
685
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Strip p4 -s script mode prefixes (info1:, error:, exit:, etc.) from output lines
|
|
1074
|
+
*/
|
|
1075
|
+
function stripScriptPrefix(line) {
|
|
1076
|
+
const match = line.match(/^(info\d*|text|warning|error|exit):\s?(.*)$/i);
|
|
1077
|
+
if (!match) {
|
|
1078
|
+
return line;
|
|
1079
|
+
}
|
|
1080
|
+
const [, prefix, value] = match;
|
|
1081
|
+
if (prefix.toLowerCase() === 'exit') {
|
|
1082
|
+
return '';
|
|
1083
|
+
}
|
|
1084
|
+
return value;
|
|
1085
|
+
}
|
|
1086
|
+
function getNormalizedLines(output, options = {}) {
|
|
1087
|
+
if (!output || typeof output !== 'string') {
|
|
1088
|
+
return [];
|
|
1089
|
+
}
|
|
1090
|
+
const { trim = false, removeEmpty = false } = options;
|
|
1091
|
+
const lines = output.split('\n');
|
|
1092
|
+
const normalized = [];
|
|
1093
|
+
for (const rawLine of lines) {
|
|
1094
|
+
const stripped = stripScriptPrefix(rawLine.replace(/\r$/, ''));
|
|
1095
|
+
const value = trim ? stripped.trim() : stripped;
|
|
1096
|
+
if (removeEmpty && value.trim().length === 0) {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
normalized.push(value);
|
|
1100
|
+
}
|
|
1101
|
+
return normalized;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Parse a single side of a diff2 header line
|
|
1105
|
+
*/
|
|
1106
|
+
function parseDiff2Side(side) {
|
|
1107
|
+
const match = side.trim().match(/^(.+?)#([^\s]+)(?:\s+\((.+?)\))?$/);
|
|
1108
|
+
if (!match) {
|
|
1109
|
+
return { depotFile: side.trim(), revision: 'unknown' };
|
|
1110
|
+
}
|
|
1111
|
+
const [, depotFile, revisionRaw, type] = match;
|
|
1112
|
+
const numericRevision = parseInt(revisionRaw, 10);
|
|
1113
|
+
return {
|
|
1114
|
+
depotFile,
|
|
1115
|
+
revision: Number.isNaN(numericRevision) ? revisionRaw : numericRevision,
|
|
1116
|
+
type: type || undefined,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
686
1119
|
/**
|
|
687
1120
|
* Parse individual values with type conversion
|
|
688
1121
|
*/
|