surf-cli 2.4.2 → 2.5.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/README.md +84 -6
- package/native/cli.cjs +521 -19
- package/native/do-executor.cjs +333 -73
- package/native/gemini-client.cjs +74 -29
- package/package.json +1 -1
package/native/do-executor.cjs
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Executor for surf `do` workflow commands
|
|
3
3
|
*
|
|
4
4
|
* Executes steps sequentially with auto-waits and streaming progress output.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Step outputs: capture results with `as` field
|
|
7
|
+
* - Loops: `repeat` for fixed iterations, `each` for array iteration
|
|
8
|
+
* - Variable substitution: %{varname} syntax
|
|
9
|
+
*
|
|
5
10
|
* Follows the same socket communication pattern as --script mode in cli.cjs.
|
|
6
11
|
*/
|
|
7
12
|
|
|
@@ -9,6 +14,9 @@ const net = require("net");
|
|
|
9
14
|
|
|
10
15
|
const SOCKET_PATH = "/tmp/surf.sock";
|
|
11
16
|
|
|
17
|
+
// Maximum iterations for loops (safety cap)
|
|
18
|
+
const MAX_LOOP_ITERATIONS = 100;
|
|
19
|
+
|
|
12
20
|
// Commands that trigger auto-wait after execution
|
|
13
21
|
// Note: 'type' is intentionally excluded - typing doesn't trigger navigation or DOM changes
|
|
14
22
|
const AUTO_WAIT_COMMANDS = [
|
|
@@ -114,6 +122,32 @@ function sendDoRequest(toolName, toolArgs, context = {}) {
|
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a variable reference or perform string substitution
|
|
127
|
+
* @param {*} template - Value to resolve (may contain %{var} references)
|
|
128
|
+
* @param {object} vars - Variables map
|
|
129
|
+
* @returns {*} - Resolved value
|
|
130
|
+
*/
|
|
131
|
+
function resolveVar(template, vars) {
|
|
132
|
+
if (typeof template !== 'string') return template;
|
|
133
|
+
|
|
134
|
+
// Check if it's a simple variable reference like %{urls}
|
|
135
|
+
const match = template.match(/^%\{(\w+)\}$/);
|
|
136
|
+
if (match) {
|
|
137
|
+
const value = vars[match[1]];
|
|
138
|
+
return value !== undefined ? value : template;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Otherwise do string substitution
|
|
142
|
+
return template.replace(/%\{(\w+)\}/g, (_, name) => {
|
|
143
|
+
const val = vars[name];
|
|
144
|
+
if (val === undefined) return `%{${name}}`;
|
|
145
|
+
// Convert objects/arrays to string for interpolation
|
|
146
|
+
if (typeof val === 'object') return JSON.stringify(val);
|
|
147
|
+
return String(val);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
117
151
|
/**
|
|
118
152
|
* Substitute variables in arguments using %{varname} syntax
|
|
119
153
|
* @param {object} args - Arguments object
|
|
@@ -123,10 +157,28 @@ function sendDoRequest(toolName, toolArgs, context = {}) {
|
|
|
123
157
|
function substituteVars(args, vars) {
|
|
124
158
|
if (!args || typeof args !== 'object') return args;
|
|
125
159
|
|
|
160
|
+
// Handle arrays specially to preserve array type
|
|
161
|
+
if (Array.isArray(args)) {
|
|
162
|
+
return args.map(item => {
|
|
163
|
+
if (typeof item === 'string') {
|
|
164
|
+
return resolveVar(item, vars);
|
|
165
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
166
|
+
return substituteVars(item, vars);
|
|
167
|
+
} else {
|
|
168
|
+
return item;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Handle plain objects
|
|
126
174
|
const result = {};
|
|
127
175
|
for (const [key, val] of Object.entries(args)) {
|
|
128
176
|
if (typeof val === 'string') {
|
|
129
|
-
result[key] = val
|
|
177
|
+
result[key] = resolveVar(val, vars);
|
|
178
|
+
} else if (Array.isArray(val)) {
|
|
179
|
+
result[key] = substituteVars(val, vars);
|
|
180
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
181
|
+
result[key] = substituteVars(val, vars);
|
|
130
182
|
} else {
|
|
131
183
|
result[key] = val;
|
|
132
184
|
}
|
|
@@ -134,9 +186,219 @@ function substituteVars(args, vars) {
|
|
|
134
186
|
return result;
|
|
135
187
|
}
|
|
136
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Extract usable output from a step response for the `as` capture
|
|
191
|
+
* @param {object} resp - Response from sendDoRequest
|
|
192
|
+
* @returns {*} - Extracted value
|
|
193
|
+
*/
|
|
194
|
+
function extractStepOutput(resp) {
|
|
195
|
+
// MCP format: resp.result.content[0].text
|
|
196
|
+
if (resp.result?.content?.[0]?.text) {
|
|
197
|
+
const text = resp.result.content[0].text;
|
|
198
|
+
// Try to parse as JSON, otherwise return raw text
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(text);
|
|
201
|
+
} catch {
|
|
202
|
+
return text;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Direct value (some tools return this)
|
|
207
|
+
if (resp.value !== undefined) return resp.value;
|
|
208
|
+
|
|
209
|
+
// Direct result object
|
|
210
|
+
if (resp.result !== undefined) return resp.result;
|
|
211
|
+
|
|
212
|
+
// Fallback to the whole response
|
|
213
|
+
return resp;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Execute a single tool step (non-loop)
|
|
218
|
+
* @param {object} step - Step to execute { cmd, args, as? }
|
|
219
|
+
* @param {object} vars - Variables map (mutated if step has `as`)
|
|
220
|
+
* @param {object} context - Execution context
|
|
221
|
+
* @param {object} options - Execution options
|
|
222
|
+
* @returns {Promise<object>} - Result { success, error?, output? }
|
|
223
|
+
*/
|
|
224
|
+
async function executeSingleStep(step, vars, context, options) {
|
|
225
|
+
const { autoWait = true, stepDelay = 100 } = options;
|
|
226
|
+
|
|
227
|
+
// Substitute variables in args
|
|
228
|
+
const resolvedArgs = substituteVars(step.args || {}, vars);
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const resp = await sendDoRequest(step.cmd, resolvedArgs, context);
|
|
232
|
+
|
|
233
|
+
if (resp.error) {
|
|
234
|
+
const errText = resp.error.content?.[0]?.text || JSON.stringify(resp.error);
|
|
235
|
+
return { success: false, error: errText };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Capture output if step has `as` field
|
|
239
|
+
if (step.as) {
|
|
240
|
+
const output = extractStepOutput(resp);
|
|
241
|
+
vars[step.as] = output;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Command-specific auto-wait
|
|
245
|
+
if (autoWait) {
|
|
246
|
+
const waitCmd = getAutoWaitCommand(step.cmd);
|
|
247
|
+
if (waitCmd) {
|
|
248
|
+
const waitArgs = waitCmd === 'wait.load'
|
|
249
|
+
? { timeout: 10000 }
|
|
250
|
+
: { stable: 100, timeout: 5000 };
|
|
251
|
+
try {
|
|
252
|
+
await sendDoRequest(waitCmd, waitArgs, context);
|
|
253
|
+
} catch {
|
|
254
|
+
// Ignore auto-wait failures silently
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Delay between steps
|
|
260
|
+
if (stepDelay > 0) {
|
|
261
|
+
await new Promise(r => setTimeout(r, stepDelay));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { success: true, output: step.as ? vars[step.as] : undefined };
|
|
265
|
+
} catch (err) {
|
|
266
|
+
return { success: false, error: err.message };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Execute a single step, handling loops recursively
|
|
272
|
+
* @param {object} step - Step to execute (may be a loop or regular step)
|
|
273
|
+
* @param {object} vars - Variables map
|
|
274
|
+
* @param {object} context - Execution context
|
|
275
|
+
* @param {object} options - Execution options
|
|
276
|
+
* @param {function} onProgress - Progress callback for streaming output
|
|
277
|
+
* @returns {Promise<object>} - Result { success, error?, stepsExecuted }
|
|
278
|
+
*/
|
|
279
|
+
async function executeStep(step, vars, context, options, onProgress) {
|
|
280
|
+
const { onError = 'stop' } = options;
|
|
281
|
+
|
|
282
|
+
// Handle `repeat` loop
|
|
283
|
+
if (step.repeat !== undefined) {
|
|
284
|
+
// Resolve repeat count (may be a variable)
|
|
285
|
+
let max = resolveVar(step.repeat, vars);
|
|
286
|
+
if (typeof max === 'string') max = parseInt(max, 10);
|
|
287
|
+
if (typeof max !== 'number' || isNaN(max)) max = 1;
|
|
288
|
+
|
|
289
|
+
// Safety cap
|
|
290
|
+
max = Math.min(max, MAX_LOOP_ITERATIONS);
|
|
291
|
+
|
|
292
|
+
if (!Array.isArray(step.steps) || step.steps.length === 0) {
|
|
293
|
+
return { success: false, error: 'repeat: steps array required', stepsExecuted: 0 };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let totalExecuted = 0;
|
|
297
|
+
|
|
298
|
+
for (let i = 0; i < max; i++) {
|
|
299
|
+
// Create loop-scoped variables
|
|
300
|
+
const loopVars = { ...vars, _index: i, _iteration: i + 1 };
|
|
301
|
+
|
|
302
|
+
// Execute nested steps
|
|
303
|
+
for (const nestedStep of step.steps) {
|
|
304
|
+
const result = await executeStep(nestedStep, loopVars, context, options, onProgress);
|
|
305
|
+
totalExecuted += result.stepsExecuted || 1;
|
|
306
|
+
|
|
307
|
+
if (!result.success && onError === 'stop') {
|
|
308
|
+
return { success: false, error: result.error, stepsExecuted: totalExecuted };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Copy captured variables back to parent scope (only from regular steps, not loops)
|
|
313
|
+
for (const nestedStep of step.steps) {
|
|
314
|
+
// Skip loop steps - their 'as' is the loop variable, not an output capture
|
|
315
|
+
const isNestedLoop = nestedStep.repeat !== undefined || nestedStep.each !== undefined;
|
|
316
|
+
if (!isNestedLoop && nestedStep.as && loopVars[nestedStep.as] !== undefined) {
|
|
317
|
+
vars[nestedStep.as] = loopVars[nestedStep.as];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check `until` condition
|
|
322
|
+
if (step.until) {
|
|
323
|
+
const untilResult = await executeSingleStep(step.until, loopVars, context, options);
|
|
324
|
+
totalExecuted++;
|
|
325
|
+
|
|
326
|
+
// Exit loop if until condition is truthy
|
|
327
|
+
const exitValue = untilResult.output;
|
|
328
|
+
if (exitValue === true || exitValue === 'true' || exitValue) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return { success: true, stepsExecuted: totalExecuted };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Handle `each` loop
|
|
338
|
+
if (step.each !== undefined) {
|
|
339
|
+
const items = resolveVar(step.each, vars);
|
|
340
|
+
|
|
341
|
+
if (!Array.isArray(items)) {
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
error: `each: expected array, got ${typeof items}${items === undefined ? ' (undefined)' : ''}`,
|
|
345
|
+
stepsExecuted: 0
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!Array.isArray(step.steps) || step.steps.length === 0) {
|
|
350
|
+
return { success: false, error: 'each: steps array required', stepsExecuted: 0 };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Safety cap
|
|
354
|
+
const maxItems = Math.min(items.length, MAX_LOOP_ITERATIONS);
|
|
355
|
+
const itemVar = step.as || 'item';
|
|
356
|
+
let totalExecuted = 0;
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < maxItems; i++) {
|
|
359
|
+
// Create loop-scoped variables
|
|
360
|
+
const loopVars = { ...vars, [itemVar]: items[i], _index: i, _iteration: i + 1 };
|
|
361
|
+
|
|
362
|
+
// Execute nested steps
|
|
363
|
+
for (const nestedStep of step.steps) {
|
|
364
|
+
const result = await executeStep(nestedStep, loopVars, context, options, onProgress);
|
|
365
|
+
totalExecuted += result.stepsExecuted || 1;
|
|
366
|
+
|
|
367
|
+
if (!result.success && onError === 'stop') {
|
|
368
|
+
return { success: false, error: result.error, stepsExecuted: totalExecuted };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Copy captured variables back to parent scope (only from regular steps, not loops)
|
|
373
|
+
for (const nestedStep of step.steps) {
|
|
374
|
+
// Skip loop steps - their 'as' is the loop variable, not an output capture
|
|
375
|
+
const isNestedLoop = nestedStep.repeat !== undefined || nestedStep.each !== undefined;
|
|
376
|
+
if (!isNestedLoop && nestedStep.as && loopVars[nestedStep.as] !== undefined) {
|
|
377
|
+
vars[nestedStep.as] = loopVars[nestedStep.as];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return { success: true, stepsExecuted: totalExecuted };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Regular step (non-loop)
|
|
386
|
+
if (onProgress) {
|
|
387
|
+
onProgress(step, 'start');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const result = await executeSingleStep(step, vars, context, options);
|
|
391
|
+
|
|
392
|
+
if (onProgress) {
|
|
393
|
+
onProgress(step, result.success ? 'ok' : 'fail', result.error);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { ...result, stepsExecuted: 1 };
|
|
397
|
+
}
|
|
398
|
+
|
|
137
399
|
/**
|
|
138
400
|
* Execute all workflow steps sequentially
|
|
139
|
-
* @param {Array<
|
|
401
|
+
* @param {Array<object>} steps - Steps to execute
|
|
140
402
|
* @param {object} options - Execution options
|
|
141
403
|
* @returns {Promise<object>} - Execution result
|
|
142
404
|
*/
|
|
@@ -147,118 +409,111 @@ async function executeDoSteps(steps, options = {}) {
|
|
|
147
409
|
stepDelay = 100,
|
|
148
410
|
context = {},
|
|
149
411
|
quiet = false, // For --json mode, suppress streaming output
|
|
412
|
+
vars: initialVars = {},
|
|
150
413
|
} = options;
|
|
151
414
|
|
|
152
415
|
const results = [];
|
|
153
|
-
const vars = context.vars || {};
|
|
416
|
+
const vars = { ...initialVars, ...(context.vars || {}) };
|
|
154
417
|
const total = steps.length;
|
|
155
418
|
let failed = 0;
|
|
419
|
+
let stepsExecuted = 0;
|
|
156
420
|
const startTotal = Date.now();
|
|
157
421
|
|
|
158
422
|
for (let i = 0; i < total; i++) {
|
|
159
423
|
const step = steps[i];
|
|
160
424
|
const startTime = Date.now();
|
|
161
|
-
const stepNum = `[${i + 1}/${total}]`;
|
|
162
425
|
|
|
163
|
-
//
|
|
164
|
-
const
|
|
165
|
-
.map(([k, v]) => typeof v === "string" && v.length > 40
|
|
166
|
-
? `${k}="${v.slice(0, 37)}..."`
|
|
167
|
-
: `${k}=${JSON.stringify(v)}`)
|
|
168
|
-
.join(" ");
|
|
169
|
-
const desc = argSummary ? `${step.cmd} ${argSummary}` : step.cmd;
|
|
426
|
+
// Check if this is a loop step
|
|
427
|
+
const isLoop = step.repeat !== undefined || step.each !== undefined;
|
|
170
428
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// Substitute variables in args
|
|
178
|
-
const resolvedArgs = substituteVars(step.args, vars);
|
|
429
|
+
if (isLoop) {
|
|
430
|
+
// Loops handle their own progress output
|
|
431
|
+
if (!quiet) {
|
|
432
|
+
const loopType = step.repeat !== undefined ? `repeat ${step.repeat}` : `each ${step.each}`;
|
|
433
|
+
console.log(`[${i + 1}/${total}] Loop: ${loopType} (${step.steps?.length || 0} nested steps)`);
|
|
434
|
+
}
|
|
179
435
|
|
|
180
|
-
const
|
|
436
|
+
const result = await executeStep(step, vars, context, { onError, autoWait, stepDelay }, null);
|
|
181
437
|
const ms = Date.now() - startTime;
|
|
182
438
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
console.log(`FAIL`);
|
|
188
|
-
console.log(` Error: ${errText}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
results.push({ step: i + 1, cmd: step.cmd, status: 'error', error: errText, ms });
|
|
439
|
+
stepsExecuted += result.stepsExecuted || 0;
|
|
440
|
+
|
|
441
|
+
if (!result.success) {
|
|
442
|
+
results.push({ step: i + 1, type: 'loop', status: 'error', error: result.error, ms });
|
|
192
443
|
failed++;
|
|
193
444
|
|
|
194
445
|
if (onError === 'stop') {
|
|
195
446
|
return {
|
|
196
447
|
status: 'failed',
|
|
197
|
-
completedSteps:
|
|
448
|
+
completedSteps: stepsExecuted,
|
|
198
449
|
totalSteps: total,
|
|
199
450
|
results,
|
|
200
|
-
error:
|
|
201
|
-
totalMs: Date.now() - startTotal
|
|
451
|
+
error: result.error,
|
|
452
|
+
totalMs: Date.now() - startTotal,
|
|
453
|
+
vars
|
|
202
454
|
};
|
|
203
455
|
}
|
|
204
456
|
} else {
|
|
457
|
+
results.push({ step: i + 1, type: 'loop', status: 'ok', stepsExecuted: result.stepsExecuted, ms });
|
|
205
458
|
if (!quiet) {
|
|
206
|
-
console.log(`
|
|
459
|
+
console.log(` Loop completed: ${result.stepsExecuted} steps (${ms}ms)`);
|
|
207
460
|
}
|
|
208
|
-
|
|
209
|
-
results.push({ step: i + 1, cmd: step.cmd, status: 'ok', ms });
|
|
210
|
-
|
|
211
|
-
// Command-specific auto-wait
|
|
212
|
-
if (autoWait) {
|
|
213
|
-
const waitCmd = getAutoWaitCommand(step.cmd);
|
|
214
|
-
if (waitCmd) {
|
|
215
|
-
const waitArgs = waitCmd === 'wait.load'
|
|
216
|
-
? { timeout: 10000 }
|
|
217
|
-
: { stable: 100, timeout: 5000 };
|
|
218
|
-
try {
|
|
219
|
-
await sendDoRequest(waitCmd, waitArgs, context);
|
|
220
|
-
} catch {
|
|
221
|
-
// Ignore auto-wait failures silently
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Fixed delay between steps
|
|
228
|
-
if (stepDelay > 0 && i < total - 1) {
|
|
229
|
-
await new Promise(r => setTimeout(r, stepDelay));
|
|
230
461
|
}
|
|
231
|
-
}
|
|
232
|
-
|
|
462
|
+
} else {
|
|
463
|
+
// Regular step
|
|
464
|
+
const stepNum = `[${i + 1}/${total}]`;
|
|
465
|
+
const argSummary = Object.entries(step.args || {})
|
|
466
|
+
.map(([k, v]) => typeof v === "string" && v.length > 40
|
|
467
|
+
? `${k}="${v.slice(0, 37)}..."`
|
|
468
|
+
: `${k}=${JSON.stringify(v)}`)
|
|
469
|
+
.join(" ");
|
|
470
|
+
const desc = argSummary ? `${step.cmd} ${argSummary}` : step.cmd;
|
|
233
471
|
|
|
234
472
|
if (!quiet) {
|
|
235
|
-
|
|
236
|
-
console.log(` Error: ${err.message}`);
|
|
473
|
+
process.stdout.write(`${stepNum} ${desc} ... `);
|
|
237
474
|
}
|
|
238
475
|
|
|
239
|
-
|
|
240
|
-
|
|
476
|
+
const result = await executeSingleStep(step, vars, context, { onError, autoWait, stepDelay });
|
|
477
|
+
const ms = Date.now() - startTime;
|
|
478
|
+
stepsExecuted++;
|
|
241
479
|
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
480
|
+
if (!result.success) {
|
|
481
|
+
if (!quiet) {
|
|
482
|
+
console.log('FAIL');
|
|
483
|
+
console.log(` Error: ${result.error}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
results.push({ step: i + 1, cmd: step.cmd, status: 'error', error: result.error, ms });
|
|
487
|
+
failed++;
|
|
488
|
+
|
|
489
|
+
if (onError === 'stop') {
|
|
490
|
+
return {
|
|
491
|
+
status: 'failed',
|
|
492
|
+
completedSteps: stepsExecuted - 1,
|
|
493
|
+
totalSteps: total,
|
|
494
|
+
results,
|
|
495
|
+
error: result.error,
|
|
496
|
+
totalMs: Date.now() - startTotal,
|
|
497
|
+
vars
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
if (!quiet) {
|
|
502
|
+
console.log(`OK (${ms}ms)`);
|
|
503
|
+
}
|
|
504
|
+
results.push({ step: i + 1, cmd: step.cmd, status: 'ok', ms });
|
|
251
505
|
}
|
|
252
506
|
}
|
|
253
507
|
}
|
|
254
508
|
|
|
255
509
|
return {
|
|
256
510
|
status: failed > 0 ? 'partial' : 'completed',
|
|
257
|
-
completedSteps:
|
|
511
|
+
completedSteps: stepsExecuted,
|
|
258
512
|
totalSteps: total,
|
|
259
513
|
results,
|
|
260
514
|
failed,
|
|
261
|
-
totalMs: Date.now() - startTotal
|
|
515
|
+
totalMs: Date.now() - startTotal,
|
|
516
|
+
vars
|
|
262
517
|
};
|
|
263
518
|
}
|
|
264
519
|
|
|
@@ -268,6 +523,11 @@ module.exports = {
|
|
|
268
523
|
shouldAutoWait,
|
|
269
524
|
getAutoWaitCommand,
|
|
270
525
|
substituteVars,
|
|
526
|
+
resolveVar,
|
|
527
|
+
extractStepOutput,
|
|
528
|
+
executeStep,
|
|
529
|
+
executeSingleStep,
|
|
271
530
|
AUTO_WAIT_COMMANDS,
|
|
272
|
-
AUTO_WAIT_MAP
|
|
531
|
+
AUTO_WAIT_MAP,
|
|
532
|
+
MAX_LOOP_ITERATIONS
|
|
273
533
|
};
|