Compare commits

..

74 Commits

Author SHA1 Message Date
Crystal Tenn 484b6eb423 add insecure_ssl set to 1 to reduce security checks for certificates 2023-04-19 16:36:59 -04:00
Crystal Tenn dd8930fd74 Merge pull request #24 from actions/ketchup/fixReusableWorkflowRef
Fix Reusable Workflow Reference for Windows by using vscodeURI instead of path.join
2023-04-19 11:07:04 -04:00
Crystal Tenn 2449e5cea1 make variable names more specific 2023-04-18 18:16:55 -04:00
Crystal Tenn dba3cf5d96 remove unused reference to path 2023-04-18 18:13:52 -04:00
Crystal Tenn 804f83828f fix path reference for reusable workflows and use vscodeURI instead 2023-04-18 17:30:53 -04:00
Crystal Tenn cf2fd6332f Merge pull request #22 from actions/release/0.3.3
Release version 0.3.3
2023-04-14 15:06:27 -04:00
GitHub Actions 51649f27f8 Release extension version 0.3.3 2023-04-14 19:05:30 +00:00
Jonathan Tamsut 74db91e276 Merge pull request #20 from actions/jtamsut/auto-complete-for-mappings
Handle auto-completion for string and boolean mappings
2023-04-14 12:03:28 -07:00
Jonathan Tamsut a1d81c730f yarn prettier 2023-04-14 10:29:20 -07:00
Jonathan Tamsut 18d1bd9734 fix case bug 2023-04-14 10:22:42 -07:00
Jonathan Tamsut fc6a1d3e0c Merge branch 'main' into jtamsut/auto-complete-for-mappings 2023-04-14 09:51:39 -07:00
Jonathan Tamsut 6df34a78ce Update languageservice/src/value-providers/definition.ts
Co-authored-by: Josh Gross <joshmgross@github.com>
2023-04-14 09:51:27 -07:00
Liela Rotschy aee5c2b919 Merge pull request #21 from actions/lrotschy/add-project-board-link
Add project board link to CONTRIBUTING.md
2023-04-14 09:54:11 -06:00
Liela Rotschy 8cdfe810db Add project board link to CONTRIBUTING.md 2023-04-14 08:03:11 -06:00
Jonathan Tamsut 17a680df41 Merge branch 'jtamsut/auto-complete-for-mappings' of https://github.com/actions/languageservices into jtamsut/auto-complete-for-mappings 2023-04-13 15:30:18 -07:00
Jonathan Tamsut 11d3fc25ee run prettier 2023-04-13 15:26:31 -07:00
Jonathan Tamsut d2783ed733 Merge branch 'main' into jtamsut/auto-complete-for-mappings 2023-04-13 15:22:22 -07:00
Jonathan Tamsut 6d0f74e38b fix typo 2023-04-13 15:21:36 -07:00
Jonathan Tamsut 2ecbeafacb add tests 2023-04-13 15:21:23 -07:00
Jonathan Tamsut a4d3fb1a3e add newline to boolean 2023-04-13 10:05:39 -07:00
Beth Brennan 83cac82450 Merge pull request #16 from actions/elbrenn/contributing-links
Fix links in CONTRIBUTING.md
2023-04-12 18:39:55 -04:00
Jonathan Tamsut fc2bacfcdc add break 2023-04-12 15:37:42 -07:00
Jonathan Tamsut 7d8a7c11a6 handle string and boolean mappings 2023-04-12 15:26:17 -07:00
Felipe Suero e37d7620d6 Merge pull request #19 from actions/felipesu19-on-error-tokens
Fixes errors around "on-error" token parsing
2023-04-12 11:45:38 -04:00
Felipe Suero 6e8cbc3e8c prettify 2023-04-12 11:32:38 -04:00
Felipe Suero d0916938ce Remove TokenType 2023-04-12 11:26:59 -04:00
Felipe Suero 488879804f prettify 2023-04-11 13:56:02 -04:00
Felipe Suero 736dd1a66c add scalar handling 2023-04-11 13:54:53 -04:00
Felipe Suero d58deaf097 Remove branch 2023-04-11 10:12:38 -04:00
Felipe Suero f4a32c43cf token 2023-04-11 10:05:30 -04:00
Beth Brennan 5aed7594cf Merge pull request #17 from actions/release/0.3.2
Release version 0.3.2
2023-04-07 13:41:58 -04:00
GitHub Actions 81094dc942 Release extension version 0.3.2 2023-04-07 17:40:15 +00:00
Beth Brennan f29fffce7e Fix links in CONTRIBUTING.md 2023-04-07 13:28:49 -04:00
Felipe Suero 8fe871750e Merge pull request #13 from actions/felipesu19-fix-workflow-token
Felipesu19 fix workflow token
2023-04-06 16:57:16 -04:00
Felipe Suero 709d0d73c6 Merge branch 'main' into felipesu19-fix-workflow-token 2023-04-06 16:49:19 -04:00
Beth Brennan febac16edd Merge pull request #1 from actions/dependabot/npm_and_yarn/http-cache-semantics-4.1.1
Bump http-cache-semantics from 4.1.0 to 4.1.1
2023-04-06 16:48:41 -04:00
Felipe Suero 1ffef93f4c Merge branch 'main' into felipesu19-fix-workflow-token 2023-04-06 16:46:08 -04:00
Felipe Suero 41b8fa9231 Create alternate logic branch for workflow-call 2023-04-06 16:45:45 -04:00
Beth Brennan 053accfafc Merge branch 'main' into dependabot/npm_and_yarn/http-cache-semantics-4.1.1 2023-04-06 16:42:23 -04:00
Christopher Schleiden fe25433a45 Merge pull request #15 from actions/cschleiden/improve-expression-validation2
Improve expression validation for short-circuiting expressions
2023-04-05 16:05:59 -07:00
Beth Brennan 0ee008991d Merge pull request #14 from actions/elbrenn/codeowners
Update CODEOWNERS
2023-04-05 16:57:14 -04:00
Christopher Schleiden cf4dce7f71 Improve expression validation for short-circuiting expressions 2023-04-05 13:54:08 -07:00
Beth Brennan 4b479b0296 Update CODEOWNERS 2023-04-05 16:44:44 -04:00
Liela Rotschy 53f5a4ce69 Merge pull request #12 from actions/lrotschy/add-branches-to-merge-group-schema
Add branches to merge-group schema
2023-04-05 11:33:25 -06:00
Felipe Suero d08fed3cf5 duplicate logic 2023-04-05 12:34:50 -04:00
Liela Rotschy d5ef2f1539 Add branches to merge-group schema 2023-04-05 09:23:50 -06:00
Christopher Schleiden fb962e6c47 Merge pull request #11 from actions/release/0.3.1
Release version 0.3.1
2023-03-28 12:13:26 -07:00
GitHub Actions e3db56d6ed Release extension version 0.3.1 2023-03-28 19:12:56 +00:00
Beth Brennan 872c873c9d Merge pull request #10 from actions/release/0.3.0
Release version 0.3.0
2023-03-27 17:13:13 -04:00
GitHub Actions 29e676fad1 Release extension version 0.3.0 2023-03-27 20:49:33 +00:00
Beth Brennan e8b3c20aca Merge pull request #9 from actions/elbrenn/actions-provider
Wrap fetchActionsMetadata in provider checking sessionToken
2023-03-27 16:47:05 -04:00
Beth Brennan 50f9c04a91 Wrap fetchActionsMetadata in provider checking sessionToken 2023-03-27 14:00:58 -04:00
Christopher Schleiden 9c4b8a4c1c Merge pull request #8 from actions/joshmgross/update-codeowners
Update CODEOWNERS
2023-03-23 16:37:45 -07:00
Josh Gross 6b1e4ff115 Update CODEOWNERS 2023-03-23 19:07:33 -04:00
Christopher Schleiden b79d0e8e47 Merge pull request #7 from actions/cschleiden/set-publish-config
Set publishConfig for individual packages
2023-03-23 14:32:15 -07:00
Christopher Schleiden c538615cc7 Set publishConfig 2023-03-23 14:30:31 -07:00
Christopher Schleiden 39fa2c1e14 Merge pull request #6 from actions/cschleiden/ignore-browser-playground
Ignore browser playground for versioning and publishing
2023-03-23 14:11:23 -07:00
Christopher Schleiden e229500fea Restore version 2023-03-23 14:06:38 -07:00
Christopher Schleiden 172f450745 Remove browser playground from workspace 2023-03-23 14:06:04 -07:00
Christopher Schleiden 266bebde2b Ignore browser playground for versioning and publishing 2023-03-23 14:03:42 -07:00
Christopher Schleiden 895f62ba7e Merge pull request #5 from actions/release/0.2.0
Release version 0.2.0
2023-03-23 13:42:13 -07:00
GitHub Actions d86a0ca08f Release extension version 0.2.0 2023-03-23 20:35:44 +00:00
Christopher Schleiden 6e5149bc79 Merge pull request #4 from actions/cschleiden/publish-to-npm
Publish packages to npm
2023-03-23 13:34:11 -07:00
Christopher Schleiden 99dd997d88 Publish packages to npm 2023-03-23 13:08:28 -07:00
Christopher Schleiden 3efd7934b4 Add permissions to job 2023-03-23 12:42:56 -07:00
Christopher Schleiden 130f22d937 Use org scope 2023-03-23 12:34:11 -07:00
Christopher Schleiden 019717a8cd Authorize for publish 2023-03-23 12:26:24 -07:00
Christopher Schleiden a0eb7ac824 Do not try to tag again 2023-03-23 12:18:02 -07:00
Christopher Schleiden d670d73091 Add --yes 2023-03-23 12:06:47 -07:00
Christopher Schleiden 7f3d6f7382 Use hard-coded version for publish 2023-03-23 11:50:02 -07:00
Christopher Schleiden ee2e82a4bd Trigger when lerna.json changes 2023-03-23 11:40:48 -07:00
Christopher Schleiden d65c105d83 Fix release workflow 2023-03-23 11:40:21 -07:00
Christopher Schleiden bc1fb6e6d6 Merge pull request #3 from actions/release/0.1.2
Release version 0.1.2
2023-03-23 11:38:41 -07:00
dependabot[bot] 1727735bd4 Bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-23 17:07:03 +00:00
31 changed files with 492 additions and 2452 deletions
+1 -1
View File
@@ -1 +1 @@
* @github/c2c-actions-experience-parser-reviewers
* @actions/actions-workflow-development-reviewers
+23 -20
View File
@@ -5,7 +5,7 @@ on:
branches:
- main
paths:
- package.json
- lerna.json
workflow_dispatch:
inputs:
@@ -30,7 +30,7 @@ jobs:
uses: actions/github-script@v6
with:
script: |
const version = '${{ inputs.version }}' || require('./package.json').version;
const version = '${{ inputs.version }}' || require('./lerna.json').version;
// Find a release for that version
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
@@ -59,12 +59,10 @@ jobs:
permissions:
contents: write
packages: write
env:
EXT_VERSION: "" # will be set in the workflow
outputs:
version: ${{ env.EXT_VERSION }}
PKG_VERSION: "" # will be set in the workflow
steps:
- uses: actions/checkout@v3
@@ -73,21 +71,15 @@ jobs:
with:
node-version: 16.x
cache: "npm"
registry-url: "https://npm.pkg.github.com"
scope: '@actions'
- name: Parse version from package.json
- name: Parse version from lerna.json
run: |
echo "EXT_VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
echo "PKG_VERSION=$(node -p -e "require('./lerna.json').version")" >> $GITHUB_ENV
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish packages
run: |
lerna publish from-git
- name: Create release and upload release asset
- name: Create release
uses: actions/github-script@v6
with:
script: |
@@ -96,11 +88,22 @@ jobs:
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: "release-v${{ env.EXT_VERSION }}",
name: "v${{ env.EXT_VERSION }}",
tag_name: "release-v${{ env.PKG_VERSION }}",
name: "v${{ env.PKG_VERSION }}",
draft: false,
prerelease: false
});
core.summary.addLink(`Release v${{ env.EXT_VERSION }}`, release.data.html_url);
await core.summary.write();
core.summary.addLink(`Release v${{ env.PKG_VERSION }}`, release.data.html_url);
await core.summary.write();
- name: setup authentication
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish packages
run: |
lerna publish ${{ env.PKG_VERSION }} --yes --no-git-reset --no-git-tag-version
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
-2
View File
@@ -1,2 +0,0 @@
@github:registry=https://npm.pkg.github.com
+10 -2
View File
@@ -8,6 +8,8 @@ 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.
@@ -21,7 +23,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.md).
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).
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.
@@ -60,4 +62,10 @@ 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)
- [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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "browser-playground",
"version": "0.1.2",
"version": "0.2.0",
"description": "",
"private": true,
"main": "index.js",
"type": "module",
"dependencies": {
"@actions/languageserver": "^0.1.2",
"@actions/languageserver": "^0.2.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
"monaco-languageclient": "^4.0.3",
+4 -1
View File
@@ -1,9 +1,12 @@
{
"name": "@actions/expressions",
"version": "0.1.2",
"version": "0.3.3",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
"publishConfig": {
"access": "public"
},
"exports": {
".": {
"import": "./dist/index.js"
+1 -1
View File
@@ -32,7 +32,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
return this.eval(this.n);
}
private eval(n: Expr): data.ExpressionData {
protected eval(n: Expr): data.ExpressionData {
return n.accept(this);
}
+6 -3
View File
@@ -1,10 +1,13 @@
{
"name": "@actions/languageserver",
"version": "0.1.2",
"version": "0.3.3",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
"publishConfig": {
"access": "public"
},
"exports": {
".": {
"import": "./dist/index.js"
@@ -40,8 +43,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/languageservice": "^0.1.2",
"@actions/workflow-parser": "^0.1.2",
"@actions/languageservice": "^0.3.3",
"@actions/workflow-parser": "^0.3.3",
"@octokit/rest": "^19.0.7",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
+4 -1
View File
@@ -3,6 +3,9 @@ import {Octokit} from "@octokit/rest";
export function getClient(token: string, userAgent?: string): Octokit {
return new Octokit({
auth: token,
userAgent: userAgent || `GitHub Actions Language Server`
userAgent: userAgent || `GitHub Actions Language Server`,
request: {
insecure_ssl: 1
}
});
}
+2 -8
View File
@@ -26,7 +26,7 @@ import {getFileProvider} from "./file-provider";
import {InitializationOptions, RepositoryContext} from "./initializationOptions";
import {onCompletion} from "./on-completion";
import {ReadFileRequest, Requests} from "./request";
import {fetchActionMetadata} from "./utils/action-metadata";
import {getActionsMetadataProvider} from "./utils/action-metadata";
import {TTLCache} from "./utils/cache";
import {timeOperation} from "./utils/timer";
import {valueProviders} from "./value-providers";
@@ -108,13 +108,7 @@ export function initConnection(connection: Connection) {
const config: ValidationConfig = {
valueProviderConfig: valueProviders(client, repoContext, cache),
contextProviderConfig: contextProviders(client, repoContext, cache),
fetchActionMetadata: async action => {
if (client) {
return await fetchActionMetadata(client, cache, action);
}
return undefined;
},
actionsMetadataProvider: getActionsMetadataProvider(client, cache),
fileProvider: getFileProvider(client, cache, repoContext?.workspaceUri, async path => {
return await connection.sendRequest(Requests.ReadFile, {path} satisfies ReadFileRequest);
})
+5 -2
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 vscodeURI from "vscode-uri/lib/umd";
export function getFileProvider(
client: Octokit | undefined,
@@ -31,7 +31,10 @@ export function getFileProvider(
throw new Error("Local file references are not supported with this configuration");
}
const file = await readFile(path.join(workspace, ref.path));
const workspaceURI = vscodeURI.URI.parse(workspace);
const refURI = vscodeURI.Utils.joinPath(workspaceURI, ref.path);
const file = await readFile(refURI.toString());
if (!file) {
throw new Error(`File not found: ${ref.path}`);
}
@@ -1,10 +1,24 @@
import {actionIdentifier, ActionMetadata, ActionReference} from "@actions/languageservice/action";
import {ActionsMetadataProvider} from "@actions/languageservice";
import {error} from "@actions/languageservice/log";
import {Octokit, RestEndpointMethodTypes} from "@octokit/rest";
import {parse} from "yaml";
import {TTLCache} from "./cache";
import {errorMessage, errorStatus} from "./error";
export function getActionsMetadataProvider(
client: Octokit | undefined,
cache: TTLCache
): ActionsMetadataProvider | undefined {
if (!client) {
return undefined;
}
return {
fetchActionMetadata: async action => fetchActionMetadata(client, cache, action)
};
}
export async function fetchActionMetadata(
client: Octokit,
cache: TTLCache,
+6 -3
View File
@@ -1,10 +1,13 @@
{
"name": "@actions/languageservice",
"version": "0.1.2",
"version": "0.3.3",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
"publishConfig": {
"access": "public"
},
"exports": {
".": {
"import": "./dist/index.js"
@@ -41,8 +44,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.1.2",
"@actions/workflow-parser": "^0.1.2",
"@actions/expressions": "^0.3.3",
"@actions/workflow-parser": "^0.3.3",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.7",
+11
View File
@@ -474,4 +474,15 @@ jobs:
expect(result.filter(x => x.label === "concurrency").map(x => x.textEdit?.newText)).toEqual(["concurrency"]);
});
});
it("adds a new line and indentation for mapping keys", 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: "]);
});
});
@@ -0,0 +1,61 @@
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!;
}
}
+1 -1
View File
@@ -3,5 +3,5 @@ export {ContextProviderConfig} from "./context-providers/config";
export {documentLinks} from "./document-links";
export {hover} from "./hover";
export {Logger, LogLevel, registerLogger, setLogLevel} from "./log";
export {validate, ValidationConfig} from "./validate";
export {validate, ValidationConfig, ActionsMetadataProvider} from "./validate";
export {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
+2 -2
View File
@@ -14,7 +14,7 @@ export async function validateAction(
step: Step | undefined,
config: ValidationConfig | undefined
): Promise<void> {
if (!isMapping(stepToken) || !step || !isActionStep(step) || !config?.fetchActionMetadata) {
if (!isMapping(stepToken) || !step || !isActionStep(step) || !config?.actionsMetadataProvider) {
return;
}
@@ -23,7 +23,7 @@ export async function validateAction(
return;
}
const actionMetadata = await config.fetchActionMetadata(action);
const actionMetadata = await config.actionsMetadataProvider.fetchActionMetadata(action);
if (actionMetadata === undefined) {
diagnostics.push({
severity: DiagnosticSeverity.Error,
+81 -64
View File
@@ -14,75 +14,77 @@ beforeEach(() => {
});
const validationConfig: ValidationConfig = {
fetchActionMetadata: (ref: ActionReference) => {
let metadata: ActionMetadata | undefined = undefined;
switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
case "actions/checkout@v3":
metadata = {
name: "Checkout",
description: "Checkout a Git repository at a particular version",
inputs: {
repository: {
description: "Repository name with owner",
default: "${{ github.repository }}"
actionsMetadataProvider: {
fetchActionMetadata: (ref: ActionReference) => {
let metadata: ActionMetadata | undefined = undefined;
switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
case "actions/checkout@v3":
metadata = {
name: "Checkout",
description: "Checkout a Git repository at a particular version",
inputs: {
repository: {
description: "Repository name with owner",
default: "${{ github.repository }}"
}
}
}
};
break;
case "actions/setup-node@v1":
metadata = {
name: "Setup Node.js environment",
description:
"Setup a Node.js environment by adding problem matchers and optionally downloading and adding it to the PATH.",
inputs: {
version: {
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
deprecationMessage:
"The version property will not be supported after October 1, 2019. Use node-version instead"
};
break;
case "actions/setup-node@v1":
metadata = {
name: "Setup Node.js environment",
description:
"Setup a Node.js environment by adding problem matchers and optionally downloading and adding it to the PATH.",
inputs: {
version: {
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
deprecationMessage:
"The version property will not be supported after October 1, 2019. Use node-version instead"
}
}
}
};
break;
case "actions/deploy-pages@main":
metadata = {
name: "Deploy GitHub Pages site",
description: "A GitHub Action to deploy an artifact as a GitHub Pages site",
inputs: {
token: {
required: true,
description: "token to use",
default: "${{ github.token }}"
};
break;
case "actions/deploy-pages@main":
metadata = {
name: "Deploy GitHub Pages site",
description: "A GitHub Action to deploy an artifact as a GitHub Pages site",
inputs: {
token: {
required: true,
description: "token to use",
default: "${{ github.token }}"
}
}
}
};
break;
case "actions/cache@v1":
metadata = {
name: "Cache",
description: "Cache artifacts like dependencies and build outputs to improve workflow execution time",
inputs: {
path: {
description: "A directory to store and save the cache",
required: true
},
key: {
description: "An explicit key for restoring and saving the cache",
required: true
},
"restore-keys": {
description: "An ordered list of keys to use for restoring the cache if no cache hit occurred for key",
required: false
};
break;
case "actions/cache@v1":
metadata = {
name: "Cache",
description: "Cache artifacts like dependencies and build outputs to improve workflow execution time",
inputs: {
path: {
description: "A directory to store and save the cache",
required: true
},
key: {
description: "An explicit key for restoring and saving the cache",
required: true
},
"restore-keys": {
description: "An ordered list of keys to use for restoring the cache if no cache hit occurred for key",
required: false
}
}
}
};
break;
case "actions/action-no-input@v1":
metadata = {
name: "Action with no inputs",
description: "An action with no inputs"
};
};
break;
case "actions/action-no-input@v1":
metadata = {
name: "Action with no inputs",
description: "An action with no inputs"
};
}
return Promise.resolve(metadata);
}
return Promise.resolve(metadata);
}
};
@@ -101,6 +103,21 @@ jobs:
expect(result).toEqual([]);
});
it("no actionsMetadataProvider", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/does-not-exist@v3
`;
const config: ValidationConfig = {};
const result = await validate(createDocument("wf.yaml", input), config);
expect(result).toEqual([]);
});
it("action does not exist", async () => {
const input = `
on: push
@@ -46,6 +46,52 @@ 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) => {
+21 -27
View File
@@ -1,6 +1,6 @@
import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@actions/expressions";
import {Lexer, Parser} from "@actions/expressions";
import {Expr} from "@actions/expressions/ast";
import {isBasicExpression, isString, ParseWorkflowResult, WorkflowTemplate} from "@actions/workflow-parser";
import {ParseWorkflowResult, WorkflowTemplate, isBasicExpression, isString} from "@actions/workflow-parser";
import {ErrorPolicy} from "@actions/workflow-parser/model/convert";
import {splitAllowedContext} from "@actions/workflow-parser/templates/allowed-context";
import {BasicExpressionToken} from "@actions/workflow-parser/templates/tokens/basic-expression-token";
@@ -13,9 +13,10 @@ import {TextDocument} from "vscode-languageserver-textdocument";
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
import {ActionMetadata, ActionReference} from "./action";
import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {AccessError, wrapDictionary} from "./expression-validation/error-dictionary";
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 {error} from "./log";
import {findToken} from "./utils/find-token";
@@ -28,10 +29,14 @@ import {defaultValueProviders} from "./value-providers/default";
export type ValidationConfig = {
valueProviderConfig?: ValueProviderConfig;
contextProviderConfig?: ContextProviderConfig;
fetchActionMetadata?(action: ActionReference): Promise<ActionMetadata | undefined>;
actionsMetadataProvider?: ActionsMetadataProvider;
fileProvider?: FileProvider;
};
export type ActionsMetadataProvider = {
fetchActionMetadata(action: ActionReference): Promise<ActionMetadata | undefined>;
};
/**
* Validates a workflow file
*
@@ -198,28 +203,17 @@ async function validateExpression(
continue;
}
try {
const context = await getContext(namedContexts, contextProviderConfig, workflowContext, Mode.Validation);
const context = await getContext(namedContexts, contextProviderConfig, workflowContext, Mode.Validation);
const e = new Evaluator(expr, wrapDictionary(context), validatorFunctions);
e.evaluate();
const e = new ValidationEvaluator(expr, wrapDictionary(context), validatorFunctions);
e.validate();
// Any invalid context access would've thrown an error via the `ErrorDictionary`, for now we don't have to check the actual
// result of the evaluation.
} catch (e) {
if (e instanceof AccessError) {
diagnostics.push({
message: `Context access might be invalid: ${e.keyName}`,
severity: DiagnosticSeverity.Warning,
range: mapRange(expression.range)
});
} else if (e instanceof ExpressionEvaluationError) {
diagnostics.push({
message: `Expression might be invalid: ${e.message}`,
severity: DiagnosticSeverity.Error,
range: mapRange(expression.range)
});
}
}
diagnostics.push(
...e.errors.map(e => ({
message: e.message,
range: mapRange(expression.range),
severity: e.severity === "error" ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning
}))
);
}
}
@@ -71,6 +71,10 @@ function mappingValues(
// No special insertText in this case
break;
case DefinitionType.String:
case DefinitionType.Boolean:
insertText = `\n${indentation}${key}: `;
break;
default:
insertText = `${key}: `;
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.1.2"
"version": "0.3.3"
}
+58 -2295
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -5,8 +5,7 @@
"./expressions",
"./workflow-parser",
"./languageservice",
"./languageserver",
"./browser-playground"
"./languageserver"
],
"devDependencies": {
"lerna": "^6.0.3"
+5 -2
View File
@@ -1,9 +1,12 @@
{
"name": "@actions/workflow-parser",
"version": "0.1.2",
"version": "0.3.3",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
"publishConfig": {
"access": "public"
},
"exports": {
".": {
"import": "./dist/index.js"
@@ -40,7 +43,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.1.2",
"@actions/expressions": "^0.3.3",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+11 -4
View File
@@ -59,17 +59,24 @@ 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),
// workflow_call and workflow_dispatch share input parsing
...convertEventWorkflowDispatchInputs(context, eventToken),
...convertEventWorkflowCall(context, eventToken)
...convertFilter("workflows", eventToken)
};
}
+6 -2
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 | undefined;
let continueOnError: boolean | ScalarToken | undefined;
let env: MappingToken | undefined;
const ifCondition = new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined);
for (const item of mapping) {
@@ -78,7 +78,11 @@ function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: Templ
env = item.value.assertMapping("step env");
break;
case "continue-on-error":
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
if (!item.value.isExpression) {
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
} else {
continueOnError = item.value.assertScalar("steps item continue-on-error");
}
}
}
@@ -1,7 +1,9 @@
import {TemplateContext} from "../../templates/template-context";
import {MappingToken, TemplateToken} from "../../templates/tokens";
import {isMapping} from "../../templates/tokens/type-guards";
import {SecretConfig, WorkflowCallConfig} from "../workflow-template";
import {SecretConfig, WorkflowCallConfig, InputConfig, InputType} from "../workflow-template";
import {convertStringList} from "./string-list";
import {ScalarToken} from "../../templates/tokens/scalar-token";
export function convertEventWorkflowCall(context: TemplateContext, token: MappingToken): WorkflowCallConfig {
const result: WorkflowCallConfig = {};
@@ -11,7 +13,7 @@ export function convertEventWorkflowCall(context: TemplateContext, token: Mappin
switch (key.value) {
case "inputs":
// Ignore, these are handled by convertEventWorkflowDispatchInputs
result.inputs = convertWorkflowInputs(context, item.value.assertMapping("workflow dispatch inputs"));
break;
case "secrets":
@@ -27,6 +29,94 @@ 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;
"continue-on-error"?: boolean | ScalarToken;
env?: MappingToken;
};
@@ -158,7 +158,7 @@ export type WorkflowDispatchConfig = {
};
export type WorkflowCallConfig = {
inputs?: {[inputName: string]: InputConfig};
inputs?: {[inputName: string]: InputConfig & {default?: string | boolean | number | ScalarToken}};
secrets?: {[secretName: string]: SecretConfig};
// TODO - these are supported in C# and Go but not in TS yet
// outputs: { [outputName: string]: OutputConfig }
+2 -1
View File
@@ -576,7 +576,8 @@
"merge-group-mapping": {
"mapping": {
"properties": {
"types": "merge-group-activity"
"types": "merge-group-activity",
"branches": "event-branches"
}
}
},
+9 -1
View File
@@ -72,7 +72,11 @@ on:
- edited
- deleted
merge_group:
types: checks_requested
branches:
- master
- main
types:
- checks_requested
milestone:
types:
- created
@@ -313,6 +317,10 @@ jobs:
]
},
"merge_group": {
"branches": [
"master",
"main"
],
"types": [
"checks_requested"
]