Skip to content

Commit cc56be5

Browse files
committed
Experimental support for workflow job debugging
1 parent 1652e0b commit cc56be5

File tree

5 files changed

+464
-1
lines changed

5 files changed

+464
-1
lines changed

package.json

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@
6363
]
6464
}
6565
],
66+
"breakpoints": [
67+
{
68+
"language": "github-actions-workflow"
69+
}
70+
],
71+
"debuggers": [
72+
{
73+
"type": "github-actions",
74+
"label": "GitHub Actions",
75+
"languages": [
76+
"github-actions-workflow"
77+
],
78+
"configurationAttributes": {
79+
"attach": {
80+
"properties": {
81+
"workflowFile": {
82+
"type": "string",
83+
"description": "Path to the workflow file in the workspace"
84+
}
85+
}
86+
}
87+
},
88+
"initialConfigurations": []
89+
}
90+
],
6691
"configuration": {
6792
"title": "GitHub Actions",
6893
"properties": {
@@ -155,6 +180,13 @@
155180
"light": "resources/icons/light/logs.svg"
156181
}
157182
},
183+
{
184+
"command": "github-actions.workflow.job.attachDebugger",
185+
"category": "GitHub Actions",
186+
"title": "Attach debugger to job",
187+
"when": "viewItem =~ /job/",
188+
"icon": "$(debug)"
189+
},
158190
{
159191
"command": "github-actions.step.logs",
160192
"category": "GitHub Actions",
@@ -373,6 +405,11 @@
373405
"when": "viewItem =~ /run\\s/",
374406
"group": "inline"
375407
},
408+
{
409+
"command": "github-actions.workflow.job.attachDebugger",
410+
"group": "inline@0",
411+
"when": "viewItem =~ /job/ && viewItem =~ /running/"
412+
},
376413
{
377414
"command": "github-actions.workflow.logs",
378415
"group": "inline",
@@ -458,6 +495,10 @@
458495
"command": "github-actions.workflow.logs",
459496
"when": "false"
460497
},
498+
{
499+
"command": "github-actions.workflow.job.attachDebugger",
500+
"when": "false"
501+
},
461502
{
462503
"command": "github-actions.step.logs",
463504
"when": "false"
@@ -589,4 +630,4 @@
589630
"elliptic": "6.6.1"
590631
}
591632
}
592-
}
633+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as vscode from "vscode";
2+
import {GitHubRepoContext} from "../git/repository";
3+
import {logDebug, logError} from "../log";
4+
import {WorkflowJob} from "../store/WorkflowJob";
5+
import {getWorkflowUri} from "../workflow/workflow";
6+
import {WorkflowJobNode} from "../treeViews/shared/workflowJobNode";
7+
8+
export type AttachWorkflowJobDebuggerArgs = Pick<WorkflowJobNode, "gitHubRepoContext" | "job">;
9+
10+
const DEFAULT_DEBUG_PORT = 4711;
11+
12+
export function registerAttachWorkflowJobDebugger(context: vscode.ExtensionContext) {
13+
context.subscriptions.push(
14+
vscode.commands.registerCommand(
15+
"github-actions.workflow.job.attachDebugger",
16+
async (args: AttachWorkflowJobDebuggerArgs) => {
17+
const repoContext = args.gitHubRepoContext;
18+
const job = args.job;
19+
20+
const workflowFile = await resolveWorkflowFilePath(repoContext, job);
21+
if (!workflowFile) {
22+
await vscode.window.showWarningMessage(
23+
"Unable to locate the workflow file in the workspace. Debugging will attach without source mapping."
24+
);
25+
}
26+
27+
const debugConfig: vscode.DebugConfiguration = {
28+
name: `GitHub Actions: ${job.job.name}`,
29+
type: "github-actions",
30+
request: "attach",
31+
workflowFile,
32+
jobName: job.job.name,
33+
port: DEFAULT_DEBUG_PORT
34+
};
35+
36+
const folder = vscode.workspace.workspaceFolders?.[0];
37+
const started = await vscode.debug.startDebugging(folder, debugConfig);
38+
if (!started) {
39+
await vscode.window.showErrorMessage("Failed to start GitHub Actions debug session.");
40+
}
41+
}
42+
)
43+
);
44+
}
45+
46+
async function resolveWorkflowFilePath(repoContext: GitHubRepoContext, job: WorkflowJob): Promise<string | undefined> {
47+
const runId = job.job.run_id;
48+
49+
if (runId) {
50+
try {
51+
const runResponse = await repoContext.client.actions.getWorkflowRun({
52+
owner: repoContext.owner,
53+
repo: repoContext.name,
54+
run_id: runId
55+
});
56+
57+
const workflowPath = runResponse.data.path;
58+
if (workflowPath) {
59+
const uri = getWorkflowUri(repoContext, workflowPath);
60+
await vscode.workspace.fs.stat(uri);
61+
return uri.fsPath;
62+
}
63+
} catch (error) {
64+
logDebug("Unable to resolve workflow file from run data", (error as Error)?.message ?? error);
65+
}
66+
}
67+
68+
return findWorkflowFileInWorkspace();
69+
}
70+
71+
async function findWorkflowFileInWorkspace(): Promise<string | undefined> {
72+
const workspaceFolders = vscode.workspace.workspaceFolders;
73+
if (!workspaceFolders || workspaceFolders.length === 0) {
74+
return undefined;
75+
}
76+
77+
try {
78+
const workflowFiles = await vscode.workspace.findFiles(
79+
"**/.github/workflows/*.{yml,yaml}",
80+
"**/node_modules/**",
81+
200
82+
);
83+
84+
if (workflowFiles.length === 0) {
85+
return undefined;
86+
}
87+
88+
if (workflowFiles.length === 1) {
89+
return workflowFiles[0].fsPath;
90+
}
91+
92+
const picks = workflowFiles.map(uri => ({
93+
label: vscode.workspace.asRelativePath(uri),
94+
uri
95+
}));
96+
97+
const selection = await vscode.window.showQuickPick(picks, {
98+
placeHolder: "Select the workflow file to map debug sources"
99+
});
100+
101+
return selection?.uri.fsPath;
102+
} catch (error) {
103+
logError(error as Error, "Unable to find workflow files in workspace");
104+
return undefined;
105+
}
106+
}

0 commit comments

Comments
 (0)