diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ce488c9 --- /dev/null +++ b/AGENTS.md @@ -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 +``` + +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. diff --git a/expressions/src/features.test.ts b/expressions/src/features.test.ts index fb2850e..837b900 100644 --- a/expressions/src/features.test.ts +++ b/expressions/src/features.test.ts @@ -55,8 +55,7 @@ describe("FeatureFlags", () => { "missingInputsQuickfix", "blockScalarChompingWarning", "allowCaseFunction", - "allowCopilotRequestsPermission", - "allowServiceContainerCommand" + "allowCopilotRequestsPermission" ]); }); }); diff --git a/expressions/src/features.ts b/expressions/src/features.ts index 4068a75..d474d2d 100644 --- a/expressions/src/features.ts +++ b/expressions/src/features.ts @@ -40,12 +40,6 @@ export interface ExperimentalFeatures { * @default false */ allowCopilotRequestsPermission?: boolean; - - /** - * Enable `entrypoint` and `command` keys in service containers (`jobs..services.*`). - * @default false - */ - allowServiceContainerCommand?: boolean; } /** @@ -61,8 +55,7 @@ const allFeatureKeys: ExperimentalFeatureKey[] = [ "missingInputsQuickfix", "blockScalarChompingWarning", "allowCaseFunction", - "allowCopilotRequestsPermission", - "allowServiceContainerCommand" + "allowCopilotRequestsPermission" ]; export class FeatureFlags { diff --git a/languageservice/src/validate.service-container-command.test.ts b/languageservice/src/validate.service-container-command.test.ts index 53537c9..01b2b39 100644 --- a/languageservice/src/validate.service-container-command.test.ts +++ b/languageservice/src/validate.service-container-command.test.ts @@ -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); }); }); diff --git a/workflow-parser/src/model/converter/container.ts b/workflow-parser/src/model/converter/container.ts index f087866..de39292 100644 --- a/workflow-parser/src/model/converter/container.ts +++ b/workflow-parser/src/model/converter/container.ts @@ -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); }