diff --git a/expressions/src/features.test.ts b/expressions/src/features.test.ts index 0ff1de7..dcb029f 100644 --- a/expressions/src/features.test.ts +++ b/expressions/src/features.test.ts @@ -55,7 +55,8 @@ describe("FeatureFlags", () => { "missingInputsQuickfix", "blockScalarChompingWarning", "allowCaseFunction", - "allowCronTimezone" + "allowCronTimezone", + "allowCopilotRequestsPermission" ]); }); }); diff --git a/expressions/src/features.ts b/expressions/src/features.ts index 163629c..c2bcdd5 100644 --- a/expressions/src/features.ts +++ b/expressions/src/features.ts @@ -40,6 +40,12 @@ export interface ExperimentalFeatures { * @default false */ allowCronTimezone?: boolean; + + /** + * Enable the copilot-requests permission in workflow permissions. + * @default false + */ + allowCopilotRequestsPermission?: boolean; } /** @@ -55,7 +61,8 @@ const allFeatureKeys: ExperimentalFeatureKey[] = [ "missingInputsQuickfix", "blockScalarChompingWarning", "allowCaseFunction", - "allowCronTimezone" + "allowCronTimezone", + "allowCopilotRequestsPermission" ]; export class FeatureFlags { diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index aa7384b..b33ad66 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -967,3 +967,79 @@ describe("schedule timezone completion", () => { expect(labels).not.toContain("timezone"); }); }); + +describe("permissions copilot-requests completion", () => { + it("includes copilot-requests when allowCopilotRequestsPermission is enabled", async () => { + const input = `on: push +permissions: + |`; + const result = await complete(...getPositionFromCursor(input), { + featureFlags: new FeatureFlags({allowCopilotRequestsPermission: true}) + }); + + expect(result).not.toBeUndefined(); + const labels = result.map(x => x.label); + expect(labels).toContain("actions"); + expect(labels).toContain("copilot-requests"); + }); + + it("excludes copilot-requests when allowCopilotRequestsPermission is disabled", async () => { + const input = `on: push +permissions: + |`; + const result = await complete(...getPositionFromCursor(input), { + featureFlags: new FeatureFlags({allowCopilotRequestsPermission: false}) + }); + + expect(result).not.toBeUndefined(); + const labels = result.map(x => x.label); + expect(labels).toContain("actions"); + expect(labels).not.toContain("copilot-requests"); + }); + + it("excludes copilot-requests when no feature flags are provided", 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("actions"); + expect(labels).not.toContain("copilot-requests"); + }); + + it("includes copilot-requests in job-level permissions when allowCopilotRequestsPermission is enabled", async () => { + const input = `on: push +jobs: + build: + runs-on: ubuntu-latest + permissions: + |`; + const result = await complete(...getPositionFromCursor(input), { + featureFlags: new FeatureFlags({allowCopilotRequestsPermission: true}) + }); + + expect(result).not.toBeUndefined(); + const labels = result.map(x => x.label); + expect(labels).toContain("actions"); + expect(labels).toContain("copilot-requests"); + }); + + it("excludes copilot-requests from job-level permissions when allowCopilotRequestsPermission is disabled", async () => { + const input = `on: push +jobs: + build: + runs-on: ubuntu-latest + permissions: + |`; + const result = await complete(...getPositionFromCursor(input), { + featureFlags: new FeatureFlags({allowCopilotRequestsPermission: false}) + }); + + expect(result).not.toBeUndefined(); + const labels = result.map(x => x.label); + expect(labels).toContain("actions"); + expect(labels).not.toContain("copilot-requests"); + }); +}); diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index a861c75..650ff1b 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -168,6 +168,14 @@ export async function complete( values = values.filter(v => v.label !== "timezone"); } + // Filter `copilot-requests` from permissions completions when the feature flag is disabled + if ( + !config?.featureFlags?.isEnabled("allowCopilotRequestsPermission") && + parent?.definition?.key === "permissions-mapping" + ) { + values = values.filter(v => v.label !== "copilot-requests"); + } + // Offer "(switch to list)" / "(switch to mapping)" when the schema allows alternative forms const escapeHatches = getEscapeHatchCompletions(token, keyToken, indentString, newPos, schema); values.push(...escapeHatches); diff --git a/workflow-parser/src/workflow-v1.0.json b/workflow-parser/src/workflow-v1.0.json index cd63c12..1b7e214 100644 --- a/workflow-parser/src/workflow-v1.0.json +++ b/workflow-parser/src/workflow-v1.0.json @@ -1602,6 +1602,10 @@ "type": "permission-level-any", "description": "Repository contents, commits, branches, downloads, releases, and merges." }, + "copilot-requests": { + "type": "permission-level-write-or-no-access", + "description": "GitHub Copilot requests." + }, "deployments": { "type": "permission-level-any", "description": "Deployments and deployment statuses."