Merge branch 'main' into dependabot/npm_and_yarn/http-cache-semantics-4.1.1

This commit is contained in:
Beth Brennan
2023-04-06 16:42:23 -04:00
committed by GitHub
25 changed files with 481 additions and 2460 deletions
+1 -1
View File
@@ -1 +1 @@
* @github/c2c-actions-experience-parser-reviewers
* @actions/actions-workflow-development-reviewers
-45
View File
@@ -1,45 +0,0 @@
name: Publish npm packages
on:
# For now auto-publish every change to the default branch
push:
branches: [main]
workflow_dispatch:
inputs:
bump:
description: How to increase the version
type: choice
default: patch
options:
- patch
- minor
- major
version:
description: The version to publish
type: string
default: ''
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: 'https://npm.pkg.github.com'
scope: '@actions'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
git config user.name github-actions
git config user.email [email protected]
- name: Publish packages
run: npx lerna publish ${{ inputs.version || inputs.bump || 'patch' }} --yes
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+51
View File
@@ -0,0 +1,51 @@
name: Create release PR
run-name: Create release PR for v${{ github.event.inputs.version }}
on:
workflow_dispatch:
inputs:
version:
required: true
description: "Version to bump `package.json` to (format: x.y.z)"
jobs:
create-release-pr:
name: Create release PR
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
- name: Bump version and push
run: |
git config --global user.email "[email protected]"
git config --global user.name "GitHub Actions"
git checkout -b release/${{ inputs.version }}
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 ${{ inputs.version }}"
git push --set-upstream origin release/${{ inputs.version }}
- name: Create PR
run: |
gh pr create \
--title "Release version ${{ inputs.version }}" \
--body "Release version ${{ inputs.version }}" \
--base main \
--head release/${{ inputs.version }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+109
View File
@@ -0,0 +1,109 @@
name: Release and publish packages
on:
push:
branches:
- main
paths:
- lerna.json
workflow_dispatch:
inputs:
version:
description: "Version to release"
required: true
jobs:
check-version-change:
outputs:
changed: ${{ steps.check-version.outputs.result }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- name: Check if version has changed
id: check-version
uses: actions/github-script@v6
with:
script: |
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,
repo: context.repo.repo,
tag: `release-v${version}`,
}).catch(() => null);
// If the release exists, the version has not changed
if (release) {
console.log(`Version ${version} has an existing release`);
console.log(release.data.html_url);
core.summary.addLink(`Release v${version}`, release.data.html_url);
await core.summary.write();
return "false";
}
console.log(`Version ${version} does not have a release`);
return true;
release:
environment: publish
needs: check-version-change
if: ${{ needs.check-version-change.outputs.changed == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
env:
PKG_VERSION: "" # will be set in the workflow
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
scope: '@actions'
- name: Parse version from lerna.json
run: |
echo "PKG_VERSION=$(node -p -e "require('./lerna.json').version")" >> $GITHUB_ENV
- run: npm ci
- name: Create release
uses: actions/github-script@v6
with:
script: |
const fs = require("fs");
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: "release-v${{ env.PKG_VERSION }}",
name: "v${{ env.PKG_VERSION }}",
draft: false,
prerelease: false
});
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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "browser-playground",
"version": "0.1.182",
"version": "0.2.0",
"description": "",
"private": true,
"main": "index.js",
"type": "module",
"dependencies": {
"@actions/languageserver": "*",
"@actions/languageserver": "^0.2.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
"monaco-languageclient": "^4.0.3",
@@ -1 +1 @@
import "@github/actions-languageserver";
import "@actions/languageserver";
+4 -1
View File
@@ -1,9 +1,12 @@
{
"name": "@actions/expressions",
"version": "0.1.182",
"version": "0.3.1",
"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.182",
"version": "0.3.1",
"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": "*",
"@actions/workflow-parser": "*",
"@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 -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);
})
@@ -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.182",
"version": "0.3.1",
"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": "*",
"@actions/workflow-parser": "*",
"@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.7",
@@ -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
}))
);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.1.182"
"version": "0.3.1"
}
+54 -2292
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.182",
"version": "0.3.1",
"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": "*",
"@actions/expressions": "^0.3.1",
"cronstrue": "^2.21.0",
"yaml": "^2.0.0-8"
},
+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"
]