@chrrxs/robloxstudio-mcp 2.23.1 → 3.0.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/dist/index.js +1409 -4434
- package/package.json +6 -4
- package/studio-plugin/MCPInspectorPlugin.rbxmx +185 -1953
- package/studio-plugin/MCPPlugin.rbxmx +185 -1953
- package/studio-plugin/src/modules/Communication.ts +16 -47
- package/studio-plugin/src/modules/Utils.ts +60 -2
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +3 -261
- package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +1 -130
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +9 -167
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +64 -63
- package/studio-plugin/src/modules/handlers/BuildHandlers.ts +0 -481
- package/studio-plugin/src/modules/handlers/InstanceHandlers.ts +0 -380
|
@@ -123,47 +123,6 @@ function getPlaceInfo(_requestData: Record<string, unknown>) {
|
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
function getServices(requestData: Record<string, unknown>) {
|
|
127
|
-
const serviceName = requestData.serviceName as string | undefined;
|
|
128
|
-
|
|
129
|
-
if (serviceName) {
|
|
130
|
-
const [ok, service] = pcall(() => game.GetService(serviceName as keyof Services));
|
|
131
|
-
if (ok && service) {
|
|
132
|
-
return {
|
|
133
|
-
service: {
|
|
134
|
-
name: service.Name,
|
|
135
|
-
className: service.ClassName,
|
|
136
|
-
path: getInstancePath(service as Instance),
|
|
137
|
-
childCount: (service as Instance).GetChildren().size(),
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
} else {
|
|
141
|
-
return { error: `Service not found: ${serviceName}` };
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
const services: { name: string; className: string; path: string; childCount: number }[] = [];
|
|
145
|
-
const commonServices = [
|
|
146
|
-
"Workspace", "Players", "StarterGui", "StarterPack", "StarterPlayer",
|
|
147
|
-
"ReplicatedStorage", "ServerStorage", "ServerScriptService",
|
|
148
|
-
"HttpService", "TeleportService", "DataStoreService",
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
for (const svcName of commonServices) {
|
|
152
|
-
const [ok, service] = pcall(() => game.GetService(svcName as keyof Services));
|
|
153
|
-
if (ok && service) {
|
|
154
|
-
services.push({
|
|
155
|
-
name: service.Name,
|
|
156
|
-
className: service.ClassName,
|
|
157
|
-
path: getInstancePath(service as Instance),
|
|
158
|
-
childCount: (service as Instance).GetChildren().size(),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { services };
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
126
|
function searchObjects(requestData: Record<string, unknown>) {
|
|
168
127
|
const query = requestData.query as string;
|
|
169
128
|
const searchType = (requestData.searchType as string) ?? "name";
|
|
@@ -316,31 +275,6 @@ function getInstanceProperties(requestData: Record<string, unknown>) {
|
|
|
316
275
|
}
|
|
317
276
|
}
|
|
318
277
|
|
|
319
|
-
function getInstanceChildren(requestData: Record<string, unknown>) {
|
|
320
|
-
const instancePath = requestData.instancePath as string;
|
|
321
|
-
if (!instancePath) return { error: "Instance path is required" };
|
|
322
|
-
|
|
323
|
-
const instance = getInstanceByPath(instancePath);
|
|
324
|
-
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
325
|
-
|
|
326
|
-
const children: { name: string; className: string; path: string; hasChildren: boolean; hasSource: boolean; enabled?: boolean }[] = [];
|
|
327
|
-
for (const child of instance.GetChildren()) {
|
|
328
|
-
const entry: { name: string; className: string; path: string; hasChildren: boolean; hasSource: boolean; enabled?: boolean } = {
|
|
329
|
-
name: child.Name,
|
|
330
|
-
className: child.ClassName,
|
|
331
|
-
path: getInstancePath(child),
|
|
332
|
-
hasChildren: child.GetChildren().size() > 0,
|
|
333
|
-
hasSource: child.IsA("LuaSourceContainer"),
|
|
334
|
-
};
|
|
335
|
-
if (child.IsA("BaseScript")) {
|
|
336
|
-
entry.enabled = child.Enabled;
|
|
337
|
-
}
|
|
338
|
-
children.push(entry);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return { instancePath, children, count: children.size() };
|
|
342
|
-
}
|
|
343
|
-
|
|
344
278
|
function searchByProperty(requestData: Record<string, unknown>) {
|
|
345
279
|
const propertyName = requestData.propertyName as string;
|
|
346
280
|
const propertyValue = requestData.propertyValue as string;
|
|
@@ -769,106 +703,14 @@ function grepScripts(requestData: Record<string, unknown>) {
|
|
|
769
703
|
};
|
|
770
704
|
}
|
|
771
705
|
|
|
772
|
-
function getDescendants(requestData: Record<string, unknown>) {
|
|
773
|
-
const instancePath = requestData.instancePath as string;
|
|
774
|
-
if (!instancePath) return { error: "Instance path is required" };
|
|
775
|
-
|
|
776
|
-
const maxDepth = (requestData.maxDepth as number) ?? 10;
|
|
777
|
-
const classFilter = requestData.classFilter as string | undefined;
|
|
778
|
-
|
|
779
|
-
const instance = getInstanceByPath(instancePath);
|
|
780
|
-
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
781
|
-
|
|
782
|
-
const descendants: { name: string; className: string; path: string; depth: number }[] = [];
|
|
783
|
-
|
|
784
|
-
function collect(inst: Instance, depth: number) {
|
|
785
|
-
if (depth > maxDepth) return;
|
|
786
|
-
for (const child of inst.GetChildren()) {
|
|
787
|
-
if (classFilter && !child.IsA(classFilter as keyof Instances)) continue;
|
|
788
|
-
descendants.push({
|
|
789
|
-
name: child.Name,
|
|
790
|
-
className: child.ClassName,
|
|
791
|
-
path: getInstancePath(child),
|
|
792
|
-
depth,
|
|
793
|
-
});
|
|
794
|
-
collect(child, depth + 1);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
collect(instance, 1);
|
|
799
|
-
|
|
800
|
-
return { instancePath, descendants, count: descendants.size(), maxDepth };
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function compareInstances(requestData: Record<string, unknown>) {
|
|
804
|
-
const instancePathA = requestData.instancePathA as string;
|
|
805
|
-
const instancePathB = requestData.instancePathB as string;
|
|
806
|
-
|
|
807
|
-
if (!instancePathA || !instancePathB) {
|
|
808
|
-
return { error: "Both instancePathA and instancePathB are required" };
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const instA = getInstanceByPath(instancePathA);
|
|
812
|
-
if (!instA) return { error: `Instance not found: ${instancePathA}` };
|
|
813
|
-
|
|
814
|
-
const instB = getInstanceByPath(instancePathB);
|
|
815
|
-
if (!instB) return { error: `Instance not found: ${instancePathB}` };
|
|
816
|
-
|
|
817
|
-
const commonProps = [
|
|
818
|
-
"Name", "ClassName",
|
|
819
|
-
"Size", "Position", "Rotation", "CFrame", "Anchored", "CanCollide",
|
|
820
|
-
"Transparency", "BrickColor", "Material", "Color", "Text", "TextColor3",
|
|
821
|
-
"BackgroundColor3", "Image", "ImageColor3", "Visible", "Active", "ZIndex",
|
|
822
|
-
"BorderSizePixel", "BackgroundTransparency", "ImageTransparency",
|
|
823
|
-
"TextTransparency", "Value", "Enabled", "Brightness", "Range", "Shadows",
|
|
824
|
-
];
|
|
825
|
-
|
|
826
|
-
const matching: Record<string, string> = {};
|
|
827
|
-
const differing: Record<string, { a: string; b: string }> = {};
|
|
828
|
-
const onlyA: string[] = [];
|
|
829
|
-
const onlyB: string[] = [];
|
|
830
|
-
|
|
831
|
-
for (const prop of commonProps) {
|
|
832
|
-
const [okA, valA] = pcall(() => tostring((instA as unknown as Record<string, unknown>)[prop]));
|
|
833
|
-
const [okB, valB] = pcall(() => tostring((instB as unknown as Record<string, unknown>)[prop]));
|
|
834
|
-
|
|
835
|
-
if (okA && okB) {
|
|
836
|
-
if (valA === valB) {
|
|
837
|
-
matching[prop] = valA as string;
|
|
838
|
-
} else {
|
|
839
|
-
differing[prop] = { a: valA as string, b: valB as string };
|
|
840
|
-
}
|
|
841
|
-
} else if (okA) {
|
|
842
|
-
onlyA.push(prop);
|
|
843
|
-
} else if (okB) {
|
|
844
|
-
onlyB.push(prop);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
return {
|
|
849
|
-
instancePathA,
|
|
850
|
-
instancePathB,
|
|
851
|
-
classNameA: instA.ClassName,
|
|
852
|
-
classNameB: instB.ClassName,
|
|
853
|
-
matching,
|
|
854
|
-
differing,
|
|
855
|
-
onlyA,
|
|
856
|
-
onlyB,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
|
|
860
706
|
export = {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
getProjectStructure,
|
|
871
|
-
grepScripts,
|
|
872
|
-
getDescendants,
|
|
873
|
-
compareInstances,
|
|
707
|
+
getFileTree,
|
|
708
|
+
searchFiles,
|
|
709
|
+
getPlaceInfo,
|
|
710
|
+
searchObjects,
|
|
711
|
+
getInstanceProperties,
|
|
712
|
+
searchByProperty,
|
|
713
|
+
getClassInfo,
|
|
714
|
+
getProjectStructure,
|
|
715
|
+
grepScripts,
|
|
874
716
|
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import Utils from "../Utils";
|
|
2
2
|
import Recording from "../Recording";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const { getInstancePath, getInstanceByPath, readScriptSource, splitLines, joinLines } = Utils;
|
|
4
|
+
const { getInstancePath, getInstanceByPath, readScriptSource, applyScriptSource, splitLines, joinLines } = Utils;
|
|
7
5
|
const { beginRecording, finishRecording } = Recording;
|
|
8
6
|
|
|
9
7
|
const SOURCE_TRUNCATE_CHAR_BUDGET = 25000;
|
|
@@ -125,42 +123,22 @@ function setScriptSource(requestData: Record<string, unknown>) {
|
|
|
125
123
|
const sourceToSet = normalizeEscapes(newSource);
|
|
126
124
|
const recordingId = beginRecording(`Set script source: ${instance.Name}`);
|
|
127
125
|
|
|
128
|
-
const [
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (readScriptSource(instance) !== sourceToSet) {
|
|
133
|
-
error("UpdateSourceAsync completed without updating the script source");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
success: true, instancePath,
|
|
138
|
-
oldSourceLength, newSourceLength: sourceToSet.size(),
|
|
139
|
-
method: "UpdateSourceAsync",
|
|
140
|
-
message: "Script source updated successfully (editor-safe)",
|
|
141
|
-
};
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
if (updateSuccess) {
|
|
145
|
-
finishRecording(recordingId, true);
|
|
146
|
-
return updateResult;
|
|
126
|
+
const [readSuccess, readResult] = pcall(() => readScriptSource(instance).size());
|
|
127
|
+
if (!readSuccess) {
|
|
128
|
+
finishRecording(recordingId, false);
|
|
129
|
+
return { error: `Failed to read script source before updating: ${readResult}` };
|
|
147
130
|
}
|
|
131
|
+
const oldSourceLength = readResult as number;
|
|
132
|
+
const applyResult = applyScriptSource(instance, sourceToSet);
|
|
148
133
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
(instance as unknown as { Source: string }).Source = sourceToSet;
|
|
152
|
-
|
|
134
|
+
if (applyResult.success) {
|
|
135
|
+
finishRecording(recordingId, true);
|
|
153
136
|
return {
|
|
154
137
|
success: true, instancePath,
|
|
155
|
-
oldSourceLength
|
|
156
|
-
method:
|
|
157
|
-
message:
|
|
138
|
+
oldSourceLength, newSourceLength: sourceToSet.size(),
|
|
139
|
+
method: applyResult.method,
|
|
140
|
+
message: `Script source updated successfully (${applyResult.method === "UpdateSourceAsync" ? "editor-safe" : "direct assignment"})`,
|
|
158
141
|
};
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
if (directSuccess) {
|
|
162
|
-
finishRecording(recordingId, true);
|
|
163
|
-
return directResult;
|
|
164
142
|
}
|
|
165
143
|
|
|
166
144
|
const [replaceSuccess, replaceResult] = pcall(() => {
|
|
@@ -172,9 +150,15 @@ function setScriptSource(requestData: Record<string, unknown>) {
|
|
|
172
150
|
|
|
173
151
|
const newScript = new Instance(className as keyof CreatableInstances) as LuaSourceContainer;
|
|
174
152
|
newScript.Name = name;
|
|
175
|
-
|
|
153
|
+
// @rbxts/types does not expose PluginSecurity Source writes.
|
|
154
|
+
const writableNewScript = newScript as unknown as { Source: string };
|
|
155
|
+
writableNewScript.Source = sourceToSet;
|
|
156
|
+
if (readScriptSource(newScript) !== sourceToSet) {
|
|
157
|
+
error("Replacement script source did not match the requested source");
|
|
158
|
+
}
|
|
176
159
|
if (wasBaseScript && enabled !== undefined) {
|
|
177
|
-
|
|
160
|
+
const newBaseScript = newScript as BaseScript;
|
|
161
|
+
newBaseScript.Enabled = enabled;
|
|
178
162
|
}
|
|
179
163
|
|
|
180
164
|
newScript.Parent = parent;
|
|
@@ -195,7 +179,7 @@ function setScriptSource(requestData: Record<string, unknown>) {
|
|
|
195
179
|
|
|
196
180
|
finishRecording(recordingId, false);
|
|
197
181
|
return {
|
|
198
|
-
error: `Failed to set script source.
|
|
182
|
+
error: `Failed to set script source. ${applyResult.error} Replace method failed: ${replaceResult}`,
|
|
199
183
|
};
|
|
200
184
|
}
|
|
201
185
|
|
|
@@ -264,11 +248,13 @@ function editScriptLines(requestData: Record<string, unknown>) {
|
|
|
264
248
|
// Byte-slice replacement avoids Lua pattern escaping (safe for multi-byte chars like em dashes).
|
|
265
249
|
const newSource = string.sub(source, 1, matchStart - 1) + newString + string.sub(source, matchStart + searchLen);
|
|
266
250
|
|
|
267
|
-
|
|
251
|
+
const applyResult = applyScriptSource(instance, newSource, source);
|
|
252
|
+
if (!applyResult.success) error(applyResult.error);
|
|
268
253
|
|
|
269
254
|
return {
|
|
270
255
|
success: true,
|
|
271
256
|
instancePath,
|
|
257
|
+
method: applyResult.method,
|
|
272
258
|
message: "Script edited successfully",
|
|
273
259
|
};
|
|
274
260
|
});
|
|
@@ -299,7 +285,8 @@ function insertScriptLines(requestData: Record<string, unknown>) {
|
|
|
299
285
|
const recordingId = beginRecording(`Insert script lines after line ${afterLine}: ${instance.Name}`);
|
|
300
286
|
|
|
301
287
|
const [success, result] = pcall(() => {
|
|
302
|
-
const
|
|
288
|
+
const source = readScriptSource(instance);
|
|
289
|
+
const [lines, hadTrailingNewline] = splitLines(source);
|
|
303
290
|
const totalLines = lines.size();
|
|
304
291
|
|
|
305
292
|
if (afterLine < 0 || afterLine > totalLines) error(`afterLine out of range (0-${totalLines})`);
|
|
@@ -312,13 +299,15 @@ function insertScriptLines(requestData: Record<string, unknown>) {
|
|
|
312
299
|
for (let i = afterLine; i < totalLines; i++) resultLines.push(lines[i]);
|
|
313
300
|
|
|
314
301
|
const newSource = joinLines(resultLines, hadTrailingNewline);
|
|
315
|
-
|
|
302
|
+
const applyResult = applyScriptSource(instance, newSource, source);
|
|
303
|
+
if (!applyResult.success) error(applyResult.error);
|
|
316
304
|
|
|
317
305
|
return {
|
|
318
306
|
success: true, instancePath,
|
|
319
307
|
insertedAfterLine: afterLine,
|
|
320
308
|
linesInserted: newLines.size(),
|
|
321
309
|
newLineCount: resultLines.size(),
|
|
310
|
+
method: applyResult.method,
|
|
322
311
|
message: "Script lines inserted successfully",
|
|
323
312
|
};
|
|
324
313
|
});
|
|
@@ -349,7 +338,8 @@ function deleteScriptLines(requestData: Record<string, unknown>) {
|
|
|
349
338
|
const recordingId = beginRecording(`Delete script lines ${startLine}-${endLine}: ${instance.Name}`);
|
|
350
339
|
|
|
351
340
|
const [success, result] = pcall(() => {
|
|
352
|
-
const
|
|
341
|
+
const source = readScriptSource(instance);
|
|
342
|
+
const [lines, hadTrailingNewline] = splitLines(source);
|
|
353
343
|
const totalLines = lines.size();
|
|
354
344
|
|
|
355
345
|
if (startLine < 1 || startLine > totalLines) error(`startLine out of range (1-${totalLines})`);
|
|
@@ -360,13 +350,15 @@ function deleteScriptLines(requestData: Record<string, unknown>) {
|
|
|
360
350
|
for (let i = endLine; i < totalLines; i++) resultLines.push(lines[i]);
|
|
361
351
|
|
|
362
352
|
const newSource = joinLines(resultLines, hadTrailingNewline);
|
|
363
|
-
|
|
353
|
+
const applyResult = applyScriptSource(instance, newSource, source);
|
|
354
|
+
if (!applyResult.success) error(applyResult.error);
|
|
364
355
|
|
|
365
356
|
return {
|
|
366
357
|
success: true, instancePath,
|
|
367
358
|
deletedLines: { startLine, endLine },
|
|
368
359
|
linesDeleted: endLine - startLine + 1,
|
|
369
360
|
newLineCount: resultLines.size(),
|
|
361
|
+
method: applyResult.method,
|
|
370
362
|
message: "Script lines deleted successfully",
|
|
371
363
|
};
|
|
372
364
|
});
|
|
@@ -435,6 +427,7 @@ function findAndReplaceInScripts(requestData: Record<string, unknown>) {
|
|
|
435
427
|
name: string;
|
|
436
428
|
className: string;
|
|
437
429
|
replacements: number;
|
|
430
|
+
error?: string;
|
|
438
431
|
}
|
|
439
432
|
|
|
440
433
|
const changes: ScriptChange[] = [];
|
|
@@ -447,9 +440,9 @@ function findAndReplaceInScripts(requestData: Record<string, unknown>) {
|
|
|
447
440
|
function processInstance(instance: Instance) {
|
|
448
441
|
if (hitLimit) return;
|
|
449
442
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
443
|
+
const matchesClass = classFilter === undefined
|
|
444
|
+
|| instance.ClassName.lower().find(classFilter.lower())[0] !== undefined;
|
|
445
|
+
if (instance.IsA("LuaSourceContainer") && matchesClass) {
|
|
453
446
|
scriptsSearched++;
|
|
454
447
|
const source = readScriptSource(instance);
|
|
455
448
|
|
|
@@ -475,23 +468,27 @@ function findAndReplaceInScripts(requestData: Record<string, unknown>) {
|
|
|
475
468
|
hitLimit = true;
|
|
476
469
|
return;
|
|
477
470
|
}
|
|
478
|
-
totalReplacements += replCount;
|
|
479
471
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
472
|
+
const applyResult = dryRun
|
|
473
|
+
? undefined
|
|
474
|
+
: applyScriptSource(instance, newSource, source);
|
|
475
|
+
if (applyResult !== undefined && !applyResult.success) {
|
|
476
|
+
changes.push({
|
|
477
|
+
instancePath: getInstancePath(instance),
|
|
478
|
+
name: instance.Name,
|
|
479
|
+
className: instance.ClassName,
|
|
480
|
+
replacements: 0,
|
|
481
|
+
error: applyResult.error ?? "Script write failed verification",
|
|
482
|
+
});
|
|
483
|
+
} else {
|
|
484
|
+
totalReplacements += replCount;
|
|
485
|
+
changes.push({
|
|
486
|
+
instancePath: getInstancePath(instance),
|
|
487
|
+
name: instance.Name,
|
|
488
|
+
className: instance.ClassName,
|
|
489
|
+
replacements: replCount,
|
|
483
490
|
});
|
|
484
|
-
if (!ok) {
|
|
485
|
-
(instance as unknown as { Source: string }).Source = newSource;
|
|
486
|
-
}
|
|
487
491
|
}
|
|
488
|
-
|
|
489
|
-
changes.push({
|
|
490
|
-
instancePath: getInstancePath(instance),
|
|
491
|
-
name: instance.Name,
|
|
492
|
-
className: instance.ClassName,
|
|
493
|
-
replacements: replCount,
|
|
494
|
-
});
|
|
495
492
|
}
|
|
496
493
|
}
|
|
497
494
|
|
|
@@ -501,20 +498,24 @@ function findAndReplaceInScripts(requestData: Record<string, unknown>) {
|
|
|
501
498
|
}
|
|
502
499
|
}
|
|
503
500
|
|
|
504
|
-
processInstance(startInstance);
|
|
501
|
+
const [traversalSuccess, traversalResult] = pcall(() => processInstance(startInstance));
|
|
505
502
|
|
|
503
|
+
const failedScripts = changes.filter((change) => change.error !== undefined).size();
|
|
504
|
+
const scriptsModified = changes.size() - failedScripts;
|
|
506
505
|
if (recordingId !== undefined) {
|
|
507
|
-
finishRecording(recordingId,
|
|
506
|
+
finishRecording(recordingId, scriptsModified > 0);
|
|
508
507
|
}
|
|
509
508
|
|
|
510
509
|
return {
|
|
511
|
-
success:
|
|
510
|
+
success: traversalSuccess && failedScripts === 0,
|
|
511
|
+
error: traversalSuccess ? undefined : `Script traversal failed: ${traversalResult}`,
|
|
512
512
|
dryRun,
|
|
513
513
|
pattern: searchPattern,
|
|
514
514
|
replacement,
|
|
515
515
|
totalReplacements,
|
|
516
516
|
scriptsSearched,
|
|
517
|
-
scriptsModified
|
|
517
|
+
scriptsModified,
|
|
518
|
+
scriptsFailed: failedScripts,
|
|
518
519
|
changes,
|
|
519
520
|
truncated: hitLimit,
|
|
520
521
|
};
|