mirror of
https://github.com/actions/runner.git
synced 2026-01-23 21:11:34 +08:00
Add step command refinements: --here, --id, and help commands
- Add --here position option to insert steps before the current step - Add --id option to specify custom step IDs for expression references - Add --help flag support for all step commands with detailed usage info - Update browser extension UI with ID field and improved position dropdown
This commit is contained in:
@@ -65,6 +65,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
|
||||
var result = command switch
|
||||
{
|
||||
HelpCommand help => HandleHelp(help),
|
||||
ListCommand list => HandleList(list),
|
||||
AddRunCommand addRun => HandleAddRun(addRun, jobContext),
|
||||
AddUsesCommand addUses => await HandleAddUsesAsync(addUses, jobContext),
|
||||
@@ -111,6 +112,211 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
|
||||
#region Command Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Handles the steps help command.
|
||||
/// </summary>
|
||||
private StepCommandResult HandleHelp(HelpCommand command)
|
||||
{
|
||||
string helpText = (command.Command, command.SubCommand) switch
|
||||
{
|
||||
(null, _) => GetTopLevelHelp(),
|
||||
("add", null) => GetAddHelp(),
|
||||
("add", "run") => GetAddRunHelp(),
|
||||
("add", "uses") => GetAddUsesHelp(),
|
||||
("edit", _) => GetEditHelp(),
|
||||
("remove", _) => GetRemoveHelp(),
|
||||
("move", _) => GetMoveHelp(),
|
||||
("list", _) => GetListHelp(),
|
||||
("export", _) => GetExportHelp(),
|
||||
_ => $"Unknown command: {command.Command}. Use 'steps --help' for available commands."
|
||||
};
|
||||
|
||||
return new StepCommandResult
|
||||
{
|
||||
Success = true,
|
||||
Message = helpText
|
||||
};
|
||||
}
|
||||
|
||||
#region Help Text Methods
|
||||
|
||||
private static string GetTopLevelHelp() =>
|
||||
@"steps - Manipulate job steps during debug session
|
||||
|
||||
COMMANDS:
|
||||
list Show all steps with status
|
||||
add Add a new step (run or uses)
|
||||
edit Modify a pending step
|
||||
remove Delete a pending step
|
||||
move Reorder a pending step
|
||||
export Generate YAML for modified steps
|
||||
|
||||
Use 'steps <command> --help' for more information about a command.";
|
||||
|
||||
private static string GetAddHelp() =>
|
||||
@"steps add - Add a new step to the job
|
||||
|
||||
USAGE:
|
||||
steps add run <script> [options] Add a shell command step
|
||||
steps add uses <action> [options] Add an action step
|
||||
|
||||
Use 'steps add run --help' or 'steps add uses --help' for detailed options.";
|
||||
|
||||
private static string GetAddRunHelp() =>
|
||||
@"steps add run - Add a shell command step
|
||||
|
||||
USAGE:
|
||||
steps add run ""<script>"" [options]
|
||||
|
||||
OPTIONS:
|
||||
--id <id> Step ID for referencing in expressions
|
||||
--name ""<name>"" Display name for the step
|
||||
--shell <shell> Shell to use (bash, sh, pwsh, python, cmd)
|
||||
--working-directory <dir> Working directory for the script
|
||||
--if ""<condition>"" Condition expression (default: success())
|
||||
--env KEY=value Environment variable (can repeat)
|
||||
--continue-on-error Don't fail job if step fails
|
||||
--timeout <minutes> Step timeout in minutes
|
||||
|
||||
POSITION OPTIONS:
|
||||
--here Insert before current step (default)
|
||||
--after <index> Insert after step at index
|
||||
--before <index> Insert before step at index
|
||||
--at <index> Insert at specific index
|
||||
--first Insert at first pending position
|
||||
--last Insert at end of job
|
||||
|
||||
EXAMPLES:
|
||||
steps add run ""npm test""
|
||||
steps add run ""echo hello"" --name ""Greeting"" --id greet
|
||||
steps add run ""./build.sh"" --shell bash --after 3";
|
||||
|
||||
private static string GetAddUsesHelp() =>
|
||||
@"steps add uses - Add an action step
|
||||
|
||||
USAGE:
|
||||
steps add uses <action@ref> [options]
|
||||
|
||||
OPTIONS:
|
||||
--id <id> Step ID for referencing in expressions
|
||||
--name ""<name>"" Display name for the step
|
||||
--with key=value Action input (can repeat)
|
||||
--env KEY=value Environment variable (can repeat)
|
||||
--if ""<condition>"" Condition expression (default: success())
|
||||
--continue-on-error Don't fail job if step fails
|
||||
--timeout <minutes> Step timeout in minutes
|
||||
|
||||
POSITION OPTIONS:
|
||||
--here Insert before current step (default)
|
||||
--after <index> Insert after step at index
|
||||
--before <index> Insert before step at index
|
||||
--at <index> Insert at specific index
|
||||
--first Insert at first pending position
|
||||
--last Insert at end of job
|
||||
|
||||
EXAMPLES:
|
||||
steps add uses actions/checkout@v4
|
||||
steps add uses actions/setup-node@v4 --with node-version=20
|
||||
steps add uses ./my-action --name ""Local Action"" --after 2";
|
||||
|
||||
private static string GetEditHelp() =>
|
||||
@"steps edit - Modify a pending step
|
||||
|
||||
USAGE:
|
||||
steps edit <index> [modifications]
|
||||
|
||||
MODIFICATIONS:
|
||||
--name ""<name>"" Change display name
|
||||
--script ""<script>"" Change script (run steps only)
|
||||
--shell <shell> Change shell (run steps only)
|
||||
--working-directory <dir> Change working directory
|
||||
--if ""<condition>"" Change condition expression
|
||||
--with key=value Set/update action input (uses steps only)
|
||||
--env KEY=value Set/update environment variable
|
||||
--remove-with <key> Remove action input
|
||||
--remove-env <key> Remove environment variable
|
||||
--continue-on-error Enable continue-on-error
|
||||
--no-continue-on-error Disable continue-on-error
|
||||
--timeout <minutes> Change timeout
|
||||
|
||||
EXAMPLES:
|
||||
steps edit 3 --name ""Updated Name""
|
||||
steps edit 4 --script ""npm run test:ci""
|
||||
steps edit 2 --env DEBUG=true --timeout 30";
|
||||
|
||||
private static string GetRemoveHelp() =>
|
||||
@"steps remove - Delete a pending step
|
||||
|
||||
USAGE:
|
||||
steps remove <index>
|
||||
|
||||
ARGUMENTS:
|
||||
<index> 1-based index of the step to remove (must be pending)
|
||||
|
||||
EXAMPLES:
|
||||
steps remove 5
|
||||
steps remove 3";
|
||||
|
||||
private static string GetMoveHelp() =>
|
||||
@"steps move - Reorder a pending step
|
||||
|
||||
USAGE:
|
||||
steps move <from> <position>
|
||||
|
||||
ARGUMENTS:
|
||||
<from> 1-based index of the step to move (must be pending)
|
||||
|
||||
POSITION OPTIONS:
|
||||
--here Move before current step
|
||||
--after <index> Move after step at index
|
||||
--before <index> Move before step at index
|
||||
--to <index> Move to specific index
|
||||
--first Move to first pending position
|
||||
--last Move to end of job
|
||||
|
||||
EXAMPLES:
|
||||
steps move 5 --after 2
|
||||
steps move 4 --first
|
||||
steps move 3 --here";
|
||||
|
||||
private static string GetListHelp() =>
|
||||
@"steps list - Show all steps with status
|
||||
|
||||
USAGE:
|
||||
steps list [options]
|
||||
|
||||
OPTIONS:
|
||||
--verbose Show additional step details
|
||||
--output json|text Output format (default: text)
|
||||
|
||||
OUTPUT:
|
||||
Shows all steps with:
|
||||
- Index number
|
||||
- Status indicator (completed, current, pending)
|
||||
- Step name
|
||||
- Step type (run/uses) and details
|
||||
- Change indicator ([ADDED], [MODIFIED], [MOVED])";
|
||||
|
||||
private static string GetExportHelp() =>
|
||||
@"steps export - Generate YAML for modified steps
|
||||
|
||||
USAGE:
|
||||
steps export [options]
|
||||
|
||||
OPTIONS:
|
||||
--changes-only Only export added/modified steps
|
||||
--with-comments Include change markers as YAML comments
|
||||
--output json|text Output format (default: text)
|
||||
|
||||
OUTPUT:
|
||||
Generates valid YAML that can be pasted into a workflow file.
|
||||
|
||||
EXAMPLES:
|
||||
steps export
|
||||
steps export --changes-only --with-comments";
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Handles the steps list command.
|
||||
/// </summary>
|
||||
@@ -227,9 +433,17 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// </summary>
|
||||
private StepCommandResult HandleAddRun(AddRunCommand command, IExecutionContext jobContext)
|
||||
{
|
||||
// Validate step ID uniqueness
|
||||
if (!string.IsNullOrEmpty(command.Id) && _manipulator.HasStepWithId(command.Id))
|
||||
{
|
||||
throw new StepCommandException(StepCommandErrors.DuplicateId,
|
||||
$"Step with ID '{command.Id}' already exists.");
|
||||
}
|
||||
|
||||
// Create the ActionStep
|
||||
var actionStep = _factory.CreateRunStep(
|
||||
script: command.Script,
|
||||
id: command.Id,
|
||||
name: command.Name,
|
||||
shell: command.Shell,
|
||||
workingDirectory: command.WorkingDirectory,
|
||||
@@ -290,9 +504,17 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// </summary>
|
||||
private async Task<StepCommandResult> HandleAddUsesAsync(AddUsesCommand command, IExecutionContext jobContext)
|
||||
{
|
||||
// Validate step ID uniqueness
|
||||
if (!string.IsNullOrEmpty(command.Id) && _manipulator.HasStepWithId(command.Id))
|
||||
{
|
||||
throw new StepCommandException(StepCommandErrors.DuplicateId,
|
||||
$"Step with ID '{command.Id}' already exists.");
|
||||
}
|
||||
|
||||
// Create the ActionStep
|
||||
var actionStep = _factory.CreateUsesStep(
|
||||
actionReference: command.Action,
|
||||
id: command.Id,
|
||||
name: command.Name,
|
||||
with: command.With,
|
||||
env: command.Env,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using GitHub.Runner.Common;
|
||||
|
||||
@@ -65,6 +66,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
public class AddRunCommand : StepCommand
|
||||
{
|
||||
public string Script { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Shell { get; set; }
|
||||
public string WorkingDirectory { get; set; }
|
||||
@@ -81,6 +83,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
public class AddUsesCommand : StepCommand
|
||||
{
|
||||
public string Action { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Dictionary<string, string> With { get; set; }
|
||||
public Dictionary<string, string> Env { get; set; }
|
||||
@@ -136,6 +139,23 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
public bool WithComments { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// steps [command] --help
|
||||
/// Shows help information for step commands.
|
||||
/// </summary>
|
||||
public class HelpCommand : StepCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// The command to show help for (null = top-level help)
|
||||
/// </summary>
|
||||
public string Command { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sub-command if applicable (e.g., "run" for "steps add run --help")
|
||||
/// </summary>
|
||||
public string SubCommand { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Position Types
|
||||
@@ -154,7 +174,9 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// <summary>Insert at first pending position</summary>
|
||||
First,
|
||||
/// <summary>Insert at end (default)</summary>
|
||||
Last
|
||||
Last,
|
||||
/// <summary>Insert before current step (requires paused state)</summary>
|
||||
Here
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,6 +192,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
public static StepPosition Before(int index) => new StepPosition { Type = PositionType.Before, Index = index };
|
||||
public static StepPosition First() => new StepPosition { Type = PositionType.First };
|
||||
public static StepPosition Last() => new StepPosition { Type = PositionType.Last };
|
||||
public static StepPosition Here() => new StepPosition { Type = PositionType.Here };
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -180,6 +203,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
PositionType.Before => $"before {Index}",
|
||||
PositionType.First => "first",
|
||||
PositionType.Last => "last",
|
||||
PositionType.Here => "here",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
@@ -225,12 +249,25 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
// Tokenize the input, respecting quoted strings
|
||||
var tokens = Tokenize(input);
|
||||
|
||||
// Handle bare "steps" command - show top-level help
|
||||
if (tokens.Count == 1 && tokens[0].Equals("steps", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new HelpCommand { Command = null };
|
||||
}
|
||||
|
||||
if (tokens.Count < 2 || !tokens[0].Equals("steps", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new StepCommandException(StepCommandErrors.ParseError,
|
||||
"Invalid command format. Expected: steps <command> [args...]");
|
||||
}
|
||||
|
||||
// Check for --help or -h anywhere in tokens
|
||||
if (tokens.Any(t => t.Equals("--help", StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Equals("-h", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return ParseHelpCommand(tokens);
|
||||
}
|
||||
|
||||
var subCommand = tokens[1].ToLower();
|
||||
|
||||
return subCommand switch
|
||||
@@ -363,6 +400,9 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
|
||||
switch (opt)
|
||||
{
|
||||
case "--id":
|
||||
cmd.Id = GetNextArg(tokens, ref i, "--id");
|
||||
break;
|
||||
case "--name":
|
||||
cmd.Name = GetNextArg(tokens, ref i, "--name");
|
||||
break;
|
||||
@@ -399,6 +439,9 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
case "--last":
|
||||
cmd.Position = StepPosition.Last();
|
||||
break;
|
||||
case "--here":
|
||||
cmd.Position = StepPosition.Here();
|
||||
break;
|
||||
default:
|
||||
throw new StepCommandException(StepCommandErrors.InvalidOption,
|
||||
$"Unknown option: {tokens[i]}");
|
||||
@@ -434,6 +477,9 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
|
||||
switch (opt)
|
||||
{
|
||||
case "--id":
|
||||
cmd.Id = GetNextArg(tokens, ref i, "--id");
|
||||
break;
|
||||
case "--name":
|
||||
cmd.Name = GetNextArg(tokens, ref i, "--name");
|
||||
break;
|
||||
@@ -467,6 +513,9 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
case "--last":
|
||||
cmd.Position = StepPosition.Last();
|
||||
break;
|
||||
case "--here":
|
||||
cmd.Position = StepPosition.Here();
|
||||
break;
|
||||
default:
|
||||
throw new StepCommandException(StepCommandErrors.InvalidOption,
|
||||
$"Unknown option: {tokens[i]}");
|
||||
@@ -638,6 +687,9 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
case "--last":
|
||||
cmd.Position = StepPosition.Last();
|
||||
break;
|
||||
case "--here":
|
||||
cmd.Position = StepPosition.Here();
|
||||
break;
|
||||
default:
|
||||
throw new StepCommandException(StepCommandErrors.InvalidOption,
|
||||
$"Unknown option: {tokens[i]}");
|
||||
@@ -647,7 +699,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
if (cmd.Position == null)
|
||||
{
|
||||
throw new StepCommandException(StepCommandErrors.ParseError,
|
||||
"Move command requires a position (--to, --after, --before, --first, or --last)");
|
||||
"Move command requires a position (--to, --after, --before, --first, --last, or --here)");
|
||||
}
|
||||
|
||||
return cmd;
|
||||
@@ -681,6 +733,41 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a help command from tokens containing --help or -h.
|
||||
/// </summary>
|
||||
private HelpCommand ParseHelpCommand(List<string> tokens)
|
||||
{
|
||||
// Create a copy to avoid modifying the original
|
||||
var workingTokens = new List<string>(tokens);
|
||||
|
||||
// Remove --help/-h from tokens
|
||||
workingTokens.RemoveAll(t => t.Equals("--help", StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Equals("-h", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// "steps --help" or just "steps" (after removing --help)
|
||||
if (workingTokens.Count <= 1)
|
||||
{
|
||||
return new HelpCommand { Command = null };
|
||||
}
|
||||
|
||||
// "steps <command> --help"
|
||||
var cmd = workingTokens[1].ToLower();
|
||||
|
||||
// Check for "steps add run --help" or "steps add uses --help"
|
||||
string subCmd = null;
|
||||
if (workingTokens.Count >= 3 && cmd == "add")
|
||||
{
|
||||
var possibleSubCmd = workingTokens[2].ToLower();
|
||||
if (possibleSubCmd == "run" || possibleSubCmd == "uses")
|
||||
{
|
||||
subCmd = possibleSubCmd;
|
||||
}
|
||||
}
|
||||
|
||||
return new HelpCommand { Command = cmd, SubCommand = subCmd };
|
||||
}
|
||||
|
||||
#region Argument Helpers
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -66,10 +66,12 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
public const string InvalidCommand = "INVALID_COMMAND";
|
||||
public const string InvalidOption = "INVALID_OPTION";
|
||||
public const string InvalidType = "INVALID_TYPE";
|
||||
public const string InvalidPosition = "INVALID_POSITION";
|
||||
public const string ActionDownloadFailed = "ACTION_DOWNLOAD_FAILED";
|
||||
public const string ParseError = "PARSE_ERROR";
|
||||
public const string NotPaused = "NOT_PAUSED";
|
||||
public const string NoContext = "NO_CONTEXT";
|
||||
public const string DuplicateId = "DUPLICATE_ID";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// Creates a new run step (script step).
|
||||
/// </summary>
|
||||
/// <param name="script">The script to execute</param>
|
||||
/// <param name="id">Optional step ID for referencing in expressions (e.g., steps.<id>.outputs)</param>
|
||||
/// <param name="name">Optional display name for the step</param>
|
||||
/// <param name="shell">Optional shell (bash, sh, pwsh, python, etc.)</param>
|
||||
/// <param name="workingDirectory">Optional working directory</param>
|
||||
@@ -28,6 +29,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// <returns>A configured ActionStep with ScriptReference</returns>
|
||||
ActionStep CreateRunStep(
|
||||
string script,
|
||||
string id = null,
|
||||
string name = null,
|
||||
string shell = null,
|
||||
string workingDirectory = null,
|
||||
@@ -40,6 +42,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// Creates a new uses step (action step).
|
||||
/// </summary>
|
||||
/// <param name="actionReference">The action reference (e.g., "actions/checkout@v4", "owner/repo@ref", "./local-action")</param>
|
||||
/// <param name="id">Optional step ID for referencing in expressions (e.g., steps.<id>.outputs)</param>
|
||||
/// <param name="name">Optional display name for the step</param>
|
||||
/// <param name="with">Optional input parameters for the action</param>
|
||||
/// <param name="env">Optional environment variables</param>
|
||||
@@ -49,6 +52,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// <returns>A configured ActionStep with RepositoryPathReference or ContainerRegistryReference</returns>
|
||||
ActionStep CreateUsesStep(
|
||||
string actionReference,
|
||||
string id = null,
|
||||
string name = null,
|
||||
Dictionary<string, string> with = null,
|
||||
Dictionary<string, string> env = null,
|
||||
@@ -132,6 +136,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// <inheritdoc/>
|
||||
public ActionStep CreateRunStep(
|
||||
string script,
|
||||
string id = null,
|
||||
string name = null,
|
||||
string shell = null,
|
||||
string workingDirectory = null,
|
||||
@@ -149,7 +154,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
var step = new ActionStep
|
||||
{
|
||||
Id = stepId,
|
||||
Name = $"_dynamic_{stepId:N}",
|
||||
Name = id ?? $"_dynamic_{stepId:N}",
|
||||
DisplayName = name ?? "Run script",
|
||||
Reference = new ScriptReference(),
|
||||
Condition = condition ?? "success()",
|
||||
@@ -183,6 +188,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// <inheritdoc/>
|
||||
public ActionStep CreateUsesStep(
|
||||
string actionReference,
|
||||
string id = null,
|
||||
string name = null,
|
||||
Dictionary<string, string> with = null,
|
||||
Dictionary<string, string> env = null,
|
||||
@@ -201,7 +207,7 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
var step = new ActionStep
|
||||
{
|
||||
Id = stepId,
|
||||
Name = $"_dynamic_{stepId:N}",
|
||||
Name = id ?? $"_dynamic_{stepId:N}",
|
||||
DisplayName = name ?? actionReference,
|
||||
Condition = condition ?? "success()",
|
||||
Enabled = true
|
||||
|
||||
@@ -112,6 +112,13 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
/// Clears all recorded changes (for testing or reset).
|
||||
/// </summary>
|
||||
void ClearChanges();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a step with the given ID already exists.
|
||||
/// </summary>
|
||||
/// <param name="id">The step ID to check for.</param>
|
||||
/// <returns>True if a step with that ID exists, false otherwise.</returns>
|
||||
bool HasStepWithId(string id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,6 +257,12 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
ArgUtil.NotNull(step, nameof(step));
|
||||
ValidateInitialized();
|
||||
|
||||
// Special case: --here inserts before current step
|
||||
if (position.Type == PositionType.Here)
|
||||
{
|
||||
return InsertStepHere(step);
|
||||
}
|
||||
|
||||
// Calculate the insertion index within the pending queue (0-based)
|
||||
int insertAt = CalculateInsertIndex(position);
|
||||
|
||||
@@ -285,6 +298,57 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a step before the current step (the one paused at a breakpoint).
|
||||
/// The new step becomes the next step to run when the user continues/steps forward.
|
||||
/// </summary>
|
||||
/// <param name="step">The step to insert.</param>
|
||||
/// <returns>The 1-based index where the step was inserted.</returns>
|
||||
private int InsertStepHere(IStep step)
|
||||
{
|
||||
if (_currentStep == null)
|
||||
{
|
||||
throw new StepCommandException(StepCommandErrors.InvalidPosition,
|
||||
"Can only use --here when paused at a breakpoint.");
|
||||
}
|
||||
|
||||
// Convert queue to list for manipulation
|
||||
var pending = _jobContext.JobSteps.ToList();
|
||||
_jobContext.JobSteps.Clear();
|
||||
|
||||
// Re-queue the current step at the front of pending
|
||||
pending.Insert(0, _currentStep);
|
||||
|
||||
// Insert the new step before it (at position 0)
|
||||
pending.Insert(0, step);
|
||||
|
||||
// Re-queue all steps
|
||||
foreach (var s in pending)
|
||||
{
|
||||
_jobContext.JobSteps.Enqueue(s);
|
||||
}
|
||||
|
||||
// The new step becomes the current step
|
||||
_currentStep = step;
|
||||
|
||||
// Calculate the 1-based index (new step takes current step's position)
|
||||
var newIndex = _completedSteps.Count + 1;
|
||||
|
||||
// Track the change
|
||||
var stepInfo = StepInfo.FromStep(step, newIndex, StepStatus.Current);
|
||||
stepInfo.Change = ChangeType.Added;
|
||||
|
||||
if (step is IActionRunner runner && runner.Action != null)
|
||||
{
|
||||
_addedStepIds.Add(runner.Action.Id);
|
||||
}
|
||||
|
||||
_changes.Add(StepChange.Added(stepInfo, newIndex));
|
||||
|
||||
Trace.Info($"Inserted step '{step.DisplayName}' at position {newIndex} (--here, before current step)");
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveStep(int index)
|
||||
{
|
||||
@@ -323,6 +387,12 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
ValidateInitialized();
|
||||
var stepInfo = ValidatePendingIndex(fromIndex);
|
||||
|
||||
// Special case: --here moves step to before current step
|
||||
if (position.Type == PositionType.Here)
|
||||
{
|
||||
return MoveStepHere(fromIndex, stepInfo);
|
||||
}
|
||||
|
||||
// Calculate queue indices - BEFORE modifying the queue
|
||||
var firstPendingIndex = GetFirstPendingIndex();
|
||||
var fromQueueIndex = fromIndex - firstPendingIndex;
|
||||
@@ -364,6 +434,64 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a step to before the current step (the one paused at a breakpoint).
|
||||
/// The moved step becomes the next step to run when the user continues/steps forward.
|
||||
/// </summary>
|
||||
/// <param name="fromIndex">The 1-based index of the step to move.</param>
|
||||
/// <param name="stepInfo">The StepInfo for the step being moved.</param>
|
||||
/// <returns>The new 1-based index of the step.</returns>
|
||||
private int MoveStepHere(int fromIndex, StepInfo stepInfo)
|
||||
{
|
||||
if (_currentStep == null)
|
||||
{
|
||||
throw new StepCommandException(StepCommandErrors.InvalidPosition,
|
||||
"Can only use --here when paused at a breakpoint.");
|
||||
}
|
||||
|
||||
// Calculate queue indices - BEFORE modifying the queue
|
||||
var firstPendingIndex = GetFirstPendingIndex();
|
||||
var fromQueueIndex = fromIndex - firstPendingIndex;
|
||||
|
||||
// Convert queue to list
|
||||
var pending = _jobContext.JobSteps.ToList();
|
||||
_jobContext.JobSteps.Clear();
|
||||
|
||||
// Remove the step from its original position
|
||||
var step = pending[fromQueueIndex];
|
||||
pending.RemoveAt(fromQueueIndex);
|
||||
|
||||
// Re-queue the current step at the front of pending
|
||||
pending.Insert(0, _currentStep);
|
||||
|
||||
// Insert the moved step before the current step (at position 0)
|
||||
pending.Insert(0, step);
|
||||
|
||||
// Re-queue all steps
|
||||
foreach (var s in pending)
|
||||
{
|
||||
_jobContext.JobSteps.Enqueue(s);
|
||||
}
|
||||
|
||||
// The moved step becomes the current step
|
||||
_currentStep = step;
|
||||
|
||||
// Calculate the new 1-based index (step takes current step's position)
|
||||
var newIndex = _completedSteps.Count + 1;
|
||||
|
||||
// Track the change
|
||||
var originalInfo = StepInfo.FromStep(step, fromIndex, StepStatus.Pending);
|
||||
_changes.Add(StepChange.Moved(originalInfo, newIndex));
|
||||
|
||||
if (step is IActionRunner runner && runner.Action != null)
|
||||
{
|
||||
_modifiedStepIds.Add(runner.Action.Id);
|
||||
}
|
||||
|
||||
Trace.Info($"Moved step '{step.DisplayName}' from position {fromIndex} to {newIndex} (--here, before current step)");
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void EditStep(int index, Action<ActionStep> edit)
|
||||
{
|
||||
@@ -423,6 +551,36 @@ namespace GitHub.Runner.Worker.Dap.StepCommands
|
||||
_originalSteps = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasStepWithId(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
return false;
|
||||
|
||||
// Check completed steps
|
||||
foreach (var step in _completedSteps)
|
||||
{
|
||||
if (step is IActionRunner runner && runner.Action?.Name == id)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check current step
|
||||
if (_currentStep is IActionRunner currentRunner && currentRunner.Action?.Name == id)
|
||||
return true;
|
||||
|
||||
// Check pending steps
|
||||
if (_jobContext?.JobSteps != null)
|
||||
{
|
||||
foreach (var step in _jobContext.JobSteps)
|
||||
{
|
||||
if (step is IActionRunner pendingRunner && pendingRunner.Action?.Name == id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -376,6 +376,19 @@ namespace GitHub.Runner.Common.Tests.Worker.Dap.StepCommands
|
||||
Assert.Equal(3, addCmd.Position.Index);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Parse_AddRunCommand_PositionHere()
|
||||
{
|
||||
// Arrange & Act
|
||||
var cmd = _parser.Parse("steps add run \"echo here\" --here");
|
||||
|
||||
// Assert
|
||||
var addCmd = Assert.IsType<AddRunCommand>(cmd);
|
||||
Assert.Equal(PositionType.Here, addCmd.Position.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -420,6 +433,20 @@ namespace GitHub.Runner.Common.Tests.Worker.Dap.StepCommands
|
||||
Assert.Equal("npm", addCmd.With["cache"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Parse_AddUsesCommand_PositionHere()
|
||||
{
|
||||
// Arrange & Act
|
||||
var cmd = _parser.Parse("steps add uses actions/checkout@v4 --here");
|
||||
|
||||
// Assert
|
||||
var addCmd = Assert.IsType<AddUsesCommand>(cmd);
|
||||
Assert.Equal("actions/checkout@v4", addCmd.Action);
|
||||
Assert.Equal(PositionType.Here, addCmd.Position.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -596,6 +623,19 @@ namespace GitHub.Runner.Common.Tests.Worker.Dap.StepCommands
|
||||
Assert.Equal(3, moveCmd.Position.Index);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Parse_MoveCommand_Here()
|
||||
{
|
||||
// Arrange & Act
|
||||
var cmd = _parser.Parse("steps move 5 --here");
|
||||
|
||||
// Assert
|
||||
var moveCmd = Assert.IsType<MoveCommand>(cmd);
|
||||
Assert.Equal(PositionType.Here, moveCmd.Position.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
|
||||
Reference in New Issue
Block a user