Remove allowServiceContainerCommand feature flag (#345)

Service container entrypoint/command support is now unconditional.
This commit is contained in:
eric sciple
2026-04-03 10:29:34 -05:00
committed by GitHub
parent 9e1662f1d4
commit f62a0e189d
5 changed files with 66 additions and 90 deletions
+33
View File
@@ -0,0 +1,33 @@
# Agents
## Build
```
npx lerna run build
```
## Test
```
npm -w @actions/expressions test
npm -w @actions/workflow-parser test
npm -w @actions/languageservice test
```
## Format
Always run formatting before committing:
```
npx prettier --write <changed files>
```
Verify with:
```
npm run format-check -ws
```
## Feature flags
Feature flags are defined in `expressions/src/features.ts` (`ExperimentalFeatures` interface + `allFeatureKeys` array). They are plumbed through `ConvertOptions`, `CompletionConfig`, `ValidationConfig`, and `initializationOptions`. When a feature graduates to stable, remove its flag and make the behavior unconditional.
+1 -2
View File
@@ -55,8 +55,7 @@ describe("FeatureFlags", () => {
"missingInputsQuickfix",
"blockScalarChompingWarning",
"allowCaseFunction",
"allowCopilotRequestsPermission",
"allowServiceContainerCommand"
"allowCopilotRequestsPermission"
]);
});
});
+1 -8
View File
@@ -40,12 +40,6 @@ export interface ExperimentalFeatures {
* @default false
*/
allowCopilotRequestsPermission?: boolean;
/**
* Enable `entrypoint` and `command` keys in service containers (`jobs.<job_id>.services.*`).
* @default false
*/
allowServiceContainerCommand?: boolean;
}
/**
@@ -61,8 +55,7 @@ const allFeatureKeys: ExperimentalFeatureKey[] = [
"missingInputsQuickfix",
"blockScalarChompingWarning",
"allowCaseFunction",
"allowCopilotRequestsPermission",
"allowServiceContainerCommand"
"allowCopilotRequestsPermission"
];
export class FeatureFlags {
@@ -1,24 +1,18 @@
import {FeatureFlags} from "@actions/expressions";
import {registerLogger} from "./log.js";
import {createDocument} from "./test-utils/document.js";
import {TestLogger} from "./test-utils/logger.js";
import {clearCache} from "./utils/workflow-cache.js";
import {validate, ValidationConfig} from "./validate.js";
import {validate} from "./validate.js";
registerLogger(new TestLogger());
const configWithFlag: ValidationConfig = {
featureFlags: new FeatureFlags({allowServiceContainerCommand: true})
};
beforeEach(() => {
clearCache();
});
describe("service container command/entrypoint", () => {
describe("with feature flag enabled", () => {
it("allows command in service container", async () => {
const input = `
it("allows command in service container", async () => {
const input = `
on: push
jobs:
build:
@@ -30,13 +24,13 @@ jobs:
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
const commandErrors = result.filter(d => d.message.includes("command"));
expect(commandErrors).toEqual([]);
});
const result = await validate(createDocument("wf.yaml", input));
const commandErrors = result.filter(d => d.message.includes("command"));
expect(commandErrors).toEqual([]);
});
it("allows entrypoint in service container", async () => {
const input = `
it("allows entrypoint in service container", async () => {
const input = `
on: push
jobs:
build:
@@ -48,13 +42,13 @@ jobs:
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
expect(entrypointErrors).toEqual([]);
});
const result = await validate(createDocument("wf.yaml", input));
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
expect(entrypointErrors).toEqual([]);
});
it("allows both command and entrypoint in service container", async () => {
const input = `
it("allows both command and entrypoint in service container", async () => {
const input = `
on: push
jobs:
build:
@@ -67,13 +61,13 @@ jobs:
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
const relevantErrors = result.filter(d => d.message.includes("command") || d.message.includes("entrypoint"));
expect(relevantErrors).toEqual([]);
});
const result = await validate(createDocument("wf.yaml", input));
const relevantErrors = result.filter(d => d.message.includes("command") || d.message.includes("entrypoint"));
expect(relevantErrors).toEqual([]);
});
it("rejects command in job container even with flag enabled", async () => {
const input = `
it("rejects command in job container", async () => {
const input = `
on: push
jobs:
build:
@@ -84,13 +78,13 @@ jobs:
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
const commandErrors = result.filter(d => d.message.includes("command"));
expect(commandErrors.length).toBeGreaterThan(0);
});
const result = await validate(createDocument("wf.yaml", input));
const commandErrors = result.filter(d => d.message.includes("command"));
expect(commandErrors.length).toBeGreaterThan(0);
});
it("rejects entrypoint in job container even with flag enabled", async () => {
const input = `
it("rejects entrypoint in job container", async () => {
const input = `
on: push
jobs:
build:
@@ -101,47 +95,8 @@ jobs:
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
expect(entrypointErrors.length).toBeGreaterThan(0);
});
});
describe("with feature flag disabled", () => {
it("rejects command in service container", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
redis:
image: redis
command: --port 6380
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
const commandErrors = result.filter(d => d.message.includes("command"));
expect(commandErrors.length).toBeGreaterThan(0);
});
it("rejects entrypoint in service container", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
services:
redis:
image: redis
entrypoint: /usr/local/bin/redis-server
steps:
- run: echo hi
`;
const result = await validate(createDocument("wf.yaml", input));
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
expect(entrypointErrors.length).toBeGreaterThan(0);
});
const result = await validate(createDocument("wf.yaml", input));
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
expect(entrypointErrors.length).toBeGreaterThan(0);
});
});
@@ -146,15 +146,11 @@ export function convertToServiceContainer(context: TemplateContext, container: T
export function convertToJobServices(context: TemplateContext, services: TemplateToken): Container[] | undefined {
const serviceList: Container[] = [];
const flags = context.state.featureFlags as import("@actions/expressions/features").FeatureFlags | undefined;
const useServiceContainer = flags?.isEnabled("allowServiceContainerCommand") ?? false;
const mapping = services.assertMapping("services");
for (const service of mapping) {
service.key.assertString("service key");
const container = useServiceContainer
? convertToServiceContainer(context, service.value)
: convertToJobContainer(context, service.value);
const container = convertToServiceContainer(context, service.value);
if (container) {
serviceList.push(container);
}