mirror of
https://github.com/actions/runner.git
synced 2026-01-22 20:44:30 +08:00
simplify
This commit is contained in:
@@ -11,10 +11,10 @@
|
||||
- [x] **Chunk 2:** Step Serializer (ActionStep → YAML)
|
||||
- [x] **Chunk 3:** Step Factory (Create new steps)
|
||||
- [x] **Chunk 4:** Step Manipulator (Queue operations)
|
||||
- [x] **Chunk 5:** REPL Commands (!step list, !step add run, !step edit, !step remove, !step move)
|
||||
- [x] **Chunk 6:** Action Download Integration (!step add uses)
|
||||
- [x] **Chunk 7:** Export Command (!step export)
|
||||
- [x] **Chunk 8:** JSON API for Browser Extension
|
||||
- [x] **Chunk 5:** REPL Commands (steps list, steps add run, steps edit, steps remove, steps move)
|
||||
- [x] **Chunk 6:** Action Download Integration (steps add uses)
|
||||
- [x] **Chunk 7:** Export Command (steps export)
|
||||
- [x] **Chunk 8:** Output Format Flag (--output text|json for programmatic use)
|
||||
- [x] **Chunk 9:** Browser Extension UI
|
||||
|
||||
## Overview
|
||||
@@ -28,7 +28,7 @@ This transforms the debugger from a "read-only inspection tool" into an **intera
|
||||
- **Primary:** Enable add/edit/move/delete of job steps during debug session
|
||||
- **Primary:** Support both `run` and `uses` step types
|
||||
- **Primary:** Export modified steps as valid YAML
|
||||
- **Secondary:** Provide both REPL commands and JSON API for different clients
|
||||
- **Secondary:** Provide `--output` flag for text/JSON response format
|
||||
- **Non-goal:** Full workflow file reconstruction (steps section only)
|
||||
- **Non-goal:** Production action restriction enforcement (noted for later)
|
||||
|
||||
@@ -37,9 +37,17 @@ This transforms the debugger from a "read-only inspection tool" into an **intera
|
||||
### Grammar
|
||||
|
||||
```
|
||||
!step <command> [target] [options]
|
||||
steps <command> [target] [options] [--output text|json]
|
||||
```
|
||||
|
||||
### Output Format
|
||||
|
||||
All commands support the `--output` flag to control response format:
|
||||
- `--output text` (default) - Human-readable text output
|
||||
- `--output json` - JSON output for programmatic use
|
||||
- Short form: `-o json`, `-o text`
|
||||
- Equals form: `--output=json`, `--output=text`
|
||||
|
||||
### Index Reference
|
||||
|
||||
- **1-based indexing** for user-friendliness
|
||||
@@ -50,16 +58,16 @@ This transforms the debugger from a "read-only inspection tool" into an **intera
|
||||
|
||||
| Command | Purpose | Example |
|
||||
|---------|---------|---------|
|
||||
| `!step list` | Show all steps | `!step list --verbose` |
|
||||
| `!step add` | Add new step | `!step add run "npm test" --after 3` |
|
||||
| `!step edit` | Modify step | `!step edit 4 --script "npm run test:ci"` |
|
||||
| `!step remove` | Delete step | `!step remove 5` |
|
||||
| `!step move` | Reorder step | `!step move 5 --after 2` |
|
||||
| `!step export` | Generate YAML | `!step export --with-comments` |
|
||||
| `steps list` | Show all steps | `steps list --verbose` |
|
||||
| `steps add` | Add new step | `steps add run "npm test" --after 3` |
|
||||
| `steps edit` | Modify step | `steps edit 4 --script "npm run test:ci"` |
|
||||
| `steps remove` | Delete step | `steps remove 5` |
|
||||
| `steps move` | Reorder step | `steps move 5 --after 2` |
|
||||
| `steps export` | Generate YAML | `steps export --with-comments` |
|
||||
|
||||
### Position Modifiers
|
||||
|
||||
For `!step add` and `!step move`:
|
||||
For `steps add` and `steps move`:
|
||||
- `--at <index>` — Insert at specific position
|
||||
- `--after <index>` — Insert after step
|
||||
- `--before <index>` — Insert before step
|
||||
@@ -91,11 +99,16 @@ See "Command API Full Reference" section at end of document.
|
||||
```csharp
|
||||
public interface IStepCommandParser
|
||||
{
|
||||
StepCommand Parse(string input); // "!step add run \"echo hello\" --after 3"
|
||||
bool IsStepCommand(string input); // Starts with "!step" or is JSON with cmd:"step.*"
|
||||
StepCommand Parse(string input); // "steps add run \"echo hello\" --after 3"
|
||||
bool IsStepCommand(string input); // Starts with "steps " or equals "steps"
|
||||
}
|
||||
|
||||
public abstract class StepCommand { }
|
||||
public enum OutputFormat { Text, Json }
|
||||
|
||||
public abstract class StepCommand
|
||||
{
|
||||
public OutputFormat Output { get; set; } = OutputFormat.Text;
|
||||
}
|
||||
public class ListCommand : StepCommand { public bool Verbose; }
|
||||
public class AddRunCommand : StepCommand {
|
||||
public string Script;
|
||||
@@ -450,7 +463,7 @@ See "Command API Full Reference" section at end of document.
|
||||
|
||||
### Chunk 5: REPL Commands (run steps)
|
||||
|
||||
**Goal:** Implement `!step list`, `!step add run`, `!step edit`, `!step remove`, `!step move`.
|
||||
**Goal:** Implement `steps list`, `steps add run`, `steps edit`, `steps remove`, `steps move`.
|
||||
|
||||
**Files to modify:**
|
||||
- `src/Runner.Worker/Dap/DapDebugSession.cs` — Add `HandleStepCommandAsync()`
|
||||
@@ -557,9 +570,9 @@ See "Command API Full Reference" section at end of document.
|
||||
|
||||
---
|
||||
|
||||
### Chunk 6: Action Download Integration (!step add uses)
|
||||
### Chunk 6: Action Download Integration (steps add uses)
|
||||
|
||||
**Goal:** Support `!step add uses` with full action download.
|
||||
**Goal:** Support `steps add uses` with full action download.
|
||||
|
||||
**Files to modify:**
|
||||
- `src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs`
|
||||
@@ -628,7 +641,7 @@ See "Command API Full Reference" section at end of document.
|
||||
|
||||
---
|
||||
|
||||
### Chunk 7: Export Command (!step export)
|
||||
### Chunk 7: Export Command (steps export)
|
||||
|
||||
**Goal:** Generate YAML output for modified steps.
|
||||
|
||||
@@ -701,77 +714,93 @@ See "Command API Full Reference" section at end of document.
|
||||
|
||||
---
|
||||
|
||||
### Chunk 8: JSON API for Browser Extension
|
||||
### Chunk 8: Output Format Flag for Browser Extension
|
||||
|
||||
**Goal:** Add JSON command support for programmatic access.
|
||||
**Goal:** Add `--output` flag support for programmatic access (replaces separate JSON API).
|
||||
|
||||
**Files to modify:**
|
||||
- `src/Runner.Worker/Dap/StepCommands/StepCommandParser.cs`
|
||||
- `src/Runner.Worker/Dap/DapDebugSession.cs`
|
||||
- `src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs`
|
||||
|
||||
**Details:**
|
||||
|
||||
1. **Detect JSON input:**
|
||||
1. **Add OutputFormat enum and property to base StepCommand:**
|
||||
```csharp
|
||||
public bool IsStepCommand(string input)
|
||||
public enum OutputFormat { Text, Json }
|
||||
|
||||
public abstract class StepCommand
|
||||
{
|
||||
var trimmed = input.Trim();
|
||||
return trimmed.StartsWith("!step") ||
|
||||
(trimmed.StartsWith("{") && trimmed.Contains("\"cmd\"") && trimmed.Contains("\"step."));
|
||||
public OutputFormat Output { get; set; } = OutputFormat.Text;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Parse JSON commands:**
|
||||
2. **Parse `--output` flag in command parser:**
|
||||
```csharp
|
||||
public StepCommand Parse(string input)
|
||||
// Recognize --output json, --output text, -o json, -o text, --output=json
|
||||
private OutputFormat ParseOutputFlag(List<string> tokens)
|
||||
{
|
||||
var trimmed = input.Trim();
|
||||
if (trimmed.StartsWith("{"))
|
||||
return ParseJsonCommand(trimmed);
|
||||
else
|
||||
return ParseReplCommand(trimmed);
|
||||
}
|
||||
|
||||
private StepCommand ParseJsonCommand(string json)
|
||||
{
|
||||
var obj = JObject.Parse(json);
|
||||
var cmd = obj["cmd"]?.ToString();
|
||||
|
||||
return cmd switch
|
||||
for (int i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
"step.list" => new ListCommand { Verbose = obj["verbose"]?.Value<bool>() ?? false },
|
||||
"step.add" => ParseJsonAddCommand(obj),
|
||||
"step.edit" => ParseJsonEditCommand(obj),
|
||||
"step.remove" => new RemoveCommand { Index = obj["index"].Value<int>() },
|
||||
"step.move" => ParseJsonMoveCommand(obj),
|
||||
"step.export" => new ExportCommand {
|
||||
ChangesOnly = obj["changesOnly"]?.Value<bool>() ?? false,
|
||||
WithComments = obj["withComments"]?.Value<bool>() ?? false
|
||||
},
|
||||
_ => throw new StepCommandException($"Unknown command: {cmd}")
|
||||
var token = tokens[i].ToLower();
|
||||
if (token == "--output" || token == "-o")
|
||||
{
|
||||
if (i + 1 < tokens.Count)
|
||||
{
|
||||
var format = tokens[i + 1].ToLower();
|
||||
tokens.RemoveAt(i); tokens.RemoveAt(i);
|
||||
return format == "json" ? OutputFormat.Json : OutputFormat.Text;
|
||||
}
|
||||
}
|
||||
else if (token.StartsWith("--output="))
|
||||
{
|
||||
var format = token.Substring("--output=".Length);
|
||||
tokens.RemoveAt(i);
|
||||
return format == "json" ? OutputFormat.Json : OutputFormat.Text;
|
||||
}
|
||||
}
|
||||
return OutputFormat.Text;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Format responses based on Output property in handler:**
|
||||
```csharp
|
||||
if (command.Output == OutputFormat.Json)
|
||||
{
|
||||
return new StepCommandResult
|
||||
{
|
||||
Success = true,
|
||||
Message = JsonConvert.SerializeObject(new { Success = true, Result = data })
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new StepCommandResult
|
||||
{
|
||||
Success = true,
|
||||
Message = FormatAsText(data)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
3. **JSON response format:**
|
||||
```csharp
|
||||
// For JSON input, return structured JSON response
|
||||
if (wasJsonInput)
|
||||
{
|
||||
return CreateSuccessResponse(new EvaluateResponseBody
|
||||
{
|
||||
Result = JsonConvert.SerializeObject(result),
|
||||
Type = "json"
|
||||
});
|
||||
}
|
||||
4. **Browser extension sends REPL commands with `--output json`:**
|
||||
```javascript
|
||||
// Browser extension builds command strings like:
|
||||
// "steps list --output json"
|
||||
// "steps add run \"echo test\" --after 3 --output json"
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
- All commands via JSON
|
||||
- Verify JSON responses are parseable
|
||||
- Test error responses
|
||||
**Benefits over separate JSON API:**
|
||||
- Single code path for parsing all commands
|
||||
- Easier debugging (UI sends same commands humans would type)
|
||||
- Commands can be copy-pasted from UI to console for testing
|
||||
- Less code to maintain
|
||||
|
||||
**Estimated effort:** Small-medium
|
||||
**Testing:**
|
||||
- All commands with `--output json` return valid JSON
|
||||
- All commands with `--output text` (or default) return human-readable text
|
||||
- Short form `-o json` works correctly
|
||||
|
||||
**Estimated effort:** Small
|
||||
|
||||
---
|
||||
|
||||
@@ -845,13 +874,18 @@ See "Command API Full Reference" section at end of document.
|
||||
}
|
||||
```
|
||||
|
||||
6. **Send commands via JSON API:**
|
||||
6. **Send commands via REPL format with `--output json`:**
|
||||
```javascript
|
||||
async function addStep(type, options) {
|
||||
const cmd = { cmd: 'step.add', type, ...options };
|
||||
const response = await sendEvaluate(JSON.stringify(cmd));
|
||||
const cmd = buildStepCommand('step.add', { type, ...options });
|
||||
const response = await sendEvaluate(cmd); // e.g., "steps add run \"echo test\" --output json"
|
||||
refreshStepList();
|
||||
}
|
||||
|
||||
async function loadSteps() {
|
||||
const response = await sendEvaluate('steps list --output json');
|
||||
// Parse JSON response
|
||||
}
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
@@ -883,7 +917,9 @@ See "Command API Full Reference" section at end of document.
|
||||
| File | Chunk | Changes |
|
||||
|------|-------|---------|
|
||||
| `DapDebugSession.cs` | 1, 5 | Add command dispatch, wire up services |
|
||||
| `content.js` | 9 | Steps panel, dialogs, export modal |
|
||||
| `StepCommandParser.cs` | 8 | Add `--output` flag parsing |
|
||||
| `StepCommandHandler.cs` | 8 | Format responses based on OutputFormat |
|
||||
| `content.js` | 9 | Steps panel, dialogs, export modal, build REPL commands |
|
||||
| `content.css` | 9 | Styling for new UI elements |
|
||||
| `background.js` | 9 | Helper functions if needed |
|
||||
|
||||
@@ -891,10 +927,23 @@ See "Command API Full Reference" section at end of document.
|
||||
|
||||
## Command API Full Reference
|
||||
|
||||
### !step list
|
||||
### Output Format Flag
|
||||
|
||||
All commands support the `--output` flag:
|
||||
|
||||
```
|
||||
!step list [--verbose]
|
||||
steps <command> ... --output text # Human-readable output (default)
|
||||
steps <command> ... --output json # JSON output for programmatic use
|
||||
steps <command> ... -o json # Short form
|
||||
steps <command> ... --output=json # Equals form
|
||||
```
|
||||
|
||||
The browser extension uses `--output json` for all commands to receive structured responses.
|
||||
|
||||
### steps list
|
||||
|
||||
```
|
||||
steps list [--verbose] [--output text|json]
|
||||
```
|
||||
|
||||
Show all steps with their indices, status, and modification state.
|
||||
@@ -912,16 +961,25 @@ Steps:
|
||||
Legend: ✓ = completed, ▶ = current/paused, [ADDED] = new, [MODIFIED] = edited
|
||||
```
|
||||
|
||||
**JSON:**
|
||||
**JSON output (`steps list --output json`):**
|
||||
```json
|
||||
{"cmd": "step.list", "verbose": false}
|
||||
{
|
||||
"Success": true,
|
||||
"Result": [
|
||||
{"index": 1, "name": "Checkout", "type": "uses", "typeDetail": "actions/checkout@v4", "status": "completed"},
|
||||
{"index": 2, "name": "Setup Node", "type": "uses", "typeDetail": "actions/setup-node@v4", "status": "completed"},
|
||||
{"index": 3, "name": "Install deps", "type": "run", "typeDetail": "npm ci", "status": "current"},
|
||||
{"index": 4, "name": "Run tests", "type": "run", "typeDetail": "npm test", "status": "pending", "change": "MODIFIED"},
|
||||
{"index": 5, "name": "Build", "type": "run", "typeDetail": "npm run build", "status": "pending", "change": "ADDED"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### !step add
|
||||
### steps add
|
||||
|
||||
**Run step:**
|
||||
```
|
||||
!step add run "<script>" [options]
|
||||
steps add run "<script>" [options]
|
||||
|
||||
Options:
|
||||
--name "<name>" Display name
|
||||
@@ -936,11 +994,12 @@ Options:
|
||||
--before <index> Insert before step
|
||||
--first Insert at first pending position
|
||||
--last Insert at end (default)
|
||||
--output text|json Response format (default: text)
|
||||
```
|
||||
|
||||
**Uses step:**
|
||||
```
|
||||
!step add uses <action> [options]
|
||||
steps add uses <action> [options]
|
||||
|
||||
Options:
|
||||
--name "<name>" Display name
|
||||
@@ -950,50 +1009,138 @@ Options:
|
||||
--continue-on-error Don't fail job on step failure
|
||||
--timeout <minutes> Step timeout
|
||||
[position options same as run]
|
||||
--output text|json Response format (default: text)
|
||||
```
|
||||
|
||||
**JSON (run):**
|
||||
**Examples:**
|
||||
```bash
|
||||
# Human usage (text output)
|
||||
steps add run "npm test" --name "Run Tests" --after 3
|
||||
|
||||
# Browser extension (JSON output)
|
||||
steps add uses actions/setup-node@v4 --with node-version=20 --output json
|
||||
```
|
||||
|
||||
**JSON response (`--output json`):**
|
||||
```json
|
||||
{
|
||||
"cmd": "step.add",
|
||||
"type": "run",
|
||||
"script": "npm test",
|
||||
"name": "Run Tests",
|
||||
"shell": "bash",
|
||||
"workingDirectory": "src",
|
||||
"if": "success()",
|
||||
"env": {"NODE_ENV": "test"},
|
||||
"continueOnError": false,
|
||||
"timeout": 10,
|
||||
"position": {"after": 3}
|
||||
"Success": true,
|
||||
"Message": "Step added at position 4",
|
||||
"Result": {"index": 4, "name": "Setup Node", "type": "uses", "typeDetail": "actions/setup-node@v4", "status": "pending", "change": "ADDED"}
|
||||
}
|
||||
```
|
||||
|
||||
**JSON (uses):**
|
||||
**Position options:**
|
||||
- `--at 3` — Insert at position 3
|
||||
- `--after 2` — Insert after step 2
|
||||
- `--before 4` — Insert before step 4
|
||||
- `--first` — Insert at first pending position
|
||||
- `--last` — Insert at end (default if omitted)
|
||||
|
||||
### steps edit
|
||||
|
||||
```
|
||||
steps edit <index> [modifications]
|
||||
|
||||
Modifications:
|
||||
--name "<name>" Change display name
|
||||
--script "<script>" Change script (run only)
|
||||
--action "<action>" Change action (uses only)
|
||||
--shell <shell> Change shell (run only)
|
||||
--working-directory <path> Change working directory
|
||||
--if "<condition>" Change condition
|
||||
--with key=value Set/update input (uses only)
|
||||
--env KEY=value Set/update env var
|
||||
--remove-with <key> Remove input
|
||||
--remove-env <KEY> Remove env var
|
||||
--continue-on-error Enable continue-on-error
|
||||
--no-continue-on-error Disable continue-on-error
|
||||
--timeout <minutes> Change timeout
|
||||
--output text|json Response format (default: text)
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Human usage
|
||||
steps edit 4 --script "npm run test:ci" --name "CI Tests"
|
||||
|
||||
# Browser extension
|
||||
steps edit 4 --script "npm run test:ci" --output json
|
||||
```
|
||||
|
||||
**JSON response (`--output json`):**
|
||||
```json
|
||||
{
|
||||
"cmd": "step.add",
|
||||
"type": "uses",
|
||||
"action": "actions/setup-node@v4",
|
||||
"name": "Setup Node",
|
||||
"with": {"node-version": "20"},
|
||||
"env": {},
|
||||
"if": "success()",
|
||||
"position": {"at": 2}
|
||||
"Success": true,
|
||||
"Message": "Step 4 updated",
|
||||
"Result": {"index": 4, "name": "CI Tests", "type": "run", "typeDetail": "npm run test:ci", "status": "pending", "change": "MODIFIED"}
|
||||
}
|
||||
```
|
||||
|
||||
**Position object options:**
|
||||
```json
|
||||
{"at": 3} // Insert at position 3
|
||||
{"after": 2} // Insert after step 2
|
||||
{"before": 4} // Insert before step 4
|
||||
{"first": true} // Insert at first pending position
|
||||
{"last": true} // Insert at end (default if omitted)
|
||||
### steps remove
|
||||
|
||||
```
|
||||
steps remove <index> [--output text|json]
|
||||
```
|
||||
|
||||
### !step edit
|
||||
**Examples:**
|
||||
```bash
|
||||
steps remove 5
|
||||
steps remove 5 --output json
|
||||
```
|
||||
|
||||
**JSON response (`--output json`):**
|
||||
```json
|
||||
{"Success": true, "Message": "Step 5 removed"}
|
||||
```
|
||||
|
||||
### steps move
|
||||
|
||||
```
|
||||
steps move <from> <position> [--output text|json]
|
||||
|
||||
Position (one required):
|
||||
--to <index> Move to position
|
||||
--after <index> Move after step
|
||||
--before <index> Move before step
|
||||
--first Move to first pending position
|
||||
--last Move to end
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
steps move 5 --after 2
|
||||
steps move 5 --after 2 --output json
|
||||
```
|
||||
|
||||
**JSON response (`--output json`):**
|
||||
```json
|
||||
{"Success": true, "Message": "Step moved to position 3"}
|
||||
```
|
||||
|
||||
### steps export
|
||||
|
||||
```
|
||||
steps export [--changes-only] [--with-comments] [--output text|json]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
steps export --with-comments
|
||||
steps export --changes-only --output json
|
||||
```
|
||||
|
||||
**JSON response (`--output json`):**
|
||||
```json
|
||||
{
|
||||
"Success": true,
|
||||
"Result": {
|
||||
"yaml": "steps:\n - name: Checkout\n uses: actions/checkout@v4\n...",
|
||||
"totalSteps": 5,
|
||||
"addedCount": 1,
|
||||
"modifiedCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
!step edit <index> [modifications]
|
||||
|
||||
@@ -1098,12 +1245,12 @@ Position (one required):
|
||||
These command names are reserved for future implementation:
|
||||
|
||||
```
|
||||
!step duplicate <index> # Clone a step
|
||||
!step enable <index> # Re-enable a disabled step
|
||||
!step disable <index> # Skip step without removing
|
||||
!step inspect <index> # Show detailed step info
|
||||
!step reset <index> # Revert modifications
|
||||
!step import # Add steps from YAML
|
||||
steps duplicate <index> # Clone a step
|
||||
steps enable <index> # Re-enable a disabled step
|
||||
steps disable <index> # Skip step without removing
|
||||
steps inspect <index> # Show detailed step info
|
||||
steps reset <index> # Revert modifications
|
||||
steps import # Add steps from YAML
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user