Compare commits

..

13 Commits

Author SHA1 Message Date
Salman Chishti 77ed325c44 Merge pull request #361 from actions/release/0.3.54
Build & Test / build (20.x) (push) Has been cancelled
Build & Test / build (22.x) (push) Has been cancelled
Build & Test / build (24.x) (push) Has been cancelled
Build & Test / check-generated (push) Has been cancelled
Release version 0.3.54
2026-04-21 20:25:41 +01:00
GitHub Actions 1372d6dec7 Release extension version 0.3.54 2026-04-21 19:11:32 +00:00
Salman Chishti a06de82217 Merge pull request #356 from actions/vulnerability-alerts-permission
Add vulnerability-alerts permission to workflow schema
2026-04-21 20:04:10 +01:00
eric sciple 36b909a32d Revert "Add field_added and field_removed issue event types to workflows (#351)" (#360) 2026-04-16 13:10:37 -05:00
Armağan 9a8a94bd21 Add field_added and field_removed issue event types to workflows (#351) 2026-04-16 10:55:56 -05:00
github-actions[bot] 8aa246e9d9 Release extension version 0.3.53 (#359)
Co-authored-by: GitHub Actions <github-actions@github.com>
2026-04-15 15:16:47 -05:00
Jason Ginchereau ffc3778653 Add concurrency queue support (#355) 2026-04-15 14:36:13 -05:00
Salman Muin Kayser Chishti 38f730cdce Add vulnerability-alerts permission to workflow schema
Add vulnerability-alerts as a new read-only permission key in the
permissions-mapping. This permission allows workflows to read
Dependabot alerts via GITHUB_TOKEN.

Uses permission-level-read-or-no-access type (read and none only).
Updated security-events description to reflect it covers code
scanning alerts only.
2026-04-15 02:40:02 +00:00
Salman Chishti a810405967 Merge pull request #354 from actions/release/0.3.52
Release version 0.3.52
2026-04-14 15:39:15 +01:00
GitHub Actions 840d04cea8 Release extension version 0.3.52 2026-04-14 14:27:30 +00:00
Salman Chishti 0446b065b0 Merge pull request #348 from actions/salmanmkc/job-workflow-context-properties
feat: add job.workflow_* context properties
2026-04-14 13:45:08 +01:00
Salman Muin Kayser Chishti 763dff2018 fix: address review nits - update doc comments, test names, and description wording
- Update getJobContext doc comment to include workflow identity fields
- Rename test to reflect all returned fields, not just status/check_run_id
- Rename validate test to 'job.workflow_* fields' covering all 4 properties
- Clarify workflow_ref description: 'ref path to' instead of 'ref of'
2026-04-14 10:55:58 +00:00
Salman Muin Kayser Chishti 0c9d817440 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
2026-04-10 22:23:20 +01:00
21 changed files with 403 additions and 70 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/expressions",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
+3 -1
View File
@@ -25,6 +25,7 @@ describe("FeatureFlags", () => {
it("returns true when all is enabled", () => {
const flags = new FeatureFlags({all: true});
expect(flags.isEnabled("missingInputsQuickfix")).toBe(true);
expect(flags.isEnabled("allowConcurrencyQueue")).toBe(true);
});
it("explicit feature flag takes precedence over all:true", () => {
@@ -55,7 +56,8 @@ describe("FeatureFlags", () => {
"missingInputsQuickfix",
"blockScalarChompingWarning",
"allowCaseFunction",
"allowCopilotRequestsPermission"
"allowCopilotRequestsPermission",
"allowConcurrencyQueue"
]);
});
});
+8 -1
View File
@@ -40,6 +40,12 @@ export interface ExperimentalFeatures {
* @default false
*/
allowCopilotRequestsPermission?: boolean;
/**
* Enable the queue property in workflow concurrency settings.
* @default false
*/
allowConcurrencyQueue?: boolean;
}
/**
@@ -55,7 +61,8 @@ const allFeatureKeys: ExperimentalFeatureKey[] = [
"missingInputsQuickfix",
"blockScalarChompingWarning",
"allowCaseFunction",
"allowCopilotRequestsPermission"
"allowCopilotRequestsPermission",
"allowConcurrencyQueue"
];
export class FeatureFlags {
+1
View File
@@ -127,6 +127,7 @@ initializationOptions: {
|---------|-------------|
| `missingInputsQuickfix` | Code action to add missing required inputs for actions |
| `blockScalarChompingWarning` | Warn when block scalars (`\|` or `>`) use implicit clip chomping, which adds a trailing newline that may be unintentional |
| `allowConcurrencyQueue` | Enable the `concurrency.queue` workflow property |
Individual feature flags take precedence over `all`. For example, `{ all: true, missingInputsQuickfix: false }` enables all experimental features except `missingInputsQuickfix`.
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageserver",
"version": "0.3.51",
"version": "0.3.54",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -48,8 +48,8 @@
"actions-languageserver": "./bin/actions-languageserver"
},
"dependencies": {
"@actions/languageservice": "^0.3.51",
"@actions/workflow-parser": "^0.3.51",
"@actions/languageservice": "^0.3.54",
"@actions/workflow-parser": "^0.3.54",
"@octokit/rest": "^21.1.1",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageservice",
"version": "0.3.51",
"version": "0.3.54",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -47,8 +47,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.51",
"@actions/workflow-parser": "^0.3.51",
"@actions/expressions": "^0.3.54",
"@actions/workflow-parser": "^0.3.54",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
@@ -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 () => {
-39
View File
@@ -1016,45 +1016,6 @@ jobs:
});
});
describe("permissions workflows completion", () => {
it("includes workflows in top-level permissions", async () => {
const input = `on: push
permissions:
|`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
const labels = result.map(x => x.label);
expect(labels).toContain("workflows");
});
it("offers only write and none for workflows", async () => {
const input = `on: push
permissions:
workflows: |`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
const labels = result.map(x => x.label);
expect(labels).toContain("write");
expect(labels).not.toContain("read");
});
it("includes workflows in job-level permissions", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
permissions:
|`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
const labels = result.map(x => x.label);
expect(labels).toContain("workflows");
});
});
describe("service container command/entrypoint completion", () => {
it("suggests entrypoint and command in service container", async () => {
const input = `on: push
@@ -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 path to 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": {
@@ -18,12 +18,16 @@ describe("job context", () => {
expect(context.pairs().length).toBe(0);
});
it("returns status and check_run_id when job has no container or services", () => {
it("returns status, check_run_id, and workflow fields when job has no container or services", () => {
const workflowContext = {job: {}} as WorkflowContext;
const context = getJobContext(workflowContext);
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();
});
});
});
+7 -1
View File
@@ -5,7 +5,7 @@ import {WorkflowContext} from "../context/workflow-context.js";
import {getDescription} from "./descriptions.js";
/**
* Returns the job context with container, services, status, and check_run_id.
* Returns the job context with container, services, status, check_run_id, and workflow identity fields.
*/
export function getJobContext(workflowContext: WorkflowContext): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#job-context
@@ -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;
}
@@ -1,3 +1,4 @@
import {FeatureFlags} from "@actions/expressions/features";
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {validate} from "./validate.js";
import {createDocument} from "./test-utils/document.js";
@@ -7,6 +8,10 @@ beforeEach(() => {
clearCache();
});
const queueValidationConfig = {
featureFlags: new FeatureFlags({allowConcurrencyQueue: true})
};
describe("validate concurrency deadlock", () => {
describe("should error on matching concurrency groups", () => {
it("simple string match", async () => {
@@ -243,3 +248,186 @@ jobs:
});
});
});
describe("validate concurrency queue + cancel-in-progress conflict", () => {
describe("should error", () => {
it("workflow-level queue: max with cancel-in-progress: true", async () => {
const input = `
on: push
concurrency:
group: deploy
cancel-in-progress: true
queue: max
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input), queueValidationConfig);
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(1);
expect(queueErrors[0]).toMatchObject({
message: "'queue: max' cannot be combined with 'cancel-in-progress: true'.",
severity: DiagnosticSeverity.Error
});
});
it("job-level queue: max with cancel-in-progress: true", async () => {
const input = `
on: push
jobs:
job1:
runs-on: ubuntu-latest
concurrency:
group: deploy
cancel-in-progress: true
queue: max
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input), queueValidationConfig);
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(1);
expect(queueErrors[0]).toMatchObject({
severity: DiagnosticSeverity.Error
});
});
it("both workflow and job level have the conflict", async () => {
const input = `
on: push
concurrency:
group: deploy
cancel-in-progress: true
queue: max
jobs:
job1:
runs-on: ubuntu-latest
concurrency:
group: build
cancel-in-progress: true
queue: max
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input), queueValidationConfig);
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(2);
});
});
describe("should not error", () => {
it("queue: max without cancel-in-progress", async () => {
const input = `
on: push
concurrency:
group: deploy
queue: max
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input));
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(0);
});
it("queue: single with cancel-in-progress: true", async () => {
const input = `
on: push
concurrency:
group: deploy
cancel-in-progress: true
queue: single
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input));
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(0);
});
it("cancel-in-progress: false with queue: max", async () => {
const input = `
on: push
concurrency:
group: deploy
cancel-in-progress: false
queue: max
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input));
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(0);
});
it("no queue property", async () => {
const input = `
on: push
concurrency:
group: deploy
cancel-in-progress: true
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input));
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(0);
});
it("string form concurrency (no mapping)", async () => {
const input = `
on: push
concurrency: deploy
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input));
const queueErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueErrors).toHaveLength(0);
});
it("does not report queue conflict when the feature is disabled", async () => {
const input = `
on: push
concurrency:
group: deploy
cancel-in-progress: true
queue: max
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo hi`;
const result = await validate(createDocument("wf.yaml", input));
const queueConflictErrors = result.filter(d => d.message.includes("queue: max"));
expect(queueConflictErrors).toHaveLength(0);
});
});
});
@@ -432,6 +432,24 @@ jobs:
expect(result).toEqual([]);
});
it("job.workflow_* fields", 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.<service_id>", async () => {
const input = `
on: push
+62 -1
View File
@@ -1,6 +1,13 @@
import {FeatureFlags, Lexer, Parser} from "@actions/expressions";
import {Expr} from "@actions/expressions/ast";
import {TemplateParseResult, WorkflowTemplate, isBasicExpression, isMapping, isString} from "@actions/workflow-parser";
import {
TemplateParseResult,
WorkflowTemplate,
isBasicExpression,
isBoolean,
isMapping,
isString
} from "@actions/workflow-parser";
import {ErrorPolicy} from "@actions/workflow-parser/model/convert";
import {getCronDescription, hasCronIntervalLessThan5Minutes} from "@actions/workflow-parser/model/converter/cron";
import {ensureStatusFunction} from "@actions/workflow-parser/model/converter/if-condition";
@@ -239,6 +246,11 @@ async function additionalValidations(
// Validate concurrency deadlock between workflow and job levels
validateConcurrencyDeadlock(diagnostics, template);
// Validate incompatible concurrency options
if (featureFlags?.isEnabled("allowConcurrencyQueue")) {
validateConcurrencyQueueCancelInProgress(diagnostics, template);
}
}
function invalidValue(diagnostics: Diagnostic[], token: StringToken, kind: ValueProviderKind) {
@@ -664,6 +676,55 @@ function validateConcurrencyDeadlock(diagnostics: Diagnostic[], template: Workfl
}
}
/**
* Validates that `queue: max` and `cancel-in-progress: true` are not both set
* in a concurrency mapping, as this combination is invalid.
*/
function validateConcurrencyQueueCancelInProgress(diagnostics: Diagnostic[], template: WorkflowTemplate): void {
// Check workflow-level concurrency
if (template.concurrency) {
checkConcurrencyQueueConflict(diagnostics, template.concurrency);
}
// Check job-level concurrency
for (const job of template.jobs || []) {
if (job.concurrency) {
checkConcurrencyQueueConflict(diagnostics, job.concurrency);
}
}
}
function checkConcurrencyQueueConflict(diagnostics: Diagnostic[], token: TemplateToken): void {
if (!isMapping(token)) {
return;
}
let hasQueueMax = false;
let hasCancelInProgressTrue = false;
let queueRange: TokenRange | undefined;
for (const pair of token) {
if (!isString(pair.key) || pair.key.isExpression || pair.value.isExpression) {
continue;
}
if (pair.key.value === "queue" && isString(pair.value) && pair.value.value === "max") {
hasQueueMax = true;
queueRange = pair.key.range;
}
if (pair.key.value === "cancel-in-progress" && isBoolean(pair.value) && pair.value.value) {
hasCancelInProgressTrue = true;
}
}
if (hasQueueMax && hasCancelInProgressTrue && queueRange) {
diagnostics.push({
message: "'queue: max' cannot be combined with 'cancel-in-progress: true'.",
range: mapRange(queueRange),
severity: DiagnosticSeverity.Error
});
}
}
/**
* Extracts the static concurrency group name from a concurrency token.
* Returns undefined if the token is an expression or doesn't have a static group.
+1 -1
View File
@@ -6,5 +6,5 @@
"languageservice",
"languageserver"
],
"version": "0.3.51"
"version": "0.3.54"
}
+9 -9
View File
@@ -136,7 +136,7 @@
},
"expressions": {
"name": "@actions/expressions",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.0.3",
@@ -396,11 +396,11 @@
},
"languageserver": {
"name": "@actions/languageserver",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"dependencies": {
"@actions/languageservice": "^0.3.51",
"@actions/workflow-parser": "^0.3.51",
"@actions/languageservice": "^0.3.54",
"@actions/workflow-parser": "^0.3.54",
"@octokit/rest": "^21.1.1",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
@@ -927,11 +927,11 @@
},
"languageservice": {
"name": "@actions/languageservice",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.51",
"@actions/workflow-parser": "^0.3.51",
"@actions/expressions": "^0.3.54",
"@actions/workflow-parser": "^0.3.54",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
@@ -14020,10 +14020,10 @@
},
"workflow-parser": {
"name": "@actions/workflow-parser",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.51",
"@actions/expressions": "^0.3.54",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/workflow-parser",
"version": "0.3.51",
"version": "0.3.54",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -48,7 +48,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.51",
"@actions/expressions": "^0.3.54",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
@@ -1,10 +1,12 @@
import type {FeatureFlags} from "@actions/expressions/features";
import {TemplateContext} from "../../templates/template-context.js";
import {TemplateToken} from "../../templates/tokens/template-token.js";
import {isString} from "../../templates/tokens/type-guards.js";
import {ConcurrencySetting} from "../workflow-template.js";
import {ConcurrencyQueue, ConcurrencySetting} from "../workflow-template.js";
export function convertConcurrency(context: TemplateContext, token: TemplateToken): ConcurrencySetting {
const result: ConcurrencySetting = {};
const featureFlags = context.state.featureFlags as FeatureFlags | undefined;
if (token.isExpression) {
return result;
@@ -26,6 +28,11 @@ export function convertConcurrency(context: TemplateContext, token: TemplateToke
case "cancel-in-progress":
result.cancelInProgress = property.value.assertBoolean("cancel-in-progress").value;
break;
case "queue":
if (featureFlags?.isEnabled("allowConcurrencyQueue")) {
result.queue = property.value.assertString("queue").value as ConcurrencyQueue;
}
break;
default:
context.error(propertyName, `Invalid property name: ${propertyName.value}`);
}
@@ -18,9 +18,12 @@ export type WorkflowTemplate = {
}[];
};
export type ConcurrencyQueue = "single" | "max";
export type ConcurrencySetting = {
group?: StringToken;
cancelInProgress?: boolean;
queue?: ConcurrencyQueue;
};
export type ActionsEnvironmentReference = {
+14 -4
View File
@@ -1644,15 +1644,15 @@
},
"security-events": {
"type": "permission-level-any",
"description": "Code scanning and Dependabot alerts."
"description": "Code scanning alerts."
},
"statuses": {
"type": "permission-level-any",
"description": "Commit statuses."
},
"workflows": {
"type": "permission-level-write-or-no-access",
"description": "Update GitHub Actions workflow files."
"vulnerability-alerts": {
"type": "permission-level-read-or-no-access",
"description": "Dependabot alerts."
}
}
}
@@ -2054,10 +2054,20 @@
"cancel-in-progress": {
"type": "boolean",
"description": "To cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true."
},
"queue": {
"type": "concurrency-queue",
"description": "The queuing mode for the concurrency group. When set to `max`, workflows or jobs will wait in a queue for the concurrency group up to the maximum queue length. Default: `single` meaning at most one item can be pending."
}
}
}
},
"concurrency-queue": {
"allowed-values": [
"single",
"max"
]
},
"job-environment": {
"description": "The environment that the job references. All environment protection rules must pass before a job referencing the environment is sent to a runner.",
"context": [
+28 -1
View File
@@ -25,7 +25,11 @@ jobs:
concurrency:
group: ref
cancel-in-progress: ${{ github.ref }}
build5:
runs-on: ubuntu-latest
concurrency:
group: deploy
queue: max
---
{
@@ -141,6 +145,29 @@ jobs:
]
},
"runs-on": "macos-latest"
},
{
"type": "job",
"id": "build5",
"name": "build5",
"if": {
"type": 3,
"expr": "success()"
},
"concurrency": {
"type": 2,
"map": [
{
"Key": "group",
"Value": "deploy"
},
{
"Key": "queue",
"Value": "max"
}
]
},
"runs-on": "ubuntu-latest"
}
]
}