From 0c9d8174401ee1247c203e0c4b0667de9c62e296 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Fri, 10 Apr 2026 22:23:20 +0100 Subject: [PATCH] feat: add job.workflow_* context properties Add workflow_ref, workflow_sha, workflow_repository, and workflow_file_path to the job context for reusable workflow jobs. These fields provide direct access to the workflow file information without needing to parse github.workflow_ref. - Add 4 new fields to getJobContext() in job.ts - Add descriptions in descriptions.json - Update autocomplete test expectations - Add validation and unit tests --- .../src/complete.expressions.test.ts | 11 +++++++++- .../src/context-providers/descriptions.json | 12 +++++++++++ .../src/context-providers/job.test.ts | 21 +++++++++++++++++++ languageservice/src/context-providers/job.ts | 6 ++++++ .../src/validate.expressions.test.ts | 18 ++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/languageservice/src/complete.expressions.test.ts b/languageservice/src/complete.expressions.test.ts index 0a2f276..09767c8 100644 --- a/languageservice/src/complete.expressions.test.ts +++ b/languageservice/src/complete.expressions.test.ts @@ -1164,7 +1164,16 @@ jobs: `; const result = await complete(...getPositionFromCursor(input), {contextProviderConfig}); - expect(result.map(x => x.label)).toEqual(["check_run_id", "container", "services", "status"]); + expect(result.map(x => x.label)).toEqual([ + "check_run_id", + "container", + "services", + "status", + "workflow_file_path", + "workflow_ref", + "workflow_repository", + "workflow_sha" + ]); }); it("job context is suggested within a job output", async () => { diff --git a/languageservice/src/context-providers/descriptions.json b/languageservice/src/context-providers/descriptions.json index b43af22..80364ab 100644 --- a/languageservice/src/context-providers/descriptions.json +++ b/languageservice/src/context-providers/descriptions.json @@ -218,6 +218,18 @@ }, "check_run_id": { "description": "The unique identifier of the check run for this job." + }, + "workflow_file_path": { + "description": "The path of the workflow file that contains the job. For example, `.github/workflows/my-workflow.yml`." + }, + "workflow_ref": { + "description": "The ref of the workflow file that contains the job. For example, `octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch`." + }, + "workflow_repository": { + "description": "The owner and repository name of the workflow file that contains the job. For example, `octocat/Hello-World`." + }, + "workflow_sha": { + "description": "The commit SHA of the workflow file that contains the job." } }, "secrets": { diff --git a/languageservice/src/context-providers/job.test.ts b/languageservice/src/context-providers/job.test.ts index 4f16596..4a768f5 100644 --- a/languageservice/src/context-providers/job.test.ts +++ b/languageservice/src/context-providers/job.test.ts @@ -24,6 +24,10 @@ describe("job context", () => { expect(context.get("status")).toBeDefined(); expect(context.get("check_run_id")).toBeDefined(); + expect(context.get("workflow_ref")).toBeDefined(); + expect(context.get("workflow_sha")).toBeDefined(); + expect(context.get("workflow_repository")).toBeDefined(); + expect(context.get("workflow_file_path")).toBeDefined(); expect(context.get("container")).toBeUndefined(); expect(context.get("services")).toBeUndefined(); }); @@ -173,4 +177,21 @@ describe("job context", () => { expect(redis.getDescription("ports")).toBeDefined(); }); }); + + describe("workflow context fields", () => { + it("includes workflow context fields with descriptions", () => { + const workflowContext = {job: {}} as WorkflowContext; + const context = getJobContext(workflowContext); + + expect(context.get("workflow_ref")).toBeDefined(); + expect(context.get("workflow_sha")).toBeDefined(); + expect(context.get("workflow_repository")).toBeDefined(); + expect(context.get("workflow_file_path")).toBeDefined(); + + expect(context.getDescription("workflow_ref")).toBeDefined(); + expect(context.getDescription("workflow_sha")).toBeDefined(); + expect(context.getDescription("workflow_repository")).toBeDefined(); + expect(context.getDescription("workflow_file_path")).toBeDefined(); + }); + }); }); diff --git a/languageservice/src/context-providers/job.ts b/languageservice/src/context-providers/job.ts index d084667..0a7bbc2 100644 --- a/languageservice/src/context-providers/job.ts +++ b/languageservice/src/context-providers/job.ts @@ -42,6 +42,12 @@ export function getJobContext(workflowContext: WorkflowContext): DescriptionDict // Check run ID jobContext.add("check_run_id", new data.StringData(""), getDescription("job", "check_run_id")); + // Workflow context fields (populated at runtime for reusable workflow jobs) + jobContext.add("workflow_file_path", new data.StringData(""), getDescription("job", "workflow_file_path")); + jobContext.add("workflow_ref", new data.StringData(""), getDescription("job", "workflow_ref")); + jobContext.add("workflow_repository", new data.StringData(""), getDescription("job", "workflow_repository")); + jobContext.add("workflow_sha", new data.StringData(""), getDescription("job", "workflow_sha")); + return jobContext; } diff --git a/languageservice/src/validate.expressions.test.ts b/languageservice/src/validate.expressions.test.ts index 3530290..903d65c 100644 --- a/languageservice/src/validate.expressions.test.ts +++ b/languageservice/src/validate.expressions.test.ts @@ -432,6 +432,24 @@ jobs: expect(result).toEqual([]); }); + it("job.workflow_ref", async () => { + const input = ` +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo \${{ job.workflow_ref }} + - run: echo \${{ job.workflow_sha }} + - run: echo \${{ job.workflow_repository }} + - run: echo \${{ job.workflow_file_path }} +`; + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([]); + }); + it("job.services.", async () => { const input = ` on: push