Compare commits

..
Author SHA1 Message Date
Salman ChishtiandGitHub b500dbdf2d Remove duplicate test file and fix expression bypass
- Remove duplicate test file (errors-job-environment-deployment-disabled-feature.yml)
- Move deployment feature flag check before expression skip so
  deployment: ${{ ... }} is still gated by allowDeploymentKeyword
2026-03-18 15:51:16 +00:00
Salman ChishtiandGitHub 64aae8a102 Add deployment field to job environment
Add support for the 'deployment' boolean property under 'environment:' in
workflow YAML. When set to false, the job accesses environment protection
rules and secrets without creating a deployment record.

Changes:
- Add 'deployment' to job-environment-mapping schema (workflow-v1.0.json)
- Add 'deployment' to ActionsEnvironmentReference type
- Add feature-gated parsing in environment converter
- Add 'allowDeploymentKeyword' experimental feature flag
- Add xlang test data for enabled/disabled feature flag scenarios
2026-03-18 15:33:51 +00:00
16 changed files with 95 additions and 48 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/expressions",
"version": "0.3.48",
"version": "0.3.47",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
+2 -1
View File
@@ -56,7 +56,8 @@ describe("FeatureFlags", () => {
"blockScalarChompingWarning",
"allowCaseFunction",
"allowCronTimezone",
"allowCopilotRequestsPermission"
"allowCopilotRequestsPermission",
"allowDeploymentKeyword"
]);
});
});
+8 -1
View File
@@ -46,6 +46,12 @@ export interface ExperimentalFeatures {
* @default false
*/
allowCopilotRequestsPermission?: boolean;
/**
* Enable the deployment keyword in workflow job environment.
* @default false
*/
allowDeploymentKeyword?: boolean;
}
/**
@@ -62,7 +68,8 @@ const allFeatureKeys: ExperimentalFeatureKey[] = [
"blockScalarChompingWarning",
"allowCaseFunction",
"allowCronTimezone",
"allowCopilotRequestsPermission"
"allowCopilotRequestsPermission",
"allowDeploymentKeyword"
];
export class FeatureFlags {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageserver",
"version": "0.3.48",
"version": "0.3.47",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -48,8 +48,8 @@
"actions-languageserver": "./bin/actions-languageserver"
},
"dependencies": {
"@actions/languageservice": "^0.3.48",
"@actions/workflow-parser": "^0.3.48",
"@actions/languageservice": "^0.3.47",
"@actions/workflow-parser": "^0.3.47",
"@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.48",
"version": "0.3.47",
"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.48",
"@actions/workflow-parser": "^0.3.48",
"@actions/expressions": "^0.3.47",
"@actions/workflow-parser": "^0.3.47",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
-18
View File
@@ -368,24 +368,6 @@ jobs:
});
});
describe("environment deployment", () => {
it("allows deployment boolean under environment mapping", async () => {
const workflow = `
on: push
jobs:
build:
runs-on: ubuntu-latest
environment:
name: prod
deployment: false
steps:
- run: echo
`;
const result = await validate(createDocument("wf.yaml", workflow));
expect(result).toEqual([]);
});
});
describe("workflow_dispatch", () => {
it("allows empty string in choice options", async () => {
const result = await validate(
+1 -1
View File
@@ -6,5 +6,5 @@
"languageservice",
"languageserver"
],
"version": "0.3.48"
"version": "0.3.47"
}
+9 -9
View File
@@ -136,7 +136,7 @@
},
"expressions": {
"name": "@actions/expressions",
"version": "0.3.48",
"version": "0.3.47",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.0.3",
@@ -396,11 +396,11 @@
},
"languageserver": {
"name": "@actions/languageserver",
"version": "0.3.48",
"version": "0.3.47",
"license": "MIT",
"dependencies": {
"@actions/languageservice": "^0.3.48",
"@actions/workflow-parser": "^0.3.48",
"@actions/languageservice": "^0.3.47",
"@actions/workflow-parser": "^0.3.47",
"@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.48",
"version": "0.3.47",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.48",
"@actions/workflow-parser": "^0.3.48",
"@actions/expressions": "^0.3.47",
"@actions/workflow-parser": "^0.3.47",
"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.48",
"version": "0.3.47",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.48",
"@actions/expressions": "^0.3.47",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/workflow-parser",
"version": "0.3.48",
"version": "0.3.47",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -48,7 +48,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.48",
"@actions/expressions": "^0.3.47",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
@@ -1,3 +1,4 @@
import {FeatureFlags} from "@actions/expressions/features";
import {TemplateContext} from "../../../templates/template-context.js";
import {TemplateToken} from "../../../templates/tokens/template-token.js";
import {isScalar} from "../../../templates/tokens/type-guards.js";
@@ -22,6 +23,18 @@ export function convertToActionsEnvironmentRef(
for (const property of environmentMapping) {
const propertyName = property.key.assertString("job environment key");
// Check deployment feature flag before skipping expressions,
// so deployment: ${{ ... }} is still gated by the flag
if (propertyName.value === "deployment") {
const featureFlags = context.state["featureFlags"] as FeatureFlags | undefined;
const flags = featureFlags ?? new FeatureFlags();
if (!flags.isEnabled("allowDeploymentKeyword")) {
context.error(property.key, `The key 'deployment' is not allowed`);
continue;
}
}
if (property.key.isExpression || property.value.isExpression) {
continue;
}
@@ -35,13 +48,9 @@ export function convertToActionsEnvironmentRef(
result.url = property.value;
break;
case "deployment": {
const deploymentValue = property.value.assertBoolean("job environment deployment");
if (deploymentValue.value === false) {
result.skipDeployment = true;
}
case "deployment":
result.deployment = property.value;
break;
}
}
}
@@ -26,7 +26,7 @@ export type ConcurrencySetting = {
export type ActionsEnvironmentReference = {
name?: TemplateToken;
url?: TemplateToken;
skipDeployment?: boolean;
deployment?: TemplateToken;
};
export type WorkflowJob = Job | ReusableWorkflowJob;
+1 -1
View File
@@ -2082,7 +2082,7 @@
},
"deployment": {
"type": "boolean",
"description": "Whether to create a deployment record for this environment. Defaults to true."
"description": "Whether to create a deployment for this environment. Set to `false` to access environment secrets and variables without creating a deployment record. Defaults to `true`."
}
}
}
+6 -1
View File
@@ -1,6 +1,7 @@
import * as fs from "fs";
import * as path from "path";
import * as YAML from "yaml";
import {FeatureFlags} from "@actions/expressions/features";
import {convertWorkflowTemplate} from "./model/convert.js";
import {NoOperationTraceWriter} from "./templates/trace-writer.js";
import {File} from "./workflows/file.js";
@@ -10,6 +11,7 @@ import {parseWorkflow} from "./workflows/workflow-parser.js";
interface TestOptions {
"include-source"?: boolean;
"allow-deployment-keyword"?: boolean;
skip?: string[];
}
@@ -85,7 +87,10 @@ describe("x-lang tests", () => {
parseResult.value!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
testFileProvider,
{
fetchReusableWorkflowDepth: 1
fetchReusableWorkflowDepth: 1,
featureFlags: new FeatureFlags({
allowDeploymentKeyword: testOptions["allow-deployment-keyword"]
})
}
);
@@ -0,0 +1,21 @@
include-source: false # Drop file/line/col from output
skip:
- C#
---
on: push
jobs:
build:
environment:
name: production
deployment: false
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"errors": [
{
"Message": ".github/workflows/errors-job-environment-deployment-disabled-feature-default-go.yml (Line: 6, Col: 7): The key 'deployment' is not allowed"
}
]
}
@@ -0,0 +1,21 @@
include-source: false # Drop file/line/col from output
skip:
- C#
---
on: push
jobs:
build:
environment:
name: production
deployment: false
runs-on: ubuntu-latest
steps:
- run: echo hi
---
{
"errors": [
{
"Message": ".github/workflows/errors-job-environment-deployment-disabled-feature.yml (Line: 6, Col: 7): The key 'deployment' is not allowed"
}
]
}
@@ -1,6 +1,7 @@
include-source: false # Drop file/line/col from output
skip:
- C#
allow-deployment-keyword: true
---
on: push
jobs: