Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7520561423 | ||
|
|
cc316ab9de | ||
|
|
d5670c383a | ||
|
|
f62a0e189d |
@@ -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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/expressions",
|
"name": "@actions/expressions",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"source": "./src/index.ts",
|
"source": "./src/index.ts",
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ describe("FeatureFlags", () => {
|
|||||||
"missingInputsQuickfix",
|
"missingInputsQuickfix",
|
||||||
"blockScalarChompingWarning",
|
"blockScalarChompingWarning",
|
||||||
"allowCaseFunction",
|
"allowCaseFunction",
|
||||||
"allowCopilotRequestsPermission",
|
"allowCopilotRequestsPermission"
|
||||||
"allowServiceContainerCommand"
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,12 +40,6 @@ export interface ExperimentalFeatures {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
allowCopilotRequestsPermission?: boolean;
|
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",
|
"missingInputsQuickfix",
|
||||||
"blockScalarChompingWarning",
|
"blockScalarChompingWarning",
|
||||||
"allowCaseFunction",
|
"allowCaseFunction",
|
||||||
"allowCopilotRequestsPermission",
|
"allowCopilotRequestsPermission"
|
||||||
"allowServiceContainerCommand"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export class FeatureFlags {
|
export class FeatureFlags {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/languageserver",
|
"name": "@actions/languageserver",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"description": "Language server for GitHub Actions",
|
"description": "Language server for GitHub Actions",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
"actions-languageserver": "./bin/actions-languageserver"
|
"actions-languageserver": "./bin/actions-languageserver"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/languageservice": "^0.3.50",
|
"@actions/languageservice": "^0.3.51",
|
||||||
"@actions/workflow-parser": "^0.3.50",
|
"@actions/workflow-parser": "^0.3.51",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
"@octokit/types": "^9.0.0",
|
"@octokit/types": "^9.0.0",
|
||||||
"vscode-languageserver": "^8.0.2",
|
"vscode-languageserver": "^8.0.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/languageservice",
|
"name": "@actions/languageservice",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"description": "Language service for GitHub Actions",
|
"description": "Language service for GitHub Actions",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -47,8 +47,8 @@
|
|||||||
"watch": "tsc --build tsconfig.build.json --watch"
|
"watch": "tsc --build tsconfig.build.json --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/expressions": "^0.3.50",
|
"@actions/expressions": "^0.3.51",
|
||||||
"@actions/workflow-parser": "^0.3.50",
|
"@actions/workflow-parser": "^0.3.51",
|
||||||
"vscode-languageserver-textdocument": "^1.0.7",
|
"vscode-languageserver-textdocument": "^1.0.7",
|
||||||
"vscode-languageserver-types": "^3.17.2",
|
"vscode-languageserver-types": "^3.17.2",
|
||||||
"vscode-uri": "^3.0.8",
|
"vscode-uri": "^3.0.8",
|
||||||
|
|||||||
@@ -1016,6 +1016,45 @@ 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", () => {
|
describe("service container command/entrypoint completion", () => {
|
||||||
it("suggests entrypoint and command in service container", async () => {
|
it("suggests entrypoint and command in service container", async () => {
|
||||||
const input = `on: push
|
const input = `on: push
|
||||||
|
|||||||
@@ -105,13 +105,6 @@
|
|||||||
"job": {
|
"job": {
|
||||||
"description": "The [`job_id`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_id) of the current job.\nNote: This context property is set by the Actions runner, and is only available within the execution `steps` of a job. Otherwise, the value of this property will be `null`."
|
"description": "The [`job_id`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_id) of the current job.\nNote: This context property is set by the Actions runner, and is only available within the execution `steps` of a job. Otherwise, the value of this property will be `null`."
|
||||||
},
|
},
|
||||||
"job_workflow_sha": {
|
|
||||||
"description": "For jobs using a reusable workflow, the commit SHA for the reusable workflow file.",
|
|
||||||
"versions": {
|
|
||||||
"ghes": ">=3.9",
|
|
||||||
"ghae": ">=3.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"path": {
|
"path": {
|
||||||
"description": "Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see \"[Workflow commands for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path).\""
|
"description": "Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see \"[Workflow commands for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path).\""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export function getGithubContext(workflowContext: WorkflowContext | undefined, m
|
|||||||
"graphql_url",
|
"graphql_url",
|
||||||
"head_ref",
|
"head_ref",
|
||||||
"job",
|
"job",
|
||||||
"job_workflow_sha",
|
|
||||||
"path",
|
"path",
|
||||||
"ref",
|
"ref",
|
||||||
"ref_name",
|
"ref_name",
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import {FeatureFlags} from "@actions/expressions";
|
|
||||||
import {registerLogger} from "./log.js";
|
import {registerLogger} from "./log.js";
|
||||||
import {createDocument} from "./test-utils/document.js";
|
import {createDocument} from "./test-utils/document.js";
|
||||||
import {TestLogger} from "./test-utils/logger.js";
|
import {TestLogger} from "./test-utils/logger.js";
|
||||||
import {clearCache} from "./utils/workflow-cache.js";
|
import {clearCache} from "./utils/workflow-cache.js";
|
||||||
import {validate, ValidationConfig} from "./validate.js";
|
import {validate} from "./validate.js";
|
||||||
|
|
||||||
registerLogger(new TestLogger());
|
registerLogger(new TestLogger());
|
||||||
|
|
||||||
const configWithFlag: ValidationConfig = {
|
|
||||||
featureFlags: new FeatureFlags({allowServiceContainerCommand: true})
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clearCache();
|
clearCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("service container command/entrypoint", () => {
|
describe("service container command/entrypoint", () => {
|
||||||
describe("with feature flag enabled", () => {
|
it("allows command in service container", async () => {
|
||||||
it("allows command in service container", async () => {
|
const input = `
|
||||||
const input = `
|
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -30,13 +24,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo hi
|
- run: echo hi
|
||||||
`;
|
`;
|
||||||
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
|
const result = await validate(createDocument("wf.yaml", input));
|
||||||
const commandErrors = result.filter(d => d.message.includes("command"));
|
const commandErrors = result.filter(d => d.message.includes("command"));
|
||||||
expect(commandErrors).toEqual([]);
|
expect(commandErrors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows entrypoint in service container", async () => {
|
it("allows entrypoint in service container", async () => {
|
||||||
const input = `
|
const input = `
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -48,13 +42,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo hi
|
- run: echo hi
|
||||||
`;
|
`;
|
||||||
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
|
const result = await validate(createDocument("wf.yaml", input));
|
||||||
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
|
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
|
||||||
expect(entrypointErrors).toEqual([]);
|
expect(entrypointErrors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows both command and entrypoint in service container", async () => {
|
it("allows both command and entrypoint in service container", async () => {
|
||||||
const input = `
|
const input = `
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -67,13 +61,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo hi
|
- run: echo hi
|
||||||
`;
|
`;
|
||||||
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
|
const result = await validate(createDocument("wf.yaml", input));
|
||||||
const relevantErrors = result.filter(d => d.message.includes("command") || d.message.includes("entrypoint"));
|
const relevantErrors = result.filter(d => d.message.includes("command") || d.message.includes("entrypoint"));
|
||||||
expect(relevantErrors).toEqual([]);
|
expect(relevantErrors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects command in job container even with flag enabled", async () => {
|
it("rejects command in job container", async () => {
|
||||||
const input = `
|
const input = `
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -84,13 +78,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo hi
|
- run: echo hi
|
||||||
`;
|
`;
|
||||||
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
|
const result = await validate(createDocument("wf.yaml", input));
|
||||||
const commandErrors = result.filter(d => d.message.includes("command"));
|
const commandErrors = result.filter(d => d.message.includes("command"));
|
||||||
expect(commandErrors.length).toBeGreaterThan(0);
|
expect(commandErrors.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects entrypoint in job container even with flag enabled", async () => {
|
it("rejects entrypoint in job container", async () => {
|
||||||
const input = `
|
const input = `
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -101,47 +95,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo hi
|
- run: echo hi
|
||||||
`;
|
`;
|
||||||
const result = await validate(createDocument("wf.yaml", input), configWithFlag);
|
const result = await validate(createDocument("wf.yaml", input));
|
||||||
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
|
const entrypointErrors = result.filter(d => d.message.includes("entrypoint"));
|
||||||
expect(entrypointErrors.length).toBeGreaterThan(0);
|
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -6,5 +6,5 @@
|
|||||||
"languageservice",
|
"languageservice",
|
||||||
"languageserver"
|
"languageserver"
|
||||||
],
|
],
|
||||||
"version": "0.3.50"
|
"version": "0.3.51"
|
||||||
}
|
}
|
||||||
Generated
+9
-9
@@ -136,7 +136,7 @@
|
|||||||
},
|
},
|
||||||
"expressions": {
|
"expressions": {
|
||||||
"name": "@actions/expressions",
|
"name": "@actions/expressions",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.0.3",
|
"@types/jest": "^29.0.3",
|
||||||
@@ -396,11 +396,11 @@
|
|||||||
},
|
},
|
||||||
"languageserver": {
|
"languageserver": {
|
||||||
"name": "@actions/languageserver",
|
"name": "@actions/languageserver",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/languageservice": "^0.3.50",
|
"@actions/languageservice": "^0.3.51",
|
||||||
"@actions/workflow-parser": "^0.3.50",
|
"@actions/workflow-parser": "^0.3.51",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
"@octokit/types": "^9.0.0",
|
"@octokit/types": "^9.0.0",
|
||||||
"vscode-languageserver": "^8.0.2",
|
"vscode-languageserver": "^8.0.2",
|
||||||
@@ -927,11 +927,11 @@
|
|||||||
},
|
},
|
||||||
"languageservice": {
|
"languageservice": {
|
||||||
"name": "@actions/languageservice",
|
"name": "@actions/languageservice",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/expressions": "^0.3.50",
|
"@actions/expressions": "^0.3.51",
|
||||||
"@actions/workflow-parser": "^0.3.50",
|
"@actions/workflow-parser": "^0.3.51",
|
||||||
"vscode-languageserver-textdocument": "^1.0.7",
|
"vscode-languageserver-textdocument": "^1.0.7",
|
||||||
"vscode-languageserver-types": "^3.17.2",
|
"vscode-languageserver-types": "^3.17.2",
|
||||||
"vscode-uri": "^3.0.8",
|
"vscode-uri": "^3.0.8",
|
||||||
@@ -14020,10 +14020,10 @@
|
|||||||
},
|
},
|
||||||
"workflow-parser": {
|
"workflow-parser": {
|
||||||
"name": "@actions/workflow-parser",
|
"name": "@actions/workflow-parser",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/expressions": "^0.3.50",
|
"@actions/expressions": "^0.3.51",
|
||||||
"cronstrue": "^2.21.0",
|
"cronstrue": "^2.21.0",
|
||||||
"yaml": "^2.0.0-8"
|
"yaml": "^2.0.0-8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@actions/workflow-parser",
|
"name": "@actions/workflow-parser",
|
||||||
"version": "0.3.50",
|
"version": "0.3.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"source": "./src/index.ts",
|
"source": "./src/index.ts",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"watch": "tsc --build tsconfig.build.json --watch"
|
"watch": "tsc --build tsconfig.build.json --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/expressions": "^0.3.50",
|
"@actions/expressions": "^0.3.51",
|
||||||
"cronstrue": "^2.21.0",
|
"cronstrue": "^2.21.0",
|
||||||
"yaml": "^2.0.0-8"
|
"yaml": "^2.0.0-8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -146,15 +146,11 @@ export function convertToServiceContainer(context: TemplateContext, container: T
|
|||||||
|
|
||||||
export function convertToJobServices(context: TemplateContext, services: TemplateToken): Container[] | undefined {
|
export function convertToJobServices(context: TemplateContext, services: TemplateToken): Container[] | undefined {
|
||||||
const serviceList: Container[] = [];
|
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");
|
const mapping = services.assertMapping("services");
|
||||||
for (const service of mapping) {
|
for (const service of mapping) {
|
||||||
service.key.assertString("service key");
|
service.key.assertString("service key");
|
||||||
const container = useServiceContainer
|
const container = convertToServiceContainer(context, service.value);
|
||||||
? convertToServiceContainer(context, service.value)
|
|
||||||
: convertToJobContainer(context, service.value);
|
|
||||||
if (container) {
|
if (container) {
|
||||||
serviceList.push(container);
|
serviceList.push(container);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1649,6 +1649,10 @@
|
|||||||
"statuses": {
|
"statuses": {
|
||||||
"type": "permission-level-any",
|
"type": "permission-level-any",
|
||||||
"description": "Commit statuses."
|
"description": "Commit statuses."
|
||||||
|
},
|
||||||
|
"workflows": {
|
||||||
|
"type": "permission-level-write-or-no-access",
|
||||||
|
"description": "Update GitHub Actions workflow files."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user