loop-task 2.1.0 → 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/dist/core/loop-controller.js +34 -29
- package/dist/i18n/en.json +1 -1
- package/dist/tui/components/TaskForm.js +24 -18
- package/package.json +1 -1
|
@@ -333,41 +333,46 @@ export class LoopController extends EventEmitter {
|
|
|
333
333
|
const cwd = resolveEffectiveCwd(this.options.cwd, this.projectDirectory);
|
|
334
334
|
const chainContext = {};
|
|
335
335
|
const hasChainTasks = !!(task?.onSuccessTaskId || task?.onFailureTaskId);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const shouldCaptureStdout = hasChainTasks ||
|
|
345
|
-
|
|
336
|
+
const singleCommandFallback = {
|
|
337
|
+
command: task?.command ?? this.options.command,
|
|
338
|
+
commandArgs: task?.commandArgs ?? this.options.commandArgs,
|
|
339
|
+
commandRaw: task?.commandRaw ?? this.options.commandRaw,
|
|
340
|
+
};
|
|
341
|
+
const taskSteps = task?.steps?.length
|
|
342
|
+
? task.steps
|
|
343
|
+
: [{ commands: [singleCommandFallback] }];
|
|
344
|
+
const shouldCaptureStdout = hasChainTasks || taskSteps.length > 1 || taskSteps[0].commands.length > 1;
|
|
345
|
+
let exitCode = 0;
|
|
346
|
+
let totalDuration = 0;
|
|
347
|
+
let combinedStdout = "";
|
|
348
|
+
for (const step of taskSteps) {
|
|
349
|
+
const stepResults = await Promise.allSettled(step.commands.map((cmd) => executeCommand(interpolate(cmd.command, chainContext), cmd.commandArgs.map(a => interpolate(a, chainContext)), cwd, this.logStream, AbortSignal.any([signal, this.runAbortController.signal]), this.runCount, shouldCaptureStdout)));
|
|
350
|
+
for (const r of stepResults) {
|
|
351
|
+
if (r.status === "fulfilled") {
|
|
352
|
+
totalDuration += r.value.duration;
|
|
353
|
+
if (r.value.stdout)
|
|
354
|
+
combinedStdout += (combinedStdout ? "\n" : "") + r.value.stdout;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const stepFailure = stepResults.some((r) => r.status === "rejected" || (r.status === "fulfilled" && r.value.exitCode !== 0));
|
|
358
|
+
if (stepFailure) {
|
|
359
|
+
const failed = stepResults.find((r) => r.status === "fulfilled" && r.value.exitCode !== 0);
|
|
360
|
+
exitCode = failed ? failed.value.exitCode : 1;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
346
364
|
this.runAbortController = null;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return sum + r.value.duration;
|
|
354
|
-
return sum;
|
|
355
|
-
}, 0);
|
|
356
|
-
const combinedStdout = results
|
|
357
|
-
.filter((r) => r.status === "fulfilled" && !!r.value.stdout)
|
|
358
|
-
.map((r) => r.value.stdout)
|
|
359
|
-
.join("\n");
|
|
365
|
+
if (shouldCaptureStdout && combinedStdout) {
|
|
366
|
+
const parsed = parseStdout(combinedStdout);
|
|
367
|
+
if (parsed !== null) {
|
|
368
|
+
Object.assign(chainContext, parsed);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
360
371
|
const result = {
|
|
361
372
|
exitCode,
|
|
362
373
|
duration: totalDuration,
|
|
363
374
|
stdout: combinedStdout || undefined,
|
|
364
375
|
};
|
|
365
|
-
if (shouldCaptureStdout && result.stdout) {
|
|
366
|
-
const parsed = parseStdout(result.stdout);
|
|
367
|
-
if (parsed !== null) {
|
|
368
|
-
Object.assign(chainContext, parsed);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
376
|
this.lastExitCode = result.exitCode;
|
|
372
377
|
this.lastDuration = result.duration;
|
|
373
378
|
const logSize = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size - this.currentRunStartOffset : 0;
|
package/dist/i18n/en.json
CHANGED
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
"cmdEditor.lines": "lines",
|
|
280
280
|
"cmdEditor.inlineHint": "enter: new line . ctrl+v: paste . ctrl+y: copy . tab: next field . ctrl+s: save",
|
|
281
281
|
"codeEditor.title": "Command Editor",
|
|
282
|
-
"codeEditor.hint": "ctrl+s save . esc cancel . ctrl+z/ctrl+shift+z undo/redo .
|
|
282
|
+
"codeEditor.hint": "ctrl+s save . esc cancel . ctrl+z/ctrl+shift+z undo/redo . && sequential . || parallel",
|
|
283
283
|
"codeEditor.buttonCopy": "Copy",
|
|
284
284
|
"codeEditor.buttonPaste": "Paste",
|
|
285
285
|
"codeEditor.buttonClear": "Clear",
|
|
@@ -24,8 +24,8 @@ export function TaskForm(props) {
|
|
|
24
24
|
const { mode, editTask, onCancel, onDone } = props;
|
|
25
25
|
const [tasks, setTasks] = useState([]);
|
|
26
26
|
const [commandValue, setCommandValue] = useState(editTask
|
|
27
|
-
? (editTask.
|
|
28
|
-
? editTask.commands.map(c => c.commandRaw ?? [c.command, ...c.commandArgs].join(" ")).join("\n&&\n")
|
|
27
|
+
? (editTask.steps?.length
|
|
28
|
+
? editTask.steps.map(step => step.commands.map(c => c.commandRaw ?? [c.command, ...c.commandArgs].join(" ")).join("\n||\n")).join("\n&&\n")
|
|
29
29
|
: (editTask.commandRaw ?? joinCommand(editTask)))
|
|
30
30
|
: "");
|
|
31
31
|
useEffect(() => {
|
|
@@ -102,30 +102,36 @@ export function TaskForm(props) {
|
|
|
102
102
|
return;
|
|
103
103
|
const onSuccessTaskId = resolveChainId(values.onSuccess ?? "");
|
|
104
104
|
const onFailureTaskId = resolveChainId(values.onFailure ?? "");
|
|
105
|
-
// Split by && to build parallel commands. If only one command, keep
|
|
106
|
-
// backward compat (command + commandArgs, no commands[]).
|
|
107
105
|
const commandSegments = commandValue
|
|
108
106
|
.split(/\n&&\n|\n&&|&&\n|&&/)
|
|
109
107
|
.map(s => s.trim())
|
|
110
108
|
.filter(s => s.length > 0);
|
|
111
109
|
let payload;
|
|
112
|
-
if (commandSegments.length > 1) {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
if (commandSegments.length > 1 || commandValue.includes("||")) {
|
|
111
|
+
const steps = commandSegments.slice(0, 8).map(seg => {
|
|
112
|
+
const parallelCmds = seg
|
|
113
|
+
.split(/\n\|\|\n|\n\|\||\|\|\n|\|\|/)
|
|
114
|
+
.map(s => s.trim())
|
|
115
|
+
.filter(s => s.length > 0)
|
|
116
|
+
.slice(0, 4)
|
|
117
|
+
.map(cmdSeg => {
|
|
118
|
+
const joined = joinCommandLines(cmdSeg);
|
|
119
|
+
const tokens = parseCommandLine(joined);
|
|
120
|
+
return {
|
|
121
|
+
command: tokens[0] ?? "",
|
|
122
|
+
commandArgs: tokens.slice(1),
|
|
123
|
+
commandRaw: cmdSeg,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
return { commands: parallelCmds };
|
|
121
127
|
});
|
|
122
|
-
|
|
128
|
+
const firstCmd = steps[0].commands[0];
|
|
123
129
|
payload = {
|
|
124
130
|
name,
|
|
125
|
-
command:
|
|
126
|
-
commandArgs:
|
|
127
|
-
commandRaw:
|
|
128
|
-
|
|
131
|
+
command: firstCmd.command,
|
|
132
|
+
commandArgs: firstCmd.commandArgs,
|
|
133
|
+
commandRaw: firstCmd.commandRaw,
|
|
134
|
+
steps,
|
|
129
135
|
onSuccessTaskId,
|
|
130
136
|
onFailureTaskId,
|
|
131
137
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loop-task",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Loop engineering toolkit. Run any command on a cadence, in the background, managed from a terminal board. Schedule tests, builds, syncs, or agent prompts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|