Compare commits

..

3 Commits

Author SHA1 Message Date
Christopher Schleiden 6ee228549a Validate context expressions 2023-04-04 16:40:04 -07:00
Christopher Schleiden 45a665690b Remove error-dictionary 2023-04-04 16:31:07 -07:00
Christopher Schleiden 0a9f420c96 Improve expression validation 2023-04-04 16:29:43 -07:00
43 changed files with 533 additions and 855 deletions
+1 -1
View File
@@ -1 +1 @@
* @actions/actions-vscode-reviewers
* @actions/actions-experience
-16
View File
@@ -1,16 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directories:
- "/"
- "/languageservice"
- "/languageserver"
- "expressions"
- "browser-playground"
schedule:
interval: "weekly"
+9 -20
View File
@@ -1,18 +1,13 @@
name: Create release PR
run-name: Create release PR for new ${{ github.event.inputs.version }} version
run-name: Create release PR for v${{ github.event.inputs.version }}
on:
workflow_dispatch:
inputs:
version:
required: true
type: choice
description: "What type of release is this"
options:
- "major"
- "minor"
- "patch"
description: "Version to bump `package.json` to (format: x.y.z)"
jobs:
create-release-pr:
@@ -36,27 +31,21 @@ jobs:
git config --global user.email "github-actions@github.com"
git config --global user.name "GitHub Actions"
NEW_VERSION=$(./script/workflows/increment-version.sh ${{ inputs.version }})
git checkout -b release/${{ inputs.version }}
git checkout -b release/$NEW_VERSION
npx lerna version $NEW_VERSION --yes --no-push --no-git-tag-version --force-publish
npx lerna version ${{ inputs.version }} --yes --no-push --no-git-tag-version --force-publish
git add **/package.json package-lock.json lerna.json
git commit -m "Release extension version $NEW_VERSION"
git commit -m "Release extension version ${{ inputs.version }}"
git push --set-upstream origin release/$NEW_VERSION
echo "new_version=$NEW_VERSION" >> $GITHUB_ENV
git push --set-upstream origin release/${{ inputs.version }}
- name: Create PR
run: |
LAST_PR=$(gh pr list --repo ${{ github.repository }} --limit 1 --state merged --search "Release version" --json number | jq -r '.[0].number')
./script/workflows/generate-release-notes.sh $LAST_PR ${{ env.new_version }}
gh pr create \
--title "Release version ${{ env.new_version }}" \
--body-file releasenotes.md \
--title "Release version ${{ inputs.version }}" \
--body "Release version ${{ inputs.version }}" \
--base main \
--head release/${{ env.new_version }}
--head release/${{ inputs.version }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -1,5 +1,5 @@
*/node_modules
*/dist
lerna-debug.log
node_modules
.DS_Store
+2 -10
View File
@@ -8,8 +8,6 @@ Hi there! We're thrilled that you'd like to contribute to this project. Your hel
We accept pull requests for bug fixes and features where we've discussed the approach in an issue and given the go-ahead for a community member to work on it. We'd also love to hear about ideas for new features as issues.
We track issues on our project board [here](https://github.com/orgs/github/projects/9557/views/1).
Please do:
* Check existing issues to verify that the [bug][bug issues] or [feature request][feature request issues] has not already been submitted.
@@ -23,7 +21,7 @@ Please avoid:
* Opening pull requests for issues marked `needs-design`, `needs-investigation`, or `blocked`.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.md).
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
@@ -62,10 +60,4 @@ Please also look at the `README.md` files for each package for additional notes
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
[bug issues]: https://github.com/actions/languageservices/labels/bug
[feature request issues]: https://github.com/actions/languageservices/labels/enhancement
[hw]: https://github.com/actions/languageservices/labels/help%20wanted
[gfi]: https://github.com/actions/languageservices/labels/good%20first%20issue
- [GitHub Help](https://help.github.com)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/expressions",
"version": "0.3.12",
"version": "0.3.1",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
+1 -1
View File
@@ -32,7 +32,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
return this.eval(this.n);
}
protected eval(n: Expr): data.ExpressionData {
private eval(n: Expr): data.ExpressionData {
return n.accept(this);
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageserver",
"version": "0.3.12",
"version": "0.3.1",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -43,8 +43,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/languageservice": "^0.3.12",
"@actions/workflow-parser": "^0.3.12",
"@actions/languageservice": "^0.3.1",
"@actions/workflow-parser": "^0.3.1",
"@octokit/rest": "^19.0.7",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
+2 -3
View File
@@ -1,9 +1,8 @@
import {Octokit} from "@octokit/rest";
export function getClient(token: string, userAgent?: string, apiUrl?: string): Octokit {
export function getClient(token: string, userAgent?: string): Octokit {
return new Octokit({
auth: token,
userAgent: userAgent || `GitHub Actions Language Server`,
baseUrl: apiUrl
userAgent: userAgent || `GitHub Actions Language Server`
});
}
+1 -1
View File
@@ -51,7 +51,7 @@ export function initConnection(connection: Connection) {
const options = params.initializationOptions as InitializationOptions;
if (options.sessionToken) {
client = getClient(options.sessionToken, options.userAgent, options.gitHubApiUrl);
client = getClient(options.sessionToken, options.userAgent);
}
if (options.repos) {
@@ -28,7 +28,6 @@ export async function getSecrets(
}
const eventsConfig = workflowContext?.template?.events;
if (eventsConfig?.workflow_call) {
// Unpredictable secrets may be passed in via a workflow_call trigger
secretsContext.complete = false;
@@ -39,7 +38,6 @@ export async function getSecrets(
}
let environmentName: string | undefined;
if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) {
environmentName = workflowContext.job.environment.value;
@@ -48,17 +46,10 @@ export async function getSecrets(
if (isString(x.key) && x.key.value === "name") {
if (isString(x.value)) {
environmentName = x.value.value;
} else {
// this means we have a dynamic enviornment, in those situations we
// want to make sure we skip doing secret validation
secretsContext.complete = false;
}
break;
}
}
} else {
// if the expression is something like environment: ${{ ... }} then we want to skip validation
secretsContext.complete = false;
}
}
@@ -99,13 +99,13 @@ it("adds action outputs", async () => {
key: "conclusion",
value: new data.Null(),
description:
"The result of a completed step after [`continue-on-error`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error) is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
"The result of a completed step after `continue-on-error` is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
},
{
key: "outcome",
value: new data.Null(),
description:
"The result of a completed step before [`continue-on-error`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error) is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
"The result of a completed step before `continue-on-error` is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
}
)
})
@@ -2,10 +2,9 @@ import {data, DescriptionDictionary} from "@actions/expressions";
import {Pair} from "@actions/expressions/data/expressiondata";
import {StringData} from "@actions/expressions/data/index";
import {WorkflowContext} from "@actions/languageservice/context/workflow-context";
import {log, warn} from "@actions/languageservice/log";
import {warn} from "@actions/languageservice/log";
import {isMapping, isString} from "@actions/workflow-parser";
import {Octokit} from "@octokit/rest";
import {RequestError} from "@octokit/request-error";
import {RepositoryContext} from "../initializationOptions";
import {TTLCache} from "../utils/cache";
@@ -43,58 +42,50 @@ export async function getVariables(
}
const variablesContext = defaultContext || new DescriptionDictionary();
try {
const variables = await getRemoteVariables(octokit, cache, repo, environmentName);
const variables = await getRemoteVariables(octokit, cache, repo, environmentName);
// Build combined map of variables
const variablesMap = new Map<
string,
{
key: string;
value: data.StringData;
description?: string;
}
>();
// Build combined map of variables
const variablesMap = new Map<
string,
{
key: string;
value: data.StringData;
description?: string;
}
>();
variables.organizationVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Organization variable`
})
);
variables.organizationVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Organization variable`
})
);
// Override org variables with repo variables
variables.repoVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Repository variable`
})
);
// Override org variables with repo variables
variables.repoVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Repository variable`
})
);
// Override repo variables with environment veriables (if defined)
variables.environmentVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Variable for environment \`${environmentName || ""}\``
})
);
// Override repo variables with environment veriables (if defined)
variables.environmentVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Variable for environment \`${environmentName || ""}\``
})
);
// Sort variables by key and add to context
Array.from(variablesMap.values())
.sort((a, b) => a.key.localeCompare(b.key))
.forEach(variable => variablesContext?.add(variable.key, variable.value, variable.description));
// Sort variables by key and add to context
Array.from(variablesMap.values())
.sort((a, b) => a.key.localeCompare(b.key))
.forEach(variable => variablesContext?.add(variable.key, variable.value, variable.description));
return variablesContext;
} catch (e) {
if (!(e instanceof RequestError)) throw e;
if (e.name == "HttpError" && e.status == 404) {
log("Failure to request variables. Ignore if you're using GitHub Enterprise Server below version 3.8");
return variablesContext;
} else throw e;
}
return variablesContext;
}
export async function getRemoteVariables(
+2 -5
View File
@@ -2,8 +2,8 @@ import {File} from "@actions/workflow-parser/workflows/file";
import {FileProvider} from "@actions/workflow-parser/workflows/file-provider";
import {fileIdentifier} from "@actions/workflow-parser/workflows/file-reference";
import {Octokit} from "@octokit/rest";
import path from "path";
import {TTLCache} from "./utils/cache";
import * as vscodeURI from "vscode-uri";
export function getFileProvider(
client: Octokit | undefined,
@@ -31,10 +31,7 @@ export function getFileProvider(
throw new Error("Local file references are not supported with this configuration");
}
const workspaceURI = vscodeURI.URI.parse(workspace);
const refURI = vscodeURI.Utils.joinPath(workspaceURI, ref.path);
const file = await readFile(refURI.toString());
const file = await readFile(path.join(workspace, ref.path));
if (!file) {
throw new Error(`File not found: ${ref.path}`);
}
@@ -23,11 +23,6 @@ export interface InitializationOptions {
* Desired log level
*/
logLevel?: LogLevel;
/**
* If a GitHub Enterprise Server should be used, the URL of the API endpoint, eg "https://ghe.my-company.com/api/v3"
*/
gitHubApiUrl?: string;
}
export interface RepositoryContext {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageservice",
"version": "0.3.12",
"version": "0.3.1",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -44,11 +44,11 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.12",
"@actions/workflow-parser": "^0.3.12",
"@actions/expressions": "^0.3.1",
"@actions/workflow-parser": "^0.3.1",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
"vscode-uri": "^3.0.7",
"yaml": "^2.1.1"
},
"engines": {
@@ -100,7 +100,7 @@ describe("expressions", () => {
label: "api_url",
documentation: {
kind: "markdown",
value: "The URL of the GitHub REST API."
value: "The URL of the GitHub Actions REST API."
},
kind: CompletionItemKind.Variable
});
+5 -48
View File
@@ -4,8 +4,8 @@ import {complete} from "./complete";
import {registerLogger} from "./log";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
import {clearCache} from "./utils/workflow-cache";
registerLogger(new TestLogger());
@@ -406,7 +406,7 @@ jobs:
expect(result.map(e => e.label)).toContain("runs-on");
const textEdit = result.filter(e => e.label === "runs-on")[0].textEdit as TextEdit;
expect(textEdit.newText).toEqual("runs-on: ");
expect(textEdit.newText).toEqual("runs-on");
expect(textEdit.range).toEqual({
start: {line: 3, character: 4},
end: {line: 3, character: 10}
@@ -421,7 +421,7 @@ jobs:
expect(result.map(e => e.label)).toContain("runs-on");
const textEdit = result.filter(e => e.label === "runs-on")[0].textEdit as TextEdit;
expect(textEdit.newText).toEqual("runs-on: ");
expect(textEdit.newText).toEqual("runs-on");
expect(textEdit.range).toEqual({
start: {line: 3, character: 4},
end: {line: 3, character: 4}
@@ -448,7 +448,7 @@ jobs:
]);
// One-of
expect(result.filter(x => x.label === "concurrency").map(x => x.textEdit?.newText)).toEqual(["concurrency: "]);
expect(result.filter(x => x.label === "concurrency").map(x => x.textEdit?.newText)).toEqual(["concurrency"]);
});
it("custom indentation", async () => {
@@ -471,50 +471,7 @@ jobs:
]);
// One-of
expect(result.filter(x => x.label === "concurrency").map(x => x.textEdit?.newText)).toEqual(["concurrency: "]);
expect(result.filter(x => x.label === "concurrency").map(x => x.textEdit?.newText)).toEqual(["concurrency"]);
});
});
it("adds a new line and indentation for mapping keys when the key is given", async () => {
const input = "concurrency: |";
const result = await complete(...getPositionFromCursor(input));
expect(result.filter(x => x.label === "cancel-in-progress").map(x => x.textEdit?.newText)).toEqual([
"\n cancel-in-progress: "
]);
expect(result.filter(x => x.label === "group").map(x => x.textEdit?.newText)).toEqual(["\n group: "]);
});
it("does not add new line if no key in line", async () => {
const input = "run-n|";
const result = await complete(...getPositionFromCursor(input));
expect(result.filter(x => x.label === "run-name").map(x => x.textEdit?.newText)).toEqual(["run-name: "]);
});
it("adds new line for nested mapping", async () => {
const input = "on:\n workflow_dispatch: in|";
const result = await complete(...getPositionFromCursor(input));
expect(result.filter(x => x.label === "inputs").map(x => x.textEdit?.newText)).toEqual(["\n inputs:\n "]);
});
it("adds : for one-of", async () => {
const input = "on:\n check_run:\n ty|";
const result = await complete(...getPositionFromCursor(input));
expect(result.filter(x => x.label === "types").map(x => x.textEdit?.newText)).toEqual(["types: "]);
});
it("does not add : for one-of in key mode", async () => {
const input = "on:\n check_run: ty|";
const result = await complete(...getPositionFromCursor(input));
expect(result.filter(x => x.label === "types").map(x => x.textEdit?.newText)).toEqual(["types"]);
});
});
+2 -2
View File
@@ -24,7 +24,7 @@ import {isPlaceholder, transform} from "./utils/transform";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache";
import {Value, ValueProviderConfig} from "./value-providers/config";
import {defaultValueProviders} from "./value-providers/default";
import {DefinitionValueMode, definitionValues} from "./value-providers/definition";
import {definitionValues} from "./value-providers/definition";
export function getExpressionInput(input: string, pos: number): string {
// Find start marker around the cursor position
@@ -180,7 +180,7 @@ async function getValues(
return [];
}
const values = definitionValues(def, indentation, keyToken ? DefinitionValueMode.Key : DefinitionValueMode.Parent);
const values = definitionValues(def, indentation);
return filterAndSortCompletionOptions(values, existingValues);
}
@@ -49,15 +49,15 @@
"description": "Returns `true` when any previous step of a job fails. If you have a chain of dependent jobs, `failure()` returns `true` if any ancestor job fails."
},
"hashFiles": {
"description": "Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`. This function calculates an individual SHA-256 hash for each matched file, and then uses those hashes to calculate a final SHA-256 hash for the set of files. If the `path` pattern does not match any files, this returns an empty string. For more information about SHA-256, see \"[SHA-2](https://wikipedia.org/wiki/SHA-2).\"\n\nYou can use pattern matching characters to match file names. Pattern matching is case-insensitive on Windows. For more information about supported pattern matching characters, see \"[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet).\""
"description": "Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`."
}
},
"github": {
"action": {
"description": "The name of the action currently running, or the [`id`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid) of a step. GitHub removes special characters, and uses the name `__run` when the current step runs a script without an `id`. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name `__run`, and the second script will be named `__run_2`. Similarly, the second invocation of `actions/checkout` will be `actionscheckout2`."
"description": "The name of the action currently running, or the [`id`](https://docs.github.com/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsid) of a step. GitHub Actions removes special characters, and uses the name `__run` when the current step runs a script without an `id`. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name `__run`, and the second script will be named `__run_2`. Similarly, the second invocation of `actions/checkout` will be `actionscheckout2`."
},
"action_path": {
"description": "The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action, for example by changing directories to the path: `cd ${{ github.action_path }}`."
"description": "The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action."
},
"action_ref": {
"description": "For a step executing an action, this is the ref of the action being executed. For example, `v2`."
@@ -71,24 +71,17 @@
"actor": {
"description": "The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from `github.triggering_actor`. Any workflow re-runs will use the privileges of `github.actor`, even if the actor initiating the re-run (`github.triggering_actor`) has different privileges."
},
"actor_id": {
"description": "The account ID of the person or app that triggered the initial workflow run. For example, `1234567`. Note that this is different from the actor username.",
"versions": {
"ghes": ">=3.9",
"ghae": ">=3.9"
}
},
"api_url": {
"description": "The URL of the GitHub REST API."
"description": "The URL of the GitHub Actions REST API."
},
"base_ref": {
"description": "The `base_ref` or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either `pull_request` or `pull_request_target`."
},
"env": {
"description": "Path on the runner to the file that sets environment 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#setting-an-environment-variable).\""
"description": "Path on the runner to the file that sets environment 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](https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#setting-an-environment-variable)."
},
"event": {
"description": "The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in \"[Events that trigger workflows](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows).\" For example, for a workflow run triggered by the [`push` event](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#push), this object contains the contents of the [push webhook payload](https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#push)."
"description": "The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in [Event that trigger workflows](/articles/events-that-trigger-workflows/). For example, for a workflow run triggered by the [`push` event](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#push), this object contains the contents of the [push webhook payload](https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push)."
},
"event_name": {
"description": "The name of the event that triggered the workflow run."
@@ -97,58 +90,53 @@
"description": "The path to the file on the runner that contains the full event webhook payload."
},
"graphql_url": {
"description": "The URL of the GitHub GraphQL API."
"description": "The URL of the GitHub Actions GraphQL API."
},
"head_ref": {
"description": "The `head_ref` or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either `pull_request` or `pull_request_target`."
},
"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`](/actions/reference/workflow-syntax-for-github-actions#jobsjob_id) of the current job. <br /> Note: 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.",
"ref": {
"description": "The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by `push`, this is the branch or tag ref that was pushed. For workflows triggered by `pull_request`, this is the pull request merge branch. For workflows triggered by `release`, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is `refs/heads/<branch_name>`, for pull requests it is `refs/pull/<pr_number>/merge`, and for tags it is `refs/tags/<tag_name>`. For example, `refs/heads/feature-branch-1`.",
"versions": {
"ghes": ">=3.9",
"ghae": ">=3.9"
"ghes": "3.3",
"ghae": "3.3"
}
},
"ref_name": {
"description": "The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, `feature-branch-1`.",
"versions": {
"ghes": "3.3",
"ghae": "3.3"
}
},
"ref_protected": {
"description": "`true` if branch protections are configured for the ref that triggered the workflow run.",
"versions": {
"ghes": "3.3",
"ghae": "3.3"
}
},
"ref_type": {
"description": "The type of ref that triggered the workflow run. Valid values are `branch` or `tag`.",
"versions": {
"ghes": "3.3",
"ghae": "3.3"
}
},
"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).\""
},
"ref": {
"description": "The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by `push`, this is the branch or tag ref that was pushed. For workflows triggered by `pull_request`, this is the pull request merge branch. For workflows triggered by `release`, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is `refs/heads/<branch_name>`, for pull requests it is `refs/pull/<pr_number>/merge`, and for tags it is `refs/tags/<tag_name>`. For example, `refs/heads/feature-branch-1`."
},
"ref_name": {
"description": "The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, `feature-branch-1`."
},
"ref_protected": {
"description": "`true` if branch protections are configured for the ref that triggered the workflow run."
},
"ref_type": {
"description": "The type of ref that triggered the workflow run. Valid values are `branch` or `tag`."
"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](https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#adding-a-system-path)."
},
"repository": {
"description": "The owner and repository name. For example, `octocat/Hello-World`."
},
"repository_id": {
"description": "The ID of the repository. For example, `123456789`. Note that this is different from the repository name.",
"versions": {
"ghes": ">=3.9",
"ghae": ">=3.9"
}
"description": "The owner and repository name. For example, `Codertocat/Hello-World`."
},
"repository_owner": {
"description": "The repository owner's username. For example, `octocat`."
},
"repository_owner_id": {
"description": "The repository owner's account ID. For example, `1234567`. Note that this is different from the owner's name.",
"versions": {
"ghes": ">=3.9",
"ghae": ">=3.9"
}
"description": "The repository owner's name. For example, `Codertocat`."
},
"repositoryUrl": {
"description": "The Git URL to the repository. For example, `git://github.com/octocat/hello-world.git`."
"description": "The Git URL to the repository. For example, `git://github.com/codertocat/hello-world.git`."
},
"retention_days": {
"description": "The number of days that workflow run logs and artifacts are kept."
@@ -160,19 +148,27 @@
"description": "A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run."
},
"run_attempt": {
"description": "A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run."
"description": "A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run.",
"versions": {
"ghes": "3.5",
"ghae": "3.4"
}
},
"secret_source": {
"description": "The source of a secret used in a workflow. Possible values are `None`, `Actions`, `Dependabot`, or `Codespaces`."
"description": "The source of a secret used in a workflow. Possible values are `None`, `Actions`, `Dependabot`, or `Codespaces`.",
"versions": {
"ghes": "3.3",
"ghae": "3.3"
}
},
"server_url": {
"description": "The URL of the GitHub server. For example: `https://github.com`."
},
"sha": {
"description": "The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see \"[Events that trigger workflows](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows).\" For example, `ffac537e6cbbf934b08745a378932722df287a53`."
"description": "The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see [Events that trigger workflows.](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows) For example, `ffac537e6cbbf934b08745a378932722df287a53`."
},
"token": {
"description": "A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the `GITHUB_TOKEN` secret. For more information, see \"[Automatic token authentication](https://docs.github.com/actions/security-guides/automatic-token-authentication).\"\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": "A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the `GITHUB_TOKEN` secret. For more information, see [Automatic token authentication](https://docs.github.com/actions/security-guides/automatic-token-authentication).\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`."
},
"triggering_actor": {
"description": "The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from `github.actor`. Any workflow re-runs will use the privileges of `github.actor`, even if the actor initiating the re-run (`github.triggering_actor`) has different privileges."
@@ -180,27 +176,13 @@
"workflow": {
"description": "The name of the workflow. If the workflow file doesn't specify a `name`, the value of this property is the full path of the workflow file in the repository."
},
"workflow_ref": {
"description": "The ref path to the workflow. For example, `octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch`.",
"versions": {
"ghes": ">=3.9",
"ghae": ">=3.9"
}
},
"workflow_sha": {
"description": "The commit SHA for the workflow file.",
"versions": {
"ghes": ">=3.9",
"ghae": ">=3.9"
}
},
"workspace": {
"description": "The default working directory on the runner for steps, and the default location of your repository when using the [`checkout`](https://github.com/actions/checkout) action."
}
},
"secrets": {
"GITHUB_TOKEN": {
"description": "Automatically created token for each workflow run. For more information, see \"[Automatic token authentication](https://docs.github.com/actions/security-guides/automatic-token-authentication).\""
"description": "`GITHUB_TOKEN` is a secret that is automatically created for every workflow run, and is always included in the secrets context. For more information, see [Automatic token authentication](https://docs.github.com/actions/security-guides/automatic-token-authentication)."
}
},
"jobs": {
@@ -213,13 +195,13 @@
},
"steps": {
"outputs": {
"description": "The set of outputs defined for the step. For more information, see \"[Metadata syntax for GitHub Actions](https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions).\""
"description": "The set of outputs defined for the step."
},
"conclusion": {
"description": "The result of a completed step after [`continue-on-error`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error) is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
"description": "The result of a completed step after `continue-on-error` is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
},
"outcome": {
"description": "The result of a completed step before [`continue-on-error`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error) is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
"description": "The result of a completed step before `continue-on-error` is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`."
}
},
"runner": {
@@ -236,24 +218,24 @@
"description": "The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them."
},
"tool_cache": {
"description": "The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see \"[About GitHub-hosted runners](https://docs.github.com/actions/reference/specifications-for-github-hosted-runners/#supported-software).\""
"description": "The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see \"[About GitHub-hosted runners](https://docs.github.com/actions/reference/specifications-for-github-hosted-runners/#supported-software)\"."
},
"debug": {
"description": "This is set only if [debug logging](https://docs.github.com/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging) is enabled, and always has the value of `1`. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps."
"description": "This is set only if [debug logging](https://docs.github.com/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging) is enabled, and always has the value of 1. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps."
}
},
"strategy": {
"fail-fast": {
"description": "When `true`, all in-progress jobs are canceled if any job in a matrix fails. For more information, see \"[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast).\""
"description": "The `fail-fast` setting for the job. Possible values are `true` or `false`. For more information, see [Workflow syntax for GitHub Actions: `jobs.<job_id>.strategy.fail-fast`](https://docs.github.com/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast)."
},
"max-parallel": {
"description": "The `max-parallel` setting for the job. For more information, see [Workflow syntax for GitHub Actions: `jobs.<job_id>.strategy.max-parallel`](https://docs.github.com/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymax-parallel)."
},
"job-index": {
"description": "The index of the current job in the matrix. **Note:** This number is a zero-based number. The first job's index in the matrix is `0`."
},
"job-total": {
"description": "The total number of jobs in the matrix. **Note:** This number **is not** a zero-based number. For example, for a matrix with four jobs, the value of `job-total` is `4`."
},
"max-parallel": {
"description": "The maximum number of jobs that can run simultaneously when using a matrix job strategy. For more information, see \"[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymax-parallel).\""
}
}
}
@@ -8,7 +8,7 @@ import {getEventPayload, getSupportedEventTypes} from "./events/eventPayloads";
import {getInputsContext} from "./inputs";
export function getGithubContext(workflowContext: WorkflowContext, mode: Mode): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-cwontext
const keys = [
"action",
"action_path",
@@ -16,7 +16,6 @@ export function getGithubContext(workflowContext: WorkflowContext, mode: Mode):
"action_repository",
"action_status",
"actor",
"actor_id",
"api_url",
"base_ref",
"env",
@@ -26,16 +25,13 @@ export function getGithubContext(workflowContext: WorkflowContext, mode: Mode):
"graphql_url",
"head_ref",
"job",
"job_workflow_sha",
"path",
"ref",
"ref_name",
"ref_protected",
"ref_type",
"path",
"repository",
"repository_id",
"repository_owner",
"repository_owner_id",
"repositoryUrl",
"retention_days",
"run_id",
@@ -47,8 +43,6 @@ export function getGithubContext(workflowContext: WorkflowContext, mode: Mode):
"token",
"triggering_actor",
"workflow",
"workflow_ref",
"workflow_sha",
"workspace"
];
+1 -1
View File
@@ -4,7 +4,7 @@ import {File} from "@actions/workflow-parser/workflows/file";
import {parseFileReference} from "@actions/workflow-parser/workflows/file-reference";
import {TextDocument} from "vscode-languageserver-textdocument";
import {DocumentLink} from "vscode-languageserver-types";
import * as vscodeURI from "vscode-uri";
import vscodeURI from "vscode-uri/lib/umd"; // work around issues with the vscode-uri package
import {actionUrl, parseActionReference} from "./action";
import {mapRange} from "./utils/range";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache";
@@ -1,44 +0,0 @@
import {data, isDescriptionDictionary} from "@actions/expressions";
import {isDictionary} from "@actions/expressions/data/dictionary";
import {ExpressionData, Pair} from "@actions/expressions/data/expressiondata";
export class AccessError extends Error {
constructor(message: string, public readonly keyName: string) {
super(message);
}
}
export class ErrorDictionary extends data.Dictionary {
constructor(...pairs: Pair[]) {
super(...pairs);
}
public complete = true;
get(key: string): ExpressionData | undefined {
const value = super.get(key);
if (value) {
return value;
}
if (this.complete) {
throw new AccessError(`Invalid context access: ${key}`, key);
}
}
}
export function wrapDictionary(d: data.Dictionary): ErrorDictionary {
const e = new ErrorDictionary();
if (isDescriptionDictionary(d)) {
e.complete = d.complete;
}
for (const {key, value} of d.pairs()) {
if (isDictionary(value)) {
e.add(key, wrapDictionary(value));
} else {
e.add(key, value);
}
}
return e;
}
@@ -1,61 +0,0 @@
import {Evaluator, ExpressionEvaluationError, data} from "@actions/expressions";
import {Expr, Logical} from "@actions/expressions/ast";
import {ExpressionData} from "@actions/expressions/data/expressiondata";
import {TokenType} from "@actions/expressions/lexer";
import {falsy, truthy} from "@actions/expressions/result";
import {AccessError} from "./error-dictionary";
export type ValidationError = {
message: string;
severity: "error" | "warning";
};
export class ValidationEvaluator extends Evaluator {
public readonly errors: ValidationError[] = [];
public validate() {
super.evaluate();
}
protected override eval(n: Expr): ExpressionData {
try {
return super.eval(n);
} catch (e) {
// Record error
if (e instanceof AccessError) {
this.errors.push({
message: `Context access might be invalid: ${e.keyName}`,
severity: "warning"
});
} else if (e instanceof ExpressionEvaluationError) {
this.errors.push({
message: `Expression might be invalid: ${e.message}`,
severity: "error"
});
}
}
// Return null but continue with the validation
return new data.Null();
}
override visitLogical(logical: Logical): ExpressionData {
let result: data.ExpressionData | undefined;
for (const arg of logical.args) {
const r = this.eval(arg);
// Simulate short-circuit behavior but continue to evalute all arguments for validation purposes
if (
!result &&
((logical.operator.type === TokenType.AND && falsy(r)) || (logical.operator.type === TokenType.OR && truthy(r)))
) {
result = r;
}
}
// result is always assigned before we return here
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return result!;
}
}
@@ -0,0 +1,129 @@
import {Lexer, Parser} from "@actions/expressions";
import {Dictionary} from "@actions/expressions/data/dictionary";
import {StringData} from "@actions/expressions/data/string";
import {splitAllowedContext} from "@actions/workflow-parser/templates/allowed-context";
import {ValidationVisitor} from "./visitor";
const testContext = new Dictionary({
key: "github",
value: new Dictionary(
{
key: "event",
value: new StringData("push")
},
{
key: "repo",
value: new Dictionary({
key: "name",
value: new StringData("test")
})
}
)
});
function useVisitor(expression: string, allowedContext: string[]): any[] {
const {namedContexts, functions} = splitAllowedContext(allowedContext);
const l = new Lexer(expression);
const lr = l.lex();
const p = new Parser(lr.tokens, namedContexts, functions);
const expr = p.parse();
const e = new ValidationVisitor(expr, testContext);
e.validate();
return e.errors;
}
describe("validation visitor", () => {
it("invalid context access", () => {
expect(useVisitor("github.foo", ["github"])).toEqual([
{
message: "Context access might be invalid: foo",
range: {
end: {
column: 10,
line: 0
},
start: {
column: 0,
line: 0
}
},
severity: "warning"
}
]);
});
it("invalid context access as index", () => {
expect(useVisitor("github[github.foo]", ["github"])).toEqual([
{
message: "Context access might be invalid: foo",
range: {
end: {
column: 17,
line: 0
},
start: {
column: 7,
line: 0
}
},
severity: "warning"
}
]);
});
it("invalid nested context access", () => {
expect(useVisitor("github.repo.name", ["github"])).toEqual([
{
message: "Context access might be invalid: name",
range: {
end: {
column: 16,
line: 0
},
start: {
column: 0,
line: 0
}
},
severity: "warning"
}
]);
});
it("invalid context accesses", () => {
expect(useVisitor("github.foo || github.foo.bar", ["github"])).toEqual([
{
message: "Context access might be invalid: foo",
range: {
end: {
column: 10,
line: 0
},
start: {
column: 0,
line: 0
}
},
severity: "warning"
},
{
message: "Context access might be invalid: bar",
range: {
end: {
column: 28,
line: 0
},
start: {
column: 14,
line: 0
}
},
severity: "warning"
}
]);
});
});
@@ -0,0 +1,148 @@
import {DescriptionDictionary} from "@actions/expressions";
import {
Binary,
ContextAccess,
Expr,
ExprVisitor,
FunctionCall,
Grouping,
IndexAccess,
Literal,
Logical,
Unary
} from "@actions/expressions/ast";
import {Dictionary} from "@actions/expressions/data/dictionary";
import {ExpressionData} from "@actions/expressions/data/expressiondata";
import {Range} from "@actions/expressions/lexer";
export type ValidationError = {
range: Range;
message: string;
severity: "error" | "warning";
};
export class ValidationVisitor implements ExprVisitor<void> {
public readonly errors: ValidationError[] = [];
constructor(private expr: Expr, private context: Dictionary) {}
validate(): void {
this._validate(this.expr);
}
private _validate(expr: Expr) {
expr.accept(this);
}
visitLiteral() {
return undefined;
}
visitUnary(unary: Unary) {
this._validate(unary.expr);
}
visitBinary(binary: Binary) {
this._validate(binary.left);
this._validate(binary.right);
}
visitLogical(logical: Logical) {
for (const arg of logical.args) {
this._validate(arg);
}
}
visitGrouping(grouping: Grouping) {
this._validate(grouping.group);
}
visitContextAccess(contextAccess: ContextAccess) {
const contextName = contextAccess.name.lexeme;
if (this.context.get(contextName) === undefined) {
this.errors.push({
message: `Context access might be invalid: ${contextName}`,
range: contextAccess.name.range,
severity: "error"
});
}
}
visitIndexAccess(indexAccess: IndexAccess) {
let contextAccess: ContextAccess | undefined;
const s: ExpressionData[] = [];
let i: Expr = indexAccess;
while (i) {
if (i instanceof IndexAccess) {
if (!(i.index instanceof Literal)) {
// Not a literal, validate independently
this._validate(i.index);
return;
}
s.push(i.index.literal);
i = i.expr;
}
if (i instanceof ContextAccess) {
contextAccess = i;
break;
}
}
if (!contextAccess) {
// Context not found, should not happen, ignore in this case
return;
}
const contextName = contextAccess.name.lexeme;
let contextValue = this.context.get(contextName);
if (contextValue === undefined || !(contextValue instanceof Dictionary)) {
const contextName = contextAccess.name.lexeme;
if (this.context.get(contextName) === undefined) {
this.errors.push({
message: `Context access might be invalid: ${contextName}`,
range: contextAccess.name.range,
severity: "warning"
});
}
return;
}
while (s.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const idx = s.pop()!;
const key = idx.coerceString();
const v: ExpressionData | undefined = contextValue.get(key);
if (v === undefined) {
if (contextValue instanceof DescriptionDictionary && !contextValue.complete) {
// If the context dictionary is not complete, we cannot validate the expression
return;
}
this.errors.push({
range: {
start: contextAccess.name.range.start,
end: (indexAccess.index as Literal).token.range.end
},
message: `Context access might be invalid: ${key}`,
severity: "warning"
});
return;
}
if (!(v instanceof Dictionary)) {
return;
}
contextValue = v;
}
}
visitFunctionCall(functionCall: FunctionCall) {
for (const arg of functionCall.args) {
this._validate(arg);
}
}
}
@@ -174,7 +174,7 @@ jobs:
expect(result).toEqual<Hover>({
contents:
'Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`. This function calculates an individual SHA-256 hash for each matched file, and then uses those hashes to calculate a final SHA-256 hash for the set of files. If the `path` pattern does not match any files, this returns an empty string. For more information about SHA-256, see "[SHA-2](https://wikipedia.org/wiki/SHA-2)."\n\nYou can use pattern matching characters to match file names. Pattern matching is case-insensitive on Windows. For more information about supported pattern matching characters, see "[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet)."',
"Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`.",
range: {
start: {line: 5, character: 22},
end: {line: 5, character: 31}
@@ -1,4 +1,4 @@
import {DescriptionDictionary} from "@actions/expressions/.";
import {DescriptionDictionary} from "@actions/expressions";
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config";
import {registerLogger} from "./log";
@@ -46,52 +46,6 @@ jobs:
]);
});
it("access invalid context field in short-circuited expression", async () => {
const result = await validate(
createDocument(
"wf.yaml",
`on: push
run-name: name-\${{ github.does-not-exist || github.does-not-exist2 }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo`
)
);
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 69,
line: 1
},
start: {
character: 15,
line: 1
}
},
severity: DiagnosticSeverity.Warning
},
{
message: "Context access might be invalid: does-not-exist2",
range: {
end: {
character: 69,
line: 1
},
start: {
character: 15,
line: 1
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("partial skip access invalid context on incomplete", async () => {
const contextProviderConfig: ContextProviderConfig = {
getContext: (context: string) => {
+2 -4
View File
@@ -15,9 +15,7 @@ import {ActionMetadata, ActionReference} from "./action";
import {ContextProviderConfig} from "./context-providers/config";
import {Mode, getContext} from "./context-providers/default";
import {WorkflowContext, getWorkflowContext} from "./context/workflow-context";
import {wrapDictionary} from "./expression-validation/error-dictionary";
import {ValidationEvaluator} from "./expression-validation/evaluator";
import {validatorFunctions} from "./expression-validation/functions";
import {ValidationVisitor} from "./expression-validation/visitor";
import {error} from "./log";
import {findToken} from "./utils/find-token";
import {mapRange} from "./utils/range";
@@ -205,7 +203,7 @@ async function validateExpression(
const context = await getContext(namedContexts, contextProviderConfig, workflowContext, Mode.Validation);
const e = new ValidationEvaluator(expr, wrapDictionary(context), validatorFunctions);
const e = new ValidationVisitor(expr, context);
e.validate();
diagnostics.push(
@@ -9,30 +9,15 @@ import {getWorkflowSchema} from "@actions/workflow-parser/workflows/workflow-sch
import {Value} from "./config";
import {stringsToValues} from "./strings-to-values";
export enum DefinitionValueMode {
/**
* We're getting completion options for a parent token
* foo:
* ba|
*/
Parent,
/**
* We're getting completion options for a key token. For example:
* foo: |
*/
Key
}
export function definitionValues(def: Definition, indentation: string, mode: DefinitionValueMode): Value[] {
export function definitionValues(def: Definition, indentation: string): Value[] {
const schema = getWorkflowSchema();
if (def instanceof MappingDefinition) {
return mappingValues(def, schema.definitions, indentation, mode);
return mappingValues(def, schema.definitions, indentation);
}
if (def instanceof OneOfDefinition) {
return oneOfValues(def, schema.definitions, indentation, mode);
return oneOfValues(def, schema.definitions, indentation);
}
if (def instanceof BooleanDefinition) {
@@ -51,7 +36,7 @@ export function definitionValues(def: Definition, indentation: string, mode: Def
if (def instanceof SequenceDefinition) {
const itemDef = schema.getDefinition(def.itemType);
if (itemDef) {
return definitionValues(itemDef, indentation, mode);
return definitionValues(itemDef, indentation);
}
}
@@ -61,8 +46,7 @@ export function definitionValues(def: Definition, indentation: string, mode: Def
function mappingValues(
mappingDefinition: MappingDefinition,
definitions: {[key: string]: Definition},
indentation: string,
mode: DefinitionValueMode
indentation: string
): Value[] {
const properties: Value[] = [];
for (const [key, value] of Object.entries(mappingDefinition.properties)) {
@@ -76,36 +60,15 @@ function mappingValues(
if (typeDef) {
switch (typeDef.definitionType) {
case DefinitionType.Sequence:
if (mode == DefinitionValueMode.Key) {
insertText = `\n${indentation}${key}:\n${indentation}${indentation}- `;
} else {
insertText = `${key}:\n${indentation}- `;
}
insertText = `${key}:\n${indentation}- `;
break;
case DefinitionType.Mapping:
if (mode == DefinitionValueMode.Key) {
insertText = `\n${indentation}${key}:\n${indentation}${indentation}`;
} else {
insertText = `${key}:\n${indentation}`;
}
insertText = `${key}:\n${indentation}`;
break;
case DefinitionType.OneOf:
if (mode == DefinitionValueMode.Parent) {
insertText = `${key}: `;
} else {
// No special insertText in this case
}
break;
case DefinitionType.String:
case DefinitionType.Boolean:
if (mode == DefinitionValueMode.Key) {
insertText = `\n${indentation}${key}: `;
} else {
insertText = `${key}: `;
}
// No special insertText in this case
break;
default:
@@ -126,12 +89,11 @@ function mappingValues(
function oneOfValues(
oneOfDefinition: OneOfDefinition,
definitions: {[key: string]: Definition},
indentation: string,
mode: DefinitionValueMode
indentation: string
): Value[] {
const values: Value[] = [];
for (const key of oneOfDefinition.oneOf) {
values.push(...definitionValues(definitions[key], indentation, mode));
values.push(...definitionValues(definitions[key], indentation));
}
return distinctValues(values);
}
+3 -8
View File
@@ -1,10 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"packages": [
"expressions",
"workflow-parser",
"languageservice",
"languageserver"
],
"version": "0.3.12"
}
"useWorkspaces": true,
"version": "0.3.1"
}
+84 -181
View File
@@ -135,7 +135,7 @@
},
"expressions": {
"name": "@actions/expressions",
"version": "0.3.12",
"version": "0.3.1",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.0.3",
@@ -395,11 +395,11 @@
},
"languageserver": {
"name": "@actions/languageserver",
"version": "0.3.12",
"version": "0.3.1",
"license": "MIT",
"dependencies": {
"@actions/languageservice": "^0.3.12",
"@actions/workflow-parser": "^0.3.12",
"@actions/languageservice": "^0.3.1",
"@actions/workflow-parser": "^0.3.1",
"@octokit/rest": "^19.0.7",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
@@ -678,14 +678,14 @@
},
"languageservice": {
"name": "@actions/languageservice",
"version": "0.3.12",
"version": "0.3.1",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.12",
"@actions/workflow-parser": "^0.3.12",
"@actions/expressions": "^0.3.1",
"@actions/workflow-parser": "^0.3.1",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
"vscode-uri": "^3.0.7",
"yaml": "^2.1.1"
},
"devDependencies": {
@@ -975,89 +975,16 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
"@babel/highlight": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/code-frame/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/code-frame/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/compat-data": {
"version": "7.20.1",
"dev": true,
@@ -1101,14 +1028,12 @@
"license": "MIT"
},
"node_modules/@babel/generator": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"version": "7.20.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.23.0",
"@babel/types": "^7.20.2",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
@@ -1146,34 +1071,31 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"version": "7.18.9",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"version": "7.19.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
"@babel/template": "^7.18.10",
"@babel/types": "^7.19.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1228,31 +1150,28 @@
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"version": "7.19.4",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.19.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -1279,13 +1198,12 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
"engines": {
@@ -1357,10 +1275,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"version": "7.20.3",
"dev": true,
"license": "MIT",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -1543,33 +1460,31 @@
}
},
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"version": "7.18.10",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10",
"@babel/types": "^7.18.10"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"version": "7.20.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.1",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.1",
"@babel/types": "^7.20.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -1578,13 +1493,12 @@
}
},
"node_modules/@babel/types": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"version": "7.20.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -4474,12 +4388,11 @@
}
},
"node_modules/axios": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"version": "1.1.3",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.4",
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -4650,12 +4563,11 @@
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"version": "3.0.2",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
@@ -5580,10 +5492,9 @@
"license": "MIT"
},
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"version": "3.1.8",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
@@ -6233,10 +6144,9 @@
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"version": "7.0.1",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -6282,9 +6192,7 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"version": "1.15.2",
"dev": true,
"funding": [
{
@@ -6292,6 +6200,7 @@
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -6743,10 +6652,9 @@
"license": "MIT"
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true
"version": "4.1.0",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/http-proxy-agent": {
"version": "5.0.0",
@@ -7028,10 +6936,9 @@
}
},
"node_modules/ip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
"dev": true
"version": "2.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/is-arrayish": {
"version": "0.2.1",
@@ -7140,9 +7047,8 @@
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -11084,9 +10990,8 @@
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -11504,9 +11409,9 @@
"license": "MIT"
},
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz",
"integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA=="
},
"node_modules/walk-up-path": {
"version": "1.0.0",
@@ -11564,10 +11469,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"version": "1.2.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -11763,9 +11667,8 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
"integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
"version": "2.1.3",
"license": "ISC",
"engines": {
"node": ">= 14"
}
@@ -11816,10 +11719,10 @@
},
"workflow-parser": {
"name": "@actions/workflow-parser",
"version": "0.3.12",
"version": "0.3.1",
"license": "MIT",
"dependencies": {
"@actions/expressions": "^0.3.12",
"@actions/expressions": "^0.3.1",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
-1
View File
@@ -1 +0,0 @@
Release 0.3.5
@@ -1,32 +0,0 @@
#!/bin/bash
# this script is used to generate release notes for a given release
# first argument is the pull request id for the last release
# the second is the new release number
# the script then grabs every pull request merged since that pull request
# and outputs a string of release notes
# get the new release number
NEW_RELEASE=$2
echo "Generating release notes for $NEW_RELEASE"
# get the last release pull request id
LAST_RELEASE_PR=$1
#get when the last release was merged
LAST_RELEASE_MERGED_AT=$(gh pr view $LAST_RELEASE_PR --repo actions/languageservices --json mergedAt | jq -r '.mergedAt')
CHANGELIST=$(gh pr list --repo actions/languageservices --base main --state merged --json title --search "merged:>$LAST_RELEASE_MERGED_AT -label:no-release")
# store the release notes in a variable so we can use it later
echo "Release $NEW_RELEASE" >> releasenotes.md
echo $CHANGELIST | jq -r '.[].title' | while read line; do
echo " - $line" >> releasenotes.md
done
echo " "
-24
View File
@@ -1,24 +0,0 @@
#!/bin/bash
VERSION=$(cat lerna.json | jq -r '.version')
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
if [ "$1" == "major" ]; then
MAJOR=$((MAJOR+1))
MINOR=0
PATCH=0
elif [ "$1" == "minor" ]; then
MINOR=$((MINOR+1))
PATCH=0
elif [ "$1" == "patch" ]; then
PATCH=$((PATCH+1))
else
echo "Invalid version type. Use 'major', 'minor' or 'patch'"
exit 1
fi
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo $NEW_VERSION
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
);
```
`convertWorkflowTemplate` then takes that intermediate representation and converts it to a [`WorkflowTemplate`](./src/model/workflow-template.ts) object, which is a more convenient representation for working with workflows.
`convertWorkflowTemplate` then takes that intermediate representation and converts it to a [`WorkflowTemplate`](./src/workflow-template.ts) object, which is a more convenient representation for working with workflows.
```typescript
const workflowTemplate = await convertWorkflowTemplate(result.context, result.value);
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/workflow-parser",
"version": "0.3.12",
"version": "0.3.1",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -43,7 +43,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.12",
"@actions/expressions": "^0.3.1",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+4 -11
View File
@@ -59,24 +59,17 @@ export function convertOn(context: TemplateContext, token: TemplateToken): Event
// All other events are defined as mappings. During schema validation we already ensure that events
// receive only known keys, so here we can focus on the values and whether they are valid.
const eventToken = item.value.assertMapping(`event ${eventName}`);
if (eventName === "workflow_call") {
result.workflow_call = convertEventWorkflowCall(context, eventToken);
continue;
}
if (eventName === "workflow_dispatch") {
result.workflow_dispatch = convertEventWorkflowDispatchInputs(context, eventToken);
continue;
}
result[eventName] = {
...convertPatternFilter("branches", eventToken),
...convertPatternFilter("tags", eventToken),
...convertPatternFilter("paths", eventToken),
...convertFilter("types", eventToken),
...convertFilter("workflows", eventToken)
...convertFilter("workflows", eventToken),
// workflow_call and workflow_dispatch share input parsing
...convertEventWorkflowDispatchInputs(context, eventToken),
...convertEventWorkflowCall(context, eventToken)
};
}
+2 -6
View File
@@ -50,7 +50,7 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
let id: StringToken | undefined;
let name: ScalarToken | undefined;
let uses: StringToken | undefined;
let continueOnError: boolean | ScalarToken | undefined;
let continueOnError: boolean | undefined;
let env: MappingToken | undefined;
const ifCondition = new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined);
for (const item of mapping) {
@@ -78,11 +78,7 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
env = item.value.assertMapping("step env");
break;
case "continue-on-error":
if (!item.value.isExpression) {
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
} else {
continueOnError = item.value.assertScalar("steps item continue-on-error");
}
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
}
}
@@ -1,9 +1,7 @@
import {TemplateContext} from "../../templates/template-context";
import {MappingToken, TemplateToken} from "../../templates/tokens";
import {isMapping} from "../../templates/tokens/type-guards";
import {SecretConfig, WorkflowCallConfig, InputConfig, InputType} from "../workflow-template";
import {convertStringList} from "./string-list";
import {ScalarToken} from "../../templates/tokens/scalar-token";
import {SecretConfig, WorkflowCallConfig} from "../workflow-template";
export function convertEventWorkflowCall(context: TemplateContext, token: MappingToken): WorkflowCallConfig {
const result: WorkflowCallConfig = {};
@@ -13,7 +11,7 @@ export function convertEventWorkflowCall(context: TemplateContext, token: Mappin
switch (key.value) {
case "inputs":
result.inputs = convertWorkflowInputs(context, item.value.assertMapping("workflow dispatch inputs"));
// Ignore, these are handled by convertEventWorkflowDispatchInputs
break;
case "secrets":
@@ -29,94 +27,6 @@ export function convertEventWorkflowCall(context: TemplateContext, token: Mappin
return result;
}
export function convertWorkflowInputs(
context: TemplateContext,
token: MappingToken
): {
[inputName: string]: InputConfig;
} {
const result: {[inputName: string]: InputConfig} = {};
for (const item of token) {
const inputName = item.key.assertString("input name");
const inputMapping = item.value.assertMapping("input configuration");
result[inputName.value] = convertWorkflowInput(context, inputMapping);
}
return result;
}
export function convertWorkflowInput(context: TemplateContext, token: MappingToken): InputConfig {
const result: InputConfig = {
type: InputType.string // Default to string
};
let defaultValue: undefined | ScalarToken;
for (const item of token) {
const key = item.key.assertString("workflow dispatch input key");
switch (key.value) {
case "description":
result.description = item.value.assertString("input description").value;
break;
case "required":
result.required = item.value.assertBoolean("input required").value;
break;
case "default":
defaultValue = item.value.assertScalar("input default");
break;
case "type":
result.type = InputType[item.value.assertString("input type").value as keyof typeof InputType];
break;
case "options":
result.options = convertStringList("input options", item.value.assertSequence("input options"));
break;
default:
context.error(item.key, `Invalid key '${key.value}'`);
}
}
// Validate default value
if (defaultValue !== undefined && !defaultValue.isExpression) {
try {
switch (result.type) {
case InputType.boolean:
result.default = defaultValue.assertBoolean("input default").value;
break;
case InputType.string:
case InputType.choice:
case InputType.environment:
result.default = defaultValue.assertString("input default").value;
break;
}
} catch (e) {
context.error(defaultValue, e);
}
}
// Validate `options` for `choice` type
if (result.type === InputType.choice) {
if (result.options === undefined || result.options.length === 0) {
context.error(token, "Missing 'options' for choice input");
}
} else {
if (result.options !== undefined) {
context.error(token, "Input type is not 'choice', but 'options' is defined");
}
}
return result;
}
function convertWorkflowCallSecrets(
context: TemplateContext,
token: MappingToken
@@ -86,7 +86,7 @@ type BaseStep = {
id: string;
name?: ScalarToken;
if: BasicExpressionToken;
"continue-on-error"?: boolean | ScalarToken;
"continue-on-error"?: boolean;
env?: MappingToken;
};
@@ -158,7 +158,7 @@ export type WorkflowDispatchConfig = {
};
export type WorkflowCallConfig = {
inputs?: {[inputName: string]: InputConfig & {default?: string | boolean | number | ScalarToken}};
inputs?: {[inputName: string]: InputConfig};
secrets?: {[secretName: string]: SecretConfig};
// TODO - these are supported in C# and Go but not in TS yet
// outputs: { [outputName: string]: OutputConfig }
+3 -9
View File
@@ -576,9 +576,7 @@
"merge-group-mapping": {
"mapping": {
"properties": {
"types": "merge-group-activity",
"branches": "event-branches",
"branches-ignore": "event-branches-ignore"
"types": "merge-group-activity"
}
}
},
@@ -1183,7 +1181,7 @@
]
},
"workflow-run-activity": {
"description": "The types of workflow run activity that trigger the workflow. Supported activity types: `completed`, `requested`, `in_progress`.",
"description": "The types of workflow run activity that trigger the workflow. Suupported activity types: `completed`, `requested`, `in_progress`.",
"one-of": [
"workflow-run-activity-type",
"workflow-run-activity-types"
@@ -1515,10 +1513,6 @@
"type": "permission-level-any",
"description": "Actions workflows, workflow runs, and artifacts."
},
"attestations": {
"type": "permission-level-any",
"description": "Artifact attestations."
},
"checks": {
"type": "permission-level-any",
"description": "Check runs and check suites."
@@ -2493,7 +2487,7 @@
"string": {
"require-non-empty": true
},
"description": "Use `shell` to override the default shell settings in the runner's operating system. You can use built-in shell keywords, or you can define a custom set of shell options. The shell command that is run internally executes a temporary file that contains the commands specified in `run`."
"description": "Use `shell` to override the default shell settings in the runner's operating system. You can use built-in shell keywords, or you can define a custom set of shell options. The shell command that is run internally executes a temporary file that contains the comands specified in `run`."
},
"working-directory": {
"string": {
+1 -14
View File
@@ -72,13 +72,7 @@ on:
- edited
- deleted
merge_group:
branches:
- master
- main
branches-ignore:
- develop
types:
- checks_requested
types: checks_requested
milestone:
types:
- created
@@ -319,13 +313,6 @@ jobs:
]
},
"merge_group": {
"branches": [
"master",
"main"
],
"branches-ignore": [
"develop"
],
"types": [
"checks_requested"
]