Merge branch 'main' into thyeggman/token-completion-range

This commit is contained in:
Jacob Wallraff
2023-01-24 13:15:52 -08:00
67 changed files with 2179 additions and 566 deletions
+74
View File
@@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at opensource@github.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+2 -2
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 GitHub
Copyright GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-expressions",
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
+120 -4
View File
@@ -1,8 +1,10 @@
import {complete, CompletionItem, trimTokenVector} from "./completion";
import {DescriptionDictionary} from "./completion/descriptionDictionary";
import {BooleanData} from "./data/boolean";
import {Dictionary} from "./data/dictionary";
import {StringData} from "./data/string";
import {FunctionInfo} from "./funcs/info";
import {wellKnownFunctions} from "./funcs";
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
import {Lexer, TokenType} from "./lexer";
const testContext = new Dictionary(
@@ -19,6 +21,24 @@ const testContext = new Dictionary(
}
)
},
{
key: "github",
value: new DescriptionDictionary(
{
key: "actor",
value: new StringData(""),
description: "The name of the person or app that initiated the workflow. For example, octocat."
},
{
key: "inputs",
value: new DescriptionDictionary({
key: "name",
value: new StringData("monalisa"),
description: "The name of a person"
})
}
)
},
{
key: "secrets",
value: new Dictionary({
@@ -26,6 +46,13 @@ const testContext = new Dictionary(
value: new BooleanData(true)
})
},
{
key: "vars",
value: new Dictionary({
key: "VAR_NAME",
value: new BooleanData(true)
})
},
{
key: "hashFiles(1,255)",
value: new Dictionary()
@@ -34,11 +61,11 @@ const testContext = new Dictionary(
const testFunctions: FunctionInfo[] = [];
const testComplete = (input: string): CompletionItem[] => {
const testComplete = (input: string, functions?: Map<string, FunctionDefinition>): CompletionItem[] => {
const pos = input.indexOf("|");
input = input.replace("|", "");
const results = complete(input.slice(0, pos >= 0 ? pos : input.length), testContext, testFunctions);
const results = complete(input.slice(0, pos >= 0 ? pos : input.length), testContext, testFunctions, functions);
return results;
};
@@ -59,7 +86,10 @@ describe("auto-complete", () => {
expect(testComplete("to")).toContainEqual(expected);
expect(testComplete("toJs")).toContainEqual(expected);
expect(testComplete("1 == toJS")).toContainEqual(expected);
expect(testComplete("1 == (toJS")).toContainEqual(expected);
expect(testComplete("toJS| == 1")).toContainEqual(expected);
expect(testComplete("(toJS| == (foo.bar)")).toContainEqual(expected);
expect(testComplete("(((toJS| == (foo.bar)")).toContainEqual(expected);
});
it("removes parentheses from passed in function context", () => {
@@ -70,13 +100,57 @@ describe("auto-complete", () => {
});
});
describe("in multi-line expressions", () => {
it("includes built-in functions", () => {
expect(testComplete("1 == (\nto").map(x => x.label)).toContainEqual("toJson");
});
});
describe("functions", () => {
it("uses provided function definitions", () => {
expect(
testComplete(
"fromJson('invalid').|",
new Map(
Object.entries({
fromjson: {
...wellKnownFunctions.fromjson,
call: () =>
new Dictionary({
key: "foo",
value: new StringData("bar")
})
}
})
)
)
).toEqual<CompletionItem[]>([{label: "foo", function: false}]);
});
});
describe("for contexts", () => {
it("provides suggestions for env", () => {
it("provides suggestions for top-level context", () => {
const expected = completionItems("BAR_TEST", "FOO");
expect(testComplete("env.X")).toEqual(expected);
expect(testComplete("1 == env.F")).toEqual(expected);
expect(testComplete("env.")).toEqual(expected);
expect(testComplete("env.FOO")).toEqual(expected);
expect(testComplete("(env).")).toEqual(expected);
});
it("provides suggestions for nested context", () => {
const expected: CompletionItem[] = [
{
label: "name",
function: false,
description: "The name of a person"
}
];
expect(testComplete("github.inputs.|")).toEqual(expected);
expect(testComplete("(github).inputs.|")).toEqual(expected);
expect(testComplete("(github.inputs).|")).toEqual(expected);
expect(testComplete("'test' == github.inputs.|")).toEqual(expected);
expect(testComplete("github.inputs.| == 'monalisa'")).toEqual(expected);
});
it("provides suggestions for secrets", () => {
@@ -87,8 +161,35 @@ describe("auto-complete", () => {
expect(testComplete("toJSON(secrets.")).toEqual(expected);
});
it("provides suggestions for variables", () => {
const expected = completionItems("VAR_NAME");
expect(testComplete("vars.V")).toEqual(expected);
expect(testComplete("1 == vars.F")).toEqual(expected);
expect(testComplete("toJSON(vars.")).toEqual(expected);
});
it("provides suggestions for contexts in function call", () => {
expect(testComplete("toJSON(env.|)")).toEqual(completionItems("BAR_TEST", "FOO"));
expect(testComplete("toJSON(secrets.")).toEqual(completionItems("AWS_TOKEN"));
});
describe("with descriptions", () => {
it("top-level", () => {
expect(testComplete("github.")).toContainEqual<CompletionItem>({
label: "actor",
function: false,
description: "The name of the person or app that initiated the workflow. For example, octocat."
});
});
it("nested", () => {
expect(testComplete("github.inputs.")).toContainEqual<CompletionItem>({
label: "name",
function: false,
description: "The name of a person"
});
});
});
});
});
@@ -114,6 +215,21 @@ describe("trimTokenVector", () => {
input: "github.mona == github.act",
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF]
},
{
input: "github.mona == (github).act",
expected: [
TokenType.LEFT_PAREN,
TokenType.IDENTIFIER,
TokenType.RIGHT_PAREN,
TokenType.DOT,
TokenType.IDENTIFIER,
TokenType.EOF
]
},
{
input: "github.mona == (github.",
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.EOF]
},
{
input: "github['test'].",
expected: [
+44 -13
View File
@@ -1,8 +1,9 @@
import {DescriptionPair} from "./completion/descriptionDictionary";
import {Dictionary, isDictionary} from "./data/dictionary";
import {ExpressionData} from "./data/expressiondata";
import {Evaluator} from "./evaluator";
import {wellKnownFunctions} from "./funcs";
import {FunctionInfo} from "./funcs/info";
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
import {Lexer, Token, TokenType} from "./lexer";
import {Parser} from "./parser";
@@ -12,15 +13,27 @@ export type CompletionItem = {
function: boolean;
};
// Complete returns a list of completion items for the given expression.
//
// The main functionality is auto-completing functions and context access:
// We can only provide assistance if the input is in one of the following forms (with | denoting the cursor position):
// - context.path.inp| or context.path['inp| -- auto-complete context access
// - context.path.| or context.path['| -- auto-complete context access
// - toJS| -- auto-complete function call or top-level
// - | -- auto-complete function call or top-level context access
export function complete(input: string, context: Dictionary, extensionFunctions: FunctionInfo[]): CompletionItem[] {
/**
* Complete returns a list of completion items for the given expression.
* The main functionality is auto-completing functions and context access:
* We can only provide assistance if the input is in one of the following forms (with | denoting the cursor position):
* - context.path.inp| or context.path['inp| -- auto-complete context access
* - context.path.| or context.path['| -- auto-complete context access
* - toJS| -- auto-complete function call or top-level
* - | -- auto-complete function call or top-level context access
*
* @param input Input expression
* @param context Context available for the expression
* @param extensionFunctions List of functions available
* @param functions Optional map of functions to use during evaluation
* @returns Array of completion items
*/
export function complete(
input: string,
context: Dictionary,
extensionFunctions: FunctionInfo[],
functions?: Map<string, FunctionDefinition>
): CompletionItem[] {
// Lex
const lexer = new Lexer(input);
const lexResult = lexer.lex();
@@ -69,7 +82,7 @@ export function complete(input: string, context: Dictionary, extensionFunctions:
);
const expr = p.parse();
const ev = new Evaluator(expr, context);
const ev = new Evaluator(expr, context, functions);
const result = ev.evaluate();
return contextKeys(result);
@@ -97,7 +110,7 @@ function contextKeys(context: ExpressionData): CompletionItem[] {
return (
context
.pairs()
.map(x => completionItemFromContext(x.key))
.map(x => completionItemFromContext(x))
// Sort contexts
.sort((a, b) => a.label.localeCompare(b.label))
);
@@ -106,12 +119,14 @@ function contextKeys(context: ExpressionData): CompletionItem[] {
return [];
}
function completionItemFromContext(context: string): CompletionItem {
function completionItemFromContext(pair: DescriptionPair): CompletionItem {
const context = pair.key.toString();
const parenIndex = context.indexOf("(");
const isFunc = parenIndex >= 0 && context.indexOf(")") >= 0;
return {
label: isFunc ? context.substring(0, parenIndex) : context,
description: pair.description,
function: isFunc
};
}
@@ -119,9 +134,25 @@ function completionItemFromContext(context: string): CompletionItem {
export function trimTokenVector(tokenVector: Token[]): Token[] {
let tokenIdx = tokenVector.length;
let openParen = 0;
while (tokenIdx > 0) {
const token = tokenVector[tokenIdx - 1];
switch (token.type) {
case TokenType.LEFT_PAREN:
if (openParen == 0) {
// Encountered an open parenthesis without a closing first, stop here
break;
}
openParen--;
tokenIdx--;
continue;
case TokenType.RIGHT_PAREN:
openParen++;
tokenIdx--;
continue;
case TokenType.IDENTIFIER:
case TokenType.DOT:
case TokenType.EOF:
@@ -0,0 +1,31 @@
import {StringData} from "../data";
import {DescriptionDictionary} from "./descriptionDictionary";
describe("description dictionary", () => {
it("pairs contains all values", () => {
const d = new DescriptionDictionary();
d.add("ABC", new StringData("val"));
expect(d.pairs()).toEqual([{key: "ABC", value: new StringData("val")}]);
});
it("does not add duplicate entries", () => {
const d = new DescriptionDictionary();
d.add("ABC", new StringData("val1"));
d.add("ABC", new StringData("val2"));
d.add("abc", new StringData("val3"));
expect(d.pairs()).toEqual([{key: "ABC", value: new StringData("val1")}]);
});
it("can set optional descriptions", () => {
const d = new DescriptionDictionary();
d.add("ABC", new StringData("val"), "desc");
d.add("DEF", new StringData("val"));
expect(d.pairs()).toEqual([
{key: "ABC", value: new StringData("val"), description: "desc"},
{key: "DEF", value: new StringData("val")}
]);
});
});
@@ -0,0 +1,37 @@
import {Dictionary} from "../data/dictionary";
import {ExpressionData, Kind, Pair} from "../data/expressiondata";
export type DescriptionPair = Pair & {description?: string};
export function isDescriptionDictionary(x: ExpressionData): x is DescriptionDictionary {
return x.kind === Kind.Dictionary && x instanceof DescriptionDictionary;
}
export class DescriptionDictionary extends Dictionary {
private readonly descriptions = new Map<string, string>();
constructor(...pairs: DescriptionPair[]) {
super();
for (const p of pairs) {
this.add(p.key, p.value, p.description);
}
}
override add(key: string, value: ExpressionData, description?: string): void {
if (this.get(key) !== undefined) {
// Key already added, ignore
return;
}
super.add(key, value);
if (description) {
this.descriptions.set(key, description);
}
}
override pairs(): DescriptionPair[] {
const pairs = super.pairs();
return pairs.map(p => ({...p, description: this.descriptions.get(p.key)}));
}
}
+2
View File
@@ -50,3 +50,5 @@ function errorDescription(typ: ErrorType): string {
return "Unknown error";
}
}
export class ExpressionEvaluationError extends Error {}
+32 -17
View File
@@ -1,27 +1,42 @@
import * as data from "./data";
import {ExpressionEvaluationError} from "./errors";
import {Evaluator} from "./evaluator";
import {Lexer} from "./lexer";
import {Parser} from "./parser";
test("evaluator", () => {
const input = "foo['']";
describe("evaluator", () => {
const lexAndParse = (input: string) => {
const lexer = new Lexer(input);
const result = lexer.lex();
const lexer = new Lexer(input);
const result = lexer.lex();
// Parse
const parser = new Parser(result.tokens, ["foo"], []);
const expr = parser.parse();
return expr;
};
// Parse
const parser = new Parser(result.tokens, ["foo"], []);
const expr = parser.parse();
it("basic evaluation", () => {
const expr = lexAndParse("foo['']");
// Evaluate expression
const evaluator = new Evaluator(
expr,
new data.Dictionary({
key: "foo",
value: new data.Dictionary({key: "bar", value: new data.NumberData(42)})
})
);
const eresult = evaluator.evaluate();
// Evaluate expression
const evaluator = new Evaluator(
expr,
new data.Dictionary({
key: "foo",
value: new data.Dictionary({key: "bar", value: new data.NumberData(42)})
})
);
const eresult = evaluator.evaluate();
expect(eresult.kind).toBe(data.Kind.Null);
expect(eresult.kind).toBe(data.Kind.Null);
});
it("handle runtime errors", () => {
const expr = lexAndParse("fromJson('test') == 123");
const evaluator = new Evaluator(expr, new data.Dictionary());
expect(() => evaluator.evaluate()).toThrowError(
new ExpressionEvaluationError("Error parsing JSON when evaluating fromJson")
);
});
});
+14 -11
View File
@@ -14,14 +14,19 @@ import {
import * as data from "./data";
import {FilteredArray} from "./filtered_array";
import {wellKnownFunctions} from "./funcs";
import {FunctionDefinition} from "./funcs/info";
import {idxHelper} from "./idxHelper";
import {TokenType} from "./lexer";
import {equals, falsy, greaterThan, lessThan, truthy} from "./result";
export class EvaluationError extends Error {}
export class Evaluator implements ExprVisitor<data.ExpressionData> {
constructor(private n: Expr, private context: data.Dictionary) {}
/**
* Creates a new evaluator
* @param n Parsed expression to evaluate
* @param context Context data to use
* @param functions Optional map of function implementations. If given, these will be preferred over the built-in functions.
*/
constructor(private n: Expr, private context: data.Dictionary, private functions?: Map<string, FunctionDefinition>) {}
public evaluate(): data.ExpressionData {
return this.eval(this.n);
@@ -109,7 +114,7 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
try {
idxResult = this.eval(ia.index);
} catch (e) {
throw new Error(`could not evaluate index for index access: ${e}`);
throw new Error(`could not evaluate index for index access: ${e}`, {cause: e});
}
idx = new idxHelper(false, idxResult);
}
@@ -150,16 +155,14 @@ export class Evaluator implements ExprVisitor<data.ExpressionData> {
// Evaluate arguments
const args = functionCall.args.map(arg => this.eval(arg));
return fcall(functionCall, args);
// Get function definitions
const functionName = functionCall.functionName.lexeme.toLowerCase();
const f = this.functions?.get(functionName) || wellKnownFunctions[functionName];
return f.call(...args);
}
}
function fcall(fc: FunctionCall, args: data.ExpressionData[]): data.ExpressionData {
const f = wellKnownFunctions[fc.functionName.lexeme.toLowerCase()];
return f.call(...args);
}
function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.ExpressionData {
const result = new FilteredArray();
+6 -2
View File
@@ -1,5 +1,6 @@
import {ExpressionData} from "../data";
import {reviver} from "../data/reviver";
import {ExpressionEvaluationError} from "../errors";
import {FunctionDefinition} from "./info";
export const fromjson: FunctionDefinition = {
@@ -16,7 +17,10 @@ export const fromjson: FunctionDefinition = {
throw new Error("empty input");
}
const d = JSON.parse(is, reviver);
return d;
try {
return JSON.parse(is, reviver);
} catch (e) {
throw new ExpressionEvaluationError("Error parsing JSON when evaluating fromJson", {cause: e});
}
}
};
+6 -3
View File
@@ -1,6 +1,9 @@
export {complete} from "./completion";
export {Expr} from "./ast";
export {complete, CompletionItem} from "./completion";
export {DescriptionDictionary, DescriptionPair, isDescriptionDictionary} from "./completion/descriptionDictionary";
export * as data from "./data";
export {ExpressionError} from "./errors";
export {ExpressionError, ExpressionEvaluationError} from "./errors";
export {Evaluator} from "./evaluator";
export {Lexer} from "./lexer";
export {wellKnownFunctions} from "./funcs";
export {Lexer, Result} from "./lexer";
export {Parser} from "./parser";
+1
View File
@@ -3,6 +3,7 @@
"compilerOptions": {
"module": "esnext",
"target": "ES2020",
"lib": ["ES2022"],
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-languageserver",
"version": "0.1.79",
"version": "0.1.94",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -38,8 +38,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@github/actions-languageservice": "^0.1.79",
"@github/actions-workflow-parser": "^0.1.79",
"@github/actions-languageservice": "^0.1.94",
"@github/actions-workflow-parser": "^0.1.94",
"@octokit/rest": "^19.0.5",
"vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7",
@@ -53,8 +53,8 @@
],
"devDependencies": {
"@types/jest": "^29.0.3",
"fetch-mock": "^9.11.0",
"jest": "^29.0.3",
"nock": "^13.2.9",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
+4 -1
View File
@@ -23,6 +23,7 @@ import {TTLCache} from "./utils/cache";
import {valueProviders} from "./value-providers";
import {getActionInputs} from "./value-providers/action-inputs";
import {Commands} from "./commands";
import {descriptionProvider} from "./description-provider";
export function initConnection(connection: Connection) {
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
@@ -120,7 +121,9 @@ export function initConnection(connection: Connection) {
});
connection.onHover(async ({position, textDocument}: HoverParams): Promise<Hover | null> => {
return hover(documents.get(textDocument.uri)!, position);
return hover(documents.get(textDocument.uri)!, position, {
descriptionProvider: descriptionProvider(sessionToken, cache)
});
});
connection.onRequest("workspace/executeCommand", (params: ExecuteCommandParams) => {
@@ -1,10 +1,10 @@
import {data} from "@github/actions-expressions";
import {DescriptionDictionary} from "@github/actions-expressions";
import {ContextProviderConfig} from "@github/actions-languageservice";
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
import {isMapping, isString} from "@github/actions-workflow-parser";
import {Octokit} from "@octokit/rest";
import {getSecrets} from "./context-providers/secrets";
import {getStepsContext} from "./context-providers/steps";
import {getVariables} from "./context-providers/variables";
import {RepositoryContext} from "./initializationOptions";
import {TTLCache} from "./utils/cache";
@@ -23,36 +23,16 @@ export function contextProviders(
const getContext = async (
name: string,
defaultContext: data.Dictionary | undefined,
defaultContext: DescriptionDictionary | undefined,
workflowContext: WorkflowContext
) => {
switch (name) {
case "secrets": {
let environmentName: string | undefined;
if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) {
environmentName = workflowContext.job.environment.value;
} else if (isMapping(workflowContext.job.environment)) {
for (const x of workflowContext.job.environment) {
if (isString(x.key) && x.key.value === "name") {
if (isString(x.value)) {
environmentName = x.value.value;
}
break;
}
}
}
}
const secrets = await getSecrets(octokit, cache, repo, environmentName);
defaultContext = defaultContext || new data.Dictionary();
secrets.forEach(secret => defaultContext!.add(secret.value, new data.StringData("***")));
return defaultContext;
}
case "steps": {
case "secrets":
return await getSecrets(workflowContext, octokit, cache, repo, defaultContext);
case "vars":
return await getVariables(workflowContext, octokit, cache, repo, defaultContext);
case "steps":
return await getStepsContext(octokit, cache, defaultContext, workflowContext);
}
}
};
@@ -1,35 +1,93 @@
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {StringData} from "@github/actions-expressions/data/string";
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
import {isMapping, isString} from "@github/actions-workflow-parser";
import {Octokit} from "@octokit/rest";
import {RepositoryContext} from "../initializationOptions";
import {TTLCache} from "../utils/cache";
export async function getSecrets(
workflowContext: WorkflowContext,
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext,
defaultContext: DescriptionDictionary | undefined
): Promise<DescriptionDictionary> {
let environmentName: string | undefined;
if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) {
environmentName = workflowContext.job.environment.value;
} else if (isMapping(workflowContext.job.environment)) {
for (const x of workflowContext.job.environment) {
if (isString(x.key) && x.key.value === "name") {
if (isString(x.value)) {
environmentName = x.value.value;
}
break;
}
}
}
}
const secrets = await getRemoteSecrets(octokit, cache, repo, environmentName);
// Build combined map of secrets
const secretsMap = new Map<
string,
{
key: string;
value: data.StringData;
description?: string;
}
>();
secrets.repoSecrets.forEach(secret =>
secretsMap.set(secret.value.toLowerCase(), {
key: secret.value,
value: new data.StringData("***"),
description: "Repository secret"
})
);
// Override repo secrets with environment secrets (if defined)
secrets.environmentSecrets.forEach(secret =>
secretsMap.set(secret.value.toLowerCase(), {
key: secret.value,
value: new data.StringData("***"),
description: `Secret for environment \`${environmentName}\``
})
);
const secretsContext = defaultContext || new DescriptionDictionary();
// Sort secrets by key and add to context
Array.from(secretsMap.values())
.sort((a, b) => a.key.localeCompare(b.key))
.forEach(secret => secretsContext?.add(secret.key, secret.value, secret.description));
return secretsContext;
}
async function getRemoteSecrets(
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext,
environmentName?: string
): Promise<StringData[]> {
const secrets: StringData[] = [];
// Repo secrets
const repoSecrets = await cache.get(`${repo.owner}/${repo.name}/secrets`, undefined, () =>
fetchSecrets(octokit, repo.owner, repo.name)
);
secrets.push(...repoSecrets);
// Environment secrets
if (environmentName) {
const envSecrets = await cache.get(
`${repo.owner}/${repo.name}/secrets/environment/${environmentName}`,
undefined,
() => fetchEnvironmentSecrets(octokit, repo.id, environmentName)
);
secrets.push(...envSecrets);
}
return secrets.sort();
): Promise<{
repoSecrets: StringData[];
environmentSecrets: StringData[];
}> {
return {
repoSecrets: await cache.get(`${repo.owner}/${repo.name}/secrets`, undefined, () =>
fetchSecrets(octokit, repo.owner, repo.name)
),
environmentSecrets:
(environmentName &&
(await cache.get(`${repo.owner}/${repo.name}/secrets/environment/${environmentName}`, undefined, () =>
fetchEnvironmentSecrets(octokit, repo.id, environmentName)
))) ||
[]
};
}
async function fetchSecrets(octokit: Octokit, owner: string, name: string): Promise<StringData[]> {
@@ -1,7 +1,8 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {getStepsContext as getDefaultStepsContext} from "@github/actions-languageservice/context-providers/steps";
import {Octokit} from "@octokit/rest";
import nock from "nock";
import fetchMock from "fetch-mock";
import {createWorkflowContext} from "../test-utils/workflow-context";
import {TTLCache} from "../utils/cache";
import {getStepsContext} from "./steps";
@@ -54,14 +55,6 @@ const actionMetadata = {
}
};
beforeEach(() => {
nock.disableNetConnect();
});
afterEach(() => {
nock.cleanAll();
});
it("returns default context when job is undefined", async () => {
const workflowContext = createWorkflowContext(workflow, undefined);
const defaultContext = getDefaultStepsContext(workflowContext);
@@ -71,27 +64,49 @@ it("returns default context when job is undefined", async () => {
});
it("adds action outputs", async () => {
nock("https://api.github.com").get("/repos/actions/cache/contents/action.yml?ref=v3").reply(200, actionMetadata);
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/cache/contents/action.yml?ref=v3", actionMetadata);
const workflowContext = createWorkflowContext(workflow, "build");
const defaultContext = getDefaultStepsContext(workflowContext);
const stepsContext = await getStepsContext(new Octokit(), new TTLCache(), defaultContext, workflowContext);
const stepsContext = await getStepsContext(
new Octokit({
request: {
fetch: mock
}
}),
new TTLCache(),
defaultContext,
workflowContext
);
expect(stepsContext).toBeDefined();
expect(stepsContext).toEqual(
new data.Dictionary({
new DescriptionDictionary({
key: "cache-primes",
value: new data.Dictionary(
value: new DescriptionDictionary(
{
key: "outputs",
value: new data.Dictionary({
value: new DescriptionDictionary({
key: "cache-hit",
value: new data.StringData("A boolean value to indicate an exact match was found for the primary key")
value: new data.StringData("A boolean value to indicate an exact match was found for the primary key"),
description: "A boolean value to indicate an exact match was found for the primary key"
})
},
{key: "conclusion", value: new data.Null()},
{key: "outcome", value: new data.Null()}
{
key: "conclusion",
value: new data.Null(),
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`."
},
{
key: "outcome",
value: new data.Null(),
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`."
}
)
})
);
@@ -1,5 +1,4 @@
import {data} from "@github/actions-expressions";
import {isDictionary} from "@github/actions-expressions/data/dictionary";
import {data, DescriptionDictionary, isDescriptionDictionary} from "@github/actions-expressions";
import {parseActionReference} from "@github/actions-languageservice/action";
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
import {isActionStep} from "@github/actions-workflow-parser/model/type-guards";
@@ -10,9 +9,9 @@ import {getActionOutputs} from "./action-outputs";
export async function getStepsContext(
octokit: Octokit,
cache: TTLCache,
defaultContext: data.Dictionary | undefined,
defaultContext: DescriptionDictionary | undefined,
workflowContext: WorkflowContext
): Promise<data.Dictionary | undefined> {
): Promise<DescriptionDictionary | undefined> {
if (!defaultContext || !workflowContext.job) {
return defaultContext;
}
@@ -26,7 +25,7 @@ export async function getStepsContext(
// Copy the default context for each step
// If the step is an action, add the action outputs to the context
const stepsContext = new data.Dictionary();
const stepsContext = new DescriptionDictionary();
for (const step of workflowContext.job.steps) {
if (!contextSteps.has(step.id)) {
continue;
@@ -38,7 +37,7 @@ export async function getStepsContext(
continue;
}
if (!isActionStep(step) || !isDictionary(defaultStepContext)) {
if (!isActionStep(step) || !isDescriptionDictionary(defaultStepContext)) {
stepsContext.add(step.id, defaultStepContext);
continue;
}
@@ -49,23 +48,23 @@ export async function getStepsContext(
continue;
}
const stepContext = new data.Dictionary();
for (const {key, value} of defaultStepContext.pairs()) {
const stepContext = new DescriptionDictionary();
for (const {key, value, description} of defaultStepContext.pairs()) {
switch (key) {
case "outputs":
const outputs = await getActionOutputs(octokit, cache, action);
if (!outputs) {
stepContext.add(key, value);
stepContext.add(key, value, description);
continue;
}
const outputsDict = new data.Dictionary();
const outputsDict = new DescriptionDictionary();
for (const [key, value] of Object.entries(outputs)) {
outputsDict.add(key, new data.StringData(value.description));
outputsDict.add(key, new data.StringData(value.description), value.description);
}
stepContext.add("outputs", outputsDict);
break;
default:
stepContext.add(key, value);
stepContext.add(key, value, description);
}
}
stepsContext.add(step.id, stepContext);
@@ -0,0 +1,144 @@
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
import {isMapping, isString} from "@github/actions-workflow-parser";
import {Octokit} from "@octokit/rest";
import {Pair} from "@github/actions-expressions/data/expressiondata";
import {RepositoryContext} from "../initializationOptions";
import {StringData} from "@github/actions-expressions/data/index";
import {TTLCache} from "../utils/cache";
export async function getVariables(
workflowContext: WorkflowContext,
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext,
defaultContext: DescriptionDictionary | undefined
): Promise<DescriptionDictionary | undefined> {
let environmentName: string | undefined;
if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) {
environmentName = workflowContext.job.environment.value;
} else if (isMapping(workflowContext.job.environment)) {
for (const x of workflowContext.job.environment) {
if (isString(x.key) && x.key.value === "name") {
if (isString(x.value)) {
environmentName = x.value.value;
}
break;
}
}
}
}
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;
}
>();
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}\``
})
);
const variablesContext = defaultContext || new DescriptionDictionary();
// 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;
}
export async function getRemoteVariables(
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext,
environmentName?: string
): Promise<{
repoVariables: Pair[];
environmentVariables: Pair[];
}> {
// Repo variables
return {
repoVariables: await cache.get(`${repo.owner}/${repo.name}/vars`, undefined, () =>
fetchVariables(octokit, repo.owner, repo.name)
),
environmentVariables:
(environmentName &&
(await cache.get(`${repo.owner}/${repo.name}/vars/environment/${environmentName}`, undefined, () =>
fetchEnvironmentVariables(octokit, repo.id, environmentName)
))) ||
[]
};
}
async function fetchVariables(octokit: Octokit, owner: string, name: string): Promise<Pair[]> {
try {
const response = (await octokit.paginate("GET /repos/{owner}/{repo}/actions/variables{?per_page}", {
owner: owner,
repo: name
})) as {
name: string;
value: string;
created_at: string;
updated_at: string;
}[];
return response.map(variable => {
return {key: variable.name, value: new StringData(variable.value)};
});
} catch (e) {
console.log("Failure to retrieve variables: ", e);
}
return [];
}
async function fetchEnvironmentVariables(
octokit: Octokit,
repositoryId: number,
environmentName: string
): Promise<Pair[]> {
try {
const response = (await octokit.paginate(
"GET /repositories/{repository_id}/environments/{environment_name}/variables{?per_page}",
{
repository_id: repositoryId,
environment_name: environmentName
}
)) as {
name: string;
value: string;
created_at: string;
updated_at: string;
}[];
return response.map(variable => {
return {key: variable.name, value: new StringData(variable.value)};
});
} catch (e) {
console.log("Failure to retrieve environment variables: ", e);
}
return [];
}
@@ -0,0 +1,26 @@
import {DescriptionProvider} from "@github/actions-languageservice/hover";
import {Octokit} from "@octokit/rest";
import {TTLCache} from "./utils/cache";
import {getActionInputDescription} from "./description-providers/action-input";
export function descriptionProvider(sessionToken: string | undefined, cache: TTLCache): DescriptionProvider {
const octokit =
sessionToken &&
new Octokit({
auth: sessionToken
});
const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => {
if (!octokit) {
return undefined;
}
const parent = path[path.length - 1];
if (context.step && parent.definition?.key === "step-with") {
return await getActionInputDescription(octokit, cache, context.step, token);
}
};
return {
getDescription
};
}
@@ -0,0 +1,132 @@
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
import {Octokit} from "@octokit/rest";
import fetchMock from "fetch-mock";
import {createWorkflowContext} from "../test-utils/workflow-context";
import {TTLCache} from "../utils/cache";
import {getActionInputDescription} from "./action-input";
const workflow = `
name: Hello World
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
`;
// A simplified version of the action.yml file from actions/checkout
const actionMetadataContent = `
name: 'Checkout'
description: 'Checkout a Git repository at a particular version'
inputs:
repository:
description: Repository name with owner. For example, actions/checkout
default: \${{ github.repository }}
ref:
description: The branch, tag or SHA to checkout.
required: true
token:
description: Personal access token (PAT) used to fetch the repository.
default: \${{ github.token }}
repo:
description: 'Repository name with owner. For example, actions/checkout'
deprecationMessage: 'Use repository instead'
runs:
using: node16
main: dist/index.js
post: dist/index.js
`;
// Based on https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3
const actionMetadata = {
name: "action.yml",
path: "action.yml",
sha: "cab09ebd3a964aba67b57f9727f5f6fff1372b04",
size: 3649,
url: "https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3",
html_url: "https://github.com/actions/checkout/blob/v3/action.yml",
git_url: "https://api.github.com/repos/actions/checkout/git/blobs/cab09ebd3a964aba67b57f9727f5f6fff1372b04",
download_url: "https://raw.githubusercontent.com/actions/checkout/v3/action.yml",
type: "file",
content: Buffer.from(actionMetadataContent).toString("base64"),
encoding: "base64",
_links: {
self: "https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3",
git: "https://api.github.com/repos/actions/checkout/git/blobs/cab09ebd3a964aba67b57f9727f5f6fff1372b04",
html: "https://github.com/actions/checkout/blob/v3/action.yml"
}
};
async function getDescription(input: string, mock: fetchMock.FetchMockSandbox) {
const workflowContext = createWorkflowContext(workflow, "build", 0);
return await getActionInputDescription(
new Octokit({
request: {
fetch: mock
}
}),
new TTLCache(),
workflowContext.step!,
new StringToken(undefined, undefined, input, undefined)
);
}
describe("action descriptions", () => {
it("optional input", async () => {
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
expect(await getDescription("repository", mock)).toEqual(
"Repository name with owner. For example, actions/checkout"
);
});
it("required input", async () => {
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
expect(await getDescription("ref", mock)).toEqual("The branch, tag or SHA to checkout.\n\n**Required**");
});
it("deprecated input", async () => {
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
expect(await getDescription("repo", mock)).toEqual(
"Repository name with owner. For example, actions/checkout\n\n**Deprecated**"
);
});
it("invalid input", async () => {
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
expect(await getDescription("typo", mock)).toBeUndefined();
});
// TODO: https://github.com/github/c2c-actions-experience/issues/7056
it.failing("action does not exist", async () => {
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", 404)
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yaml?ref=v3", 404);
expect(await getDescription("repository", mock)).toBeUndefined();
});
// TODO: https://github.com/github/c2c-actions-experience/issues/7056
it.failing("invalid permissions", async () => {
const mock = fetchMock
.sandbox()
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", 403);
expect(await getDescription("repository", mock)).toBeUndefined();
});
});
@@ -0,0 +1,53 @@
import {parseActionReference} from "@github/actions-languageservice/action";
import {isString} from "@github/actions-workflow-parser";
import {isActionStep} from "@github/actions-workflow-parser/model/type-guards";
import {Step} from "@github/actions-workflow-parser/model/workflow-template";
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
import {Octokit} from "@octokit/rest";
import {fetchActionMetadata} from "../utils/action-metadata";
import {TTLCache} from "../utils/cache";
export async function getActionInputDescription(
client: Octokit,
cache: TTLCache,
step: Step,
token: TemplateToken
): Promise<string | undefined> {
if (!isActionStep(step)) {
return undefined;
}
const action = parseActionReference(step.uses.value);
if (!action) {
return undefined;
}
const inputName = isString(token) && token.value;
if (!inputName) {
return undefined;
}
const metadata = await fetchActionMetadata(client, cache, action);
if (!metadata?.inputs) {
return undefined;
}
const input = metadata.inputs[inputName];
if (!input) {
return undefined;
}
let description = input.description;
const deprecated = input.deprecationMessage !== undefined;
if (deprecated) {
// Validation will include the deprecation message, so don't duplicate it here
description += `\n\n**Deprecated**`;
}
if (input.required) {
description += "\n\n**Required**";
}
return description;
}
+23 -4
View File
@@ -1,7 +1,26 @@
import {createConnection} from "vscode-languageserver/node";
import {Connection} from "vscode-languageserver";
import {
BrowserMessageReader,
BrowserMessageWriter,
createConnection as createBrowserConnection
} from "vscode-languageserver/browser";
import {createConnection as createNodeConnection} from "vscode-languageserver/node";
import {initConnection} from "./connection";
// By default create node connection
const connection = createConnection();
initConnection(connection);
/** Helper function determining whether we are executing with node runtime */
function isNode(): boolean {
return typeof process !== "undefined" && process.versions?.node != null;
}
function getConnection(): Connection {
if (isNode()) {
return createNodeConnection();
} else {
const messageReader = new BrowserMessageReader(self);
const messageWriter = new BrowserMessageWriter(self);
return createBrowserConnection(messageReader, messageWriter);
}
}
initConnection(getConnection());
@@ -19,7 +19,7 @@ export function createWorkflowContext(workflow: string, job?: string, stepIndex?
context.job = template.jobs.find(j => j.id.value === job);
}
if (stepIndex) {
if (stepIndex !== undefined) {
context.step = context.job?.steps[stepIndex];
}
+2 -1
View File
@@ -7,7 +7,8 @@
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "node"
"moduleResolution": "node",
"lib": ["es6", "webworker"]
},
"watchOptions": {
"watchFile": "useFsEvents",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-languageservice",
"version": "0.1.79",
"version": "0.1.94",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -38,8 +38,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@github/actions-expressions": "^0.1.79",
"@github/actions-workflow-parser": "^0.1.79",
"@github/actions-expressions": "^0.1.94",
"@github/actions-workflow-parser": "^0.1.94",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"yaml": "^2.1.1"
@@ -1,5 +1,5 @@
import {data} from "@github/actions-expressions";
import {CompletionItemKind} from "vscode-languageserver-types";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {CompletionItem, CompletionItemKind} from "vscode-languageserver-types";
import {complete, getExpressionInput} from "./complete";
import {ContextProviderConfig} from "./context-providers/config";
import {registerLogger} from "./log";
@@ -10,9 +10,10 @@ const contextProviderConfig: ContextProviderConfig = {
getContext: async (context: string) => {
switch (context) {
case "github":
return new data.Dictionary({
return new DescriptionDictionary({
key: "event",
value: new data.StringData("push")
value: new data.StringData("push"),
description: "The event that triggered the workflow"
});
}
@@ -29,6 +30,7 @@ describe("expressions", () => {
return getExpressionInput(doc.getText(), pos.character);
};
// With ${{ }}
expect(test("${{ gh |")).toBe(" gh ");
expect(test("${{ gh |}}")).toBe(" gh ");
expect(test("${{ vars| == 'test' }}")).toBe(" vars");
@@ -36,6 +38,22 @@ describe("expressions", () => {
expect(test("${{ github.| == 'test' }}")).toBe(" github.");
expect(test("test ${{ github.| == 'test' }}")).toBe(" github.");
expect(test("${{ vars }} ${{ gh |}}")).toBe(" gh ");
expect(test("${{ test.|")).toBe(" test.");
expect(test("${{ test.| }}")).toBe(" test.");
expect(test("${{ 1 == (test.|)")).toBe(" 1 == (test.");
// Without ${{ }}
expect(test("gh |")).toBe("gh ");
expect(test("gh |}}")).toBe("gh ");
expect(test("vars| == 'test' }}")).toBe("vars");
expect(test("fromJso|('test').bar == 'test' }}")).toBe("fromJso");
expect(test("github.| == 'test' }}")).toBe("github.");
expect(test("github.| == 'test' }}")).toBe("github.");
expect(test("test.|")).toBe("test.");
expect(test("test.| }}")).toBe("test.");
expect(test("1 == (test.|)")).toBe("1 == (test.");
});
describe("top-level auto-complete", () => {
@@ -57,6 +75,30 @@ describe("expressions", () => {
]);
});
it("within parentheses", async () => {
const result = await complete(
...getPositionFromCursor("run-name: ${{ 1 == (github.|) }}"),
undefined,
contextProviderConfig
);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("contains description", async () => {
const input = "run-name: ${{ github.| }}";
const result = await complete(...getPositionFromCursor(input), undefined, undefined);
expect(result).toContainEqual<CompletionItem>({
label: "api_url",
documentation: {
kind: "markdown",
value: "The URL of the GitHub Actions REST API."
},
kind: CompletionItemKind.Variable
});
});
it("single region with existing input", async () => {
const input = "run-name: ${{ g| }}";
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
@@ -187,6 +229,25 @@ jobs:
});
});
it("nested with parentheses", async () => {
const input = `on:
workflow_dispatch:
inputs:
test:
type: string
jobs:
build:
runs-on: ubuntu-latest
env:
foo: '{}'
steps:
- name: "\${{ fromJSON('test') == (inputs.|) }}"`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["test"]);
});
it("nested auto-complete", async () => {
const input = "run-name: ${{ github.| }}";
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
@@ -202,30 +263,90 @@ jobs:
expect(result.map(x => x.label)).toEqual(["arch", "name", "os", "temp", "tool_cache"]);
});
it("job if", async () => {
const input = `on: push
describe("job if", () => {
describe("without ${{", () => {
it("simple", async () => {
const input = `on: push
jobs:
build:
if: github.|
runs-on: ubuntu-latest
steps:
- run: echo`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("complex", async () => {
const input = `on: push
jobs:
build:
if: false && github.| == 'some-repo'
runs-on: ubuntu-latest
steps:
- run: echo`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
});
describe("with ${{", () => {
it("simple", async () => {
const input = `on: push
jobs:
build:
if: \${{ github.| }}
runs-on: ubuntu-latest
steps:
- run: echo`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("complex", async () => {
const input = `on: push
jobs:
build:
if: \${{ false && github.| == 'some-repo' }}
runs-on: ubuntu-latest
steps:
- run: echo`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
});
});
it("step if", async () => {
const input = `on: push
describe("step if", () => {
it("with ${{", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo
if: \${{ github.| }}`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("without ${{", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo
if: github.|`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
expect(result.map(x => x.label)).toEqual(["event"]);
});
});
});
@@ -319,11 +440,11 @@ env:
jobs:
a:
runs-on: ubuntu-latest
env:
env:
envjoba: job_a_env
b:
runs-on: ubuntu-latest
env:
env:
envjobb: job_b_env
steps:
- name: step a
@@ -387,7 +508,7 @@ jobs:
it("includes expected keys", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
@@ -452,7 +573,7 @@ jobs:
on:
schedule:
- cron: '0 0 * * *'
jobs:
test:
runs-on: ubuntu-latest
@@ -469,7 +590,7 @@ jobs:
it("includes event payload", async () => {
const input = `
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
+4 -6
View File
@@ -312,7 +312,7 @@ jobs:
// Includes detail when available. Using continue-on-error as a sample here.
expect(result.map(x => (x.documentation as MarkupContent)?.value)).toContain(
"Prevents a job from failing when a step fails. Set to true to allow a job to pass when this step fails."
"Prevents a job from failing when a step fails. Set to `true` to allow a job to pass when this step fails."
);
});
@@ -337,9 +337,7 @@ o|
const result = await complete(...getPositionFromCursor(input));
const onResult = result.find(x => x.label === "on");
expect(onResult).not.toBeUndefined();
expect((onResult!.documentation as MarkupContent).value).toEqual(
"The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows."
);
expect((onResult!.documentation as MarkupContent).value).toContain("The GitHub event that triggers the workflow.");
});
it("event list includes descriptions when available ", async () => {
@@ -348,8 +346,8 @@ o|
const result = await complete(...getPositionFromCursor(input));
const dispatchResult = result.find(x => x.label === "workflow_dispatch");
expect(dispatchResult).not.toBeUndefined();
expect((dispatchResult!.documentation as MarkupContent).value).toEqual(
"You can now create workflows that are manually triggered with the new workflow_dispatch event. You will then see a 'Run workflow' button on the Actions tab, enabling you to easily trigger a run."
expect((dispatchResult!.documentation as MarkupContent).value).toContain(
"The `workflow_dispatch` event allows you to manually trigger a workflow run."
);
});
+20 -10
View File
@@ -14,6 +14,8 @@ import {CompletionItem, CompletionItemKind, CompletionItemTag, Range, TextEdit}
import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {validatorFunctions} from "./expression-validation/functions";
import {error} from "./log";
import {nullTrace} from "./nulltrace";
import {findToken} from "./utils/find-token";
import {guessIndentation} from "./utils/indentation-guesser";
@@ -25,12 +27,14 @@ import {definitionValues} from "./value-providers/definition";
export function getExpressionInput(input: string, pos: number): string {
// Find start marker around the cursor position
const startPos = input.lastIndexOf(OPEN_EXPRESSION, pos);
let startPos = input.lastIndexOf(OPEN_EXPRESSION, pos);
if (startPos === -1) {
return input;
startPos = 0;
} else {
startPos += OPEN_EXPRESSION.length;
}
return input.substring(startPos + OPEN_EXPRESSION.length, pos);
return input.substring(startPos, pos);
}
export async function complete(
@@ -75,13 +79,14 @@ export async function complete(
// Transform the overall position into a node relative position
let relCharPos: number = 0;
const lineDiff = newPos.line - token.range!.start[0];
if (token.range!.start[0] !== token.range!.end[0]) {
const range = mapRange(token.range!);
if (range.start.line !== range.end.line) {
const lines = currentInput.split("\n");
const lineDiff = newPos.line - range.start.line - 1;
const linesBeforeCusor = lines.slice(0, lineDiff);
relCharPos = linesBeforeCusor.join("\n").length + 1 + newPos.character;
relCharPos = linesBeforeCusor.join("\n").length + newPos.character + 1;
} else {
relCharPos = newPos.character - token.range!.start[1] + 1;
relCharPos = newPos.character - range.start.character;
}
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
@@ -89,9 +94,14 @@ export async function complete(
const allowedContext = token.definitionInfo?.allowedContext || [];
const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion);
return completeExpression(expressionInput, context, []).map(item =>
mapExpressionCompletionItem(item, currentInput[relCharPos])
);
try {
return completeExpression(expressionInput, context, [], validatorFunctions).map(item =>
mapExpressionCompletionItem(item, currentInput[relCharPos])
);
} catch (e: any) {
error(`Error while completing expression: '${e?.message || "<no details>"}'`);
return [];
}
}
}
@@ -1,31 +1,10 @@
import {data} from "@github/actions-expressions";
import {Dictionary} from "@github/actions-expressions/data/dictionary";
import {ExpressionData, Pair} from "@github/actions-expressions/data/expressiondata";
import {DescriptionDictionary} from "@github/actions-expressions";
import {WorkflowContext} from "../context/workflow-context";
export type ContextProviderConfig = {
getContext: (
name: string,
defaultContext: data.Dictionary | undefined,
defaultContext: DescriptionDictionary | undefined,
workflowContext: WorkflowContext
) => Promise<data.Dictionary | undefined>;
) => Promise<DescriptionDictionary | undefined>;
};
/**
* DynamicDictionary is a dictionary that returns an empty DynamicDictionary (or other given type)
* for any key that is not present.
*/
export class DynamicDictionary<T extends ExpressionData = Dictionary> extends data.Dictionary {
constructor(pairs: Pair[], private creator: () => T = () => new data.Dictionary() as T) {
super(...pairs);
}
get(key: string): ExpressionData | undefined {
const value = super.get(key);
if (value) {
return value;
}
return this.creator();
}
}
@@ -1,7 +1,8 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {Kind} from "@github/actions-expressions/data/expressiondata";
import {WorkflowContext} from "../context/workflow-context";
import {ContextProviderConfig} from "./config";
import {getDescription, RootContext} from "./descriptions";
import {getEnvContext} from "./env";
import {getGithubContext} from "./github";
import {getInputsContext} from "./inputs";
@@ -13,7 +14,7 @@ import {getStrategyContext} from "./strategy";
// ContextValue is the type of the value returned by a context provider
// Null indicates that the context provider doesn't have any value to provide
export type ContextValue = data.Dictionary | data.Null;
export type ContextValue = DescriptionDictionary | data.Null;
export enum Mode {
Completion,
@@ -26,12 +27,12 @@ export async function getContext(
config: ContextProviderConfig | undefined,
workflowContext: WorkflowContext,
mode: Mode
): Promise<data.Dictionary> {
const context = new data.Dictionary();
): Promise<DescriptionDictionary> {
const context = new DescriptionDictionary();
const filteredNames = filterContextNames(names, workflowContext);
for (const contextName of filteredNames) {
let value = getDefaultContext(contextName, workflowContext, mode) || new data.Dictionary();
let value = getDefaultContext(contextName, workflowContext, mode) || new DescriptionDictionary();
if (value.kind === Kind.Null) {
context.add(contextName, value);
continue;
@@ -39,7 +40,7 @@ export async function getContext(
value = (await config?.getContext(contextName, value, workflowContext)) || value;
context.add(contextName, value);
context.add(contextName, value, getDescription(RootContext, contextName));
}
return context;
@@ -75,7 +76,11 @@ function getDefaultContext(name: string, workflowContext: WorkflowContext, mode:
});
case "secrets":
return objectToDictionary({GITHUB_TOKEN: "***"});
return new DescriptionDictionary({
key: "GITHUB_TOKEN",
value: new data.StringData("***"),
description: getDescription("secrets", "GITHUB_TOKEN")
});
case "steps":
return getStepsContext(workflowContext);
@@ -87,8 +92,9 @@ function getDefaultContext(name: string, workflowContext: WorkflowContext, mode:
return undefined;
}
function objectToDictionary(object: {[key: string]: string}): data.Dictionary {
const dictionary = new data.Dictionary();
function objectToDictionary(object: {[key: string]: string}): DescriptionDictionary {
const dictionary = new DescriptionDictionary();
for (const key in object) {
dictionary.add(key, new data.StringData(object[key]));
}
@@ -0,0 +1,186 @@
{
"$schema": "./descriptionsSchema.json",
"root": {
"github": {
"description": "Information about the workflow run. For more information, see [`github` context](https://docs.github.com/actions/learn-github-actions/contexts#github-context)."
},
"env": {
"description": "Contains variables set in a workflow, job, or step. For more information, see [`env` context](https://docs.github.com/actions/learn-github-actions/contexts#env-context)."
},
"vars": {
"description": "Contains variables set at the repository, organization, or environment levels. For more information, see [`vars` context](https://docs.github.com/actions/learn-github-actions/contexts#vars-context)."
},
"job": {
"description": "Information about the currently running job. For more information, see [`job` context](https://docs.github.com/actions/learn-github-actions/contexts#job-context)."
},
"jobs": {
"description": "For reusable workflows only, contains outputs of jobs from the reusable workflow. For more information, see [`jobs` context](https://docs.github.com/actions/learn-github-actions/contexts#jobs-context)."
},
"steps": {
"description": "Information about the steps that have been run in the current job. For more information, see [`steps` context](https://docs.github.com/actions/learn-github-actions/contexts#steps-context)."
},
"runner": {
"description": "Information about the runner that is running the current job. For more information, see [`runner` context](https://docs.github.com/actions/learn-github-actions/contexts#runner-context)."
},
"secrets": {
"description": "Contains the names and values of secrets that are available to a workflow run. For more information, see [`secrets` context](https://docs.github.com/actions/learn-github-actions/contexts#secrets-context)."
},
"strategy": {
"description": "Information about the matrix execution strategy for the current job. For more information, see [`strategy` context](https://docs.github.com/actions/learn-github-actions/contexts#strategy-context)."
},
"matrix": {
"description": "Contains the matrix properties defined in the workflow that apply to the current job. For more information, see [`matrix` context](https://docs.github.com/actions/learn-github-actions/contexts#matrix-context)."
},
"needs": {
"description": "Contains the outputs of all jobs that are defined as a dependency of the current job. For more information, see [`needs` context](https://docs.github.com/actions/learn-github-actions/contexts#needs-context)."
},
"inputs": {
"description": "Contains the inputs of a reusable or manually triggered workflow. For more information, see [`inputs` context](https://docs.github.com/actions/learn-github-actions/contexts#inputs-context)."
}
},
"github": {
"action": {
"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."
},
"action_ref": {
"description": "For a step executing an action, this is the ref of the action being executed. For example, `v2`."
},
"action_repository": {
"description": "For a step executing an action, this is the owner and repository name of the action. For example, `actions/checkout`."
},
"action_status": {
"description": "For a composite action, the current result of the composite action."
},
"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."
},
"api_url": {
"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](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 [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."
},
"event_path": {
"description": "The path to the file on the runner that contains the full event webhook payload."
},
"graphql_url": {
"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`](/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`."
},
"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.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](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, `Codertocat/Hello-World`."
},
"repository_owner": {
"description": "The repository owner's name. For example, `Codertocat`."
},
"repositoryUrl": {
"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."
},
"run_id": {
"description": "A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run."
},
"run_number": {
"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.",
"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`.",
"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`."
},
"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`."
},
"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."
},
"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."
},
"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": "`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)."
}
},
"steps": {
"outputs": {
"description": "The set of outputs defined for the step."
},
"conclusion": {
"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` 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`."
}
}
}
@@ -0,0 +1,14 @@
import descriptions from "./descriptions.json";
export const RootContext = "root";
/**
* Get a description for a built-in context
* @param context Name of the context, for example `github`
* @param key Key of the context, for example `actor`
* @returns Description if one is found, otherwise undefined
*/
export function getDescription(context: string, key: string): string | undefined {
// The inferred type doesn't quite match the actual type, use any to work around that
return (descriptions as any)[context]?.[key]?.description;
}
@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"$comment": "Ignore this, just to make VS Code happy"
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"versions": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"description"
]
}
}
}
@@ -1,10 +1,10 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {isScalar, isString} from "@github/actions-workflow-parser";
import {WorkflowContext} from "../context/workflow-context";
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
import {WorkflowContext} from "../context/workflow-context";
export function getEnvContext(workflowContext: WorkflowContext): data.Dictionary {
const d = new data.Dictionary();
export function getEnvContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
//step env
if (workflowContext.step?.env) {
@@ -18,6 +18,7 @@ import label from "./label.json";
import marketplace_purchase from "./marketplace_purchase.json";
import member from "./member.json";
import membership from "./membership.json";
import merge_group from "./merge_group.json";
import meta from "./meta.json";
import milestone from "./milestone.json";
import org_block from "./org_block.json";
@@ -70,6 +71,7 @@ export const eventPayloads: {[key: string]: Object} = {
marketplace_purchase,
member,
membership,
merge_group,
meta,
milestone,
org_block,
@@ -0,0 +1,155 @@
{
"action": "checks_requested",
"merge_group": {
"head_sha": "2ffea6db159f6b6c47a24e778fb9ef40cf6b1c7d",
"head_ref": "refs/heads/gh-readonly-queue/main/pr-104-929f8209d40f77f4abc622a499c93a83babdbe64",
"base_sha": "380387fbc80638b734a49e1be1c4dfec1c01b33c",
"base_ref": "refs/heads/main",
"head_commit": {
"id": "ec26c3e57ca3a959ca5aad62de7213c562f8c821",
"tree_id": "31b122c26a97cf9af023e9ddab94a82c6e77b0ea",
"message": "Merge pull request #2048 from octo-repo/update-readme\n\nUpdate README.md",
"timestamp": "2019-05-15T15:20:30Z",
"author": {
"name": "Codertocat",
"email": "21031067+Codertocat@users.noreply.github.com"
},
"committer": {
"name": "Codertocat",
"email": "21031067+Codertocat@users.noreply.github.com"
}
}
},
"repository": {
"id": 17273051,
"node_id": "MDEwOlJlcG9zaXRvcnkxNzI3MzA1MQ==",
"name": "octo-repo",
"full_name": "octo-org/octo-repo",
"private": true,
"owner": {
"login": "octo-org",
"id": 6811672,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY4MTE2NzI=",
"avatar_url": "https://avatars.githubusercontent.com/u/6811672?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/octo-org",
"html_url": "https://github.com/octo-org",
"followers_url": "https://api.github.com/users/octo-org/followers",
"following_url": "https://api.github.com/users/octo-org/following{/other_user}",
"gists_url": "https://api.github.com/users/octo-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octo-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octo-org/subscriptions",
"organizations_url": "https://api.github.com/users/octo-org/orgs",
"repos_url": "https://api.github.com/users/octo-org/repos",
"events_url": "https://api.github.com/users/octo-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/octo-org/received_events",
"type": "Organization",
"site_admin": false
},
"html_url": "https://github.com/octo-org/octo-repo",
"description": "My first repo on GitHub!",
"fork": false,
"url": "https://api.github.com/repos/octo-org/octo-repo",
"forks_url": "https://api.github.com/repos/octo-org/octo-repo/forks",
"keys_url": "https://api.github.com/repos/octo-org/octo-repo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/octo-org/octo-repo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/octo-org/octo-repo/teams",
"hooks_url": "https://api.github.com/repos/octo-org/octo-repo/hooks",
"issue_events_url": "https://api.github.com/repos/octo-org/octo-repo/issues/events{/number}",
"events_url": "https://api.github.com/repos/octo-org/octo-repo/events",
"assignees_url": "https://api.github.com/repos/octo-org/octo-repo/assignees{/user}",
"branches_url": "https://api.github.com/repos/octo-org/octo-repo/branches{/branch}",
"tags_url": "https://api.github.com/repos/octo-org/octo-repo/tags",
"blobs_url": "https://api.github.com/repos/octo-org/octo-repo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/octo-org/octo-repo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/octo-org/octo-repo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/octo-org/octo-repo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/octo-org/octo-repo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/octo-org/octo-repo/languages",
"stargazers_url": "https://api.github.com/repos/octo-org/octo-repo/stargazers",
"contributors_url": "https://api.github.com/repos/octo-org/octo-repo/contributors",
"subscribers_url": "https://api.github.com/repos/octo-org/octo-repo/subscribers",
"subscription_url": "https://api.github.com/repos/octo-org/octo-repo/subscription",
"commits_url": "https://api.github.com/repos/octo-org/octo-repo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/octo-org/octo-repo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/octo-org/octo-repo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/octo-org/octo-repo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/octo-org/octo-repo/contents/{+path}",
"compare_url": "https://api.github.com/repos/octo-org/octo-repo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/octo-org/octo-repo/merges",
"archive_url": "https://api.github.com/repos/octo-org/octo-repo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/octo-org/octo-repo/downloads",
"issues_url": "https://api.github.com/repos/octo-org/octo-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/octo-org/octo-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/octo-org/octo-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/octo-org/octo-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/octo-org/octo-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/octo-org/octo-repo/releases{/id}",
"deployments_url": "https://api.github.com/repos/octo-org/octo-repo/deployments",
"created_at": "2014-02-28T02:42:51Z",
"updated_at": "2021-03-11T14:54:13Z",
"pushed_at": "2021-03-11T14:54:10Z",
"git_url": "git://github.com/octo-org/octo-repo.git",
"ssh_url": "org-6811672@github.com:octo-org/octo-repo.git",
"clone_url": "https://github.com/octo-org/octo-repo.git",
"svn_url": "https://github.com/octo-org/octo-repo",
"homepage": "",
"size": 300,
"stargazers_count": 0,
"watchers_count": 0,
"language": "JavaScript",
"has_issues": true,
"has_projects": false,
"has_downloads": true,
"has_wiki": false,
"has_pages": true,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 39,
"license": null,
"forks": 0,
"open_issues": 39,
"watchers": 0,
"default_branch": "main"
},
"organization": {
"login": "octo-org",
"id": 6811672,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY4MTE2NzI=",
"url": "https://api.github.com/orgs/octo-org",
"repos_url": "https://api.github.com/orgs/octo-org/repos",
"events_url": "https://api.github.com/orgs/octo-org/events",
"hooks_url": "https://api.github.com/orgs/octo-org/hooks",
"issues_url": "https://api.github.com/orgs/octo-org/issues",
"members_url": "https://api.github.com/orgs/octo-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/octo-org/public_members{/member}",
"avatar_url": "https://avatars.githubusercontent.com/u/6811672?v=4",
"description": "Working better together!"
},
"sender": {
"login": "Codertocat",
"id": 21031067,
"node_id": "MDQ6VXNlcjIxMDMxMDY3",
"avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Codertocat",
"html_url": "https://github.com/Codertocat",
"followers_url": "https://api.github.com/users/Codertocat/followers",
"following_url": "https://api.github.com/users/Codertocat/following{/other_user}",
"gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",
"organizations_url": "https://api.github.com/users/Codertocat/orgs",
"repos_url": "https://api.github.com/users/Codertocat/repos",
"events_url": "https://api.github.com/users/Codertocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/Codertocat/received_events",
"type": "User",
"site_admin": false
},
"installation": {
"id": 1,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY4MTE2NzI="
}
}
@@ -1,10 +1,7 @@
{
"action": "on-demand-test",
"branch": "master",
"client_payload": {
"unit": false,
"integration": true
},
"client_payload": {},
"repository": {
"id": 17273051,
"node_id": "MDEwOlJlcG9zaXRvcnkxNzI3MzA1MQ==",
@@ -1,11 +1,12 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {ExpressionData} from "@github/actions-expressions/data/expressiondata";
import {WorkflowContext} from "../context/workflow-context";
import {getDescription} from "./descriptions";
import {eventPayloads} from "./events/eventPayloads";
import {getInputsContext} from "./inputs";
export function getGithubContext(workflowContext: WorkflowContext): data.Dictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
export function getGithubContext(workflowContext: WorkflowContext): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-cwontext
const keys = [
"action",
"action_path",
@@ -43,13 +44,23 @@ export function getGithubContext(workflowContext: WorkflowContext): data.Diction
"workspace"
];
return new data.Dictionary(
return new DescriptionDictionary(
...keys.map(key => {
const description = getDescription("github", key);
if (key == "event") {
return {key, value: getEventContext(workflowContext)};
return {
key,
value: getEventContext(workflowContext),
description
};
}
return {key, value: new data.Null()};
return {
key,
value: new data.Null(),
description
};
})
);
}
@@ -88,6 +99,13 @@ function getEventContext(workflowContext: WorkflowContext): ExpressionData {
function merge(d: data.Dictionary, toAdd: Object): data.Dictionary {
for (const [key, value] of Object.entries(toAdd)) {
if (value && typeof value === "object" && !d.get(key)) {
if (!Array.isArray(value) && Object.entries(value).length === 0) {
// Allow an empty object to be any value
d.add(key, new data.Null());
continue;
}
d.add(key, merge(new data.Dictionary(), value));
} else {
d.add(key, new data.Null());
@@ -1,9 +1,9 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {InputConfig} from "@github/actions-workflow-parser/model/workflow-template";
import {WorkflowContext} from "../context/workflow-context";
export function getInputsContext(workflowContext: WorkflowContext): data.Dictionary {
const d = new data.Dictionary();
export function getInputsContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
const events = workflowContext?.template?.events;
if (!events) {
@@ -23,22 +23,22 @@ export function getInputsContext(workflowContext: WorkflowContext): data.Diction
return d;
}
function addInputs(d: data.Dictionary, inputs: {[inputName: string]: InputConfig}) {
function addInputs(d: DescriptionDictionary, inputs: {[inputName: string]: InputConfig}) {
for (const inputName of Object.keys(inputs)) {
const input = inputs[inputName];
switch (input.type) {
case "choice":
if (input.default) {
d.add(inputName, new data.StringData(input.default as string));
d.add(inputName, new data.StringData(input.default as string), input.description);
} else {
// Default to the first input or an empty string
d.add(inputName, new data.StringData((input.options || [""])[0]));
d.add(inputName, new data.StringData((input.options || [""])[0]), input.description);
}
break;
case "environment":
if (input.default) {
d.add(inputName, new data.StringData(input.default as string));
d.add(inputName, new data.StringData(input.default as string), input.description);
} else {
// For now default to an empty value if there is no default value. This will always be an environment, so
// we could also dynamically look up environments and default to the first one, but leaving this as a
@@ -48,12 +48,12 @@ function addInputs(d: data.Dictionary, inputs: {[inputName: string]: InputConfig
break;
case "boolean":
d.add(inputName, new data.BooleanData((input.default as boolean) || false));
d.add(inputName, new data.BooleanData((input.default as boolean) || false), input.description);
break;
case "string":
default:
d.add(inputName, new data.StringData((input.default as string) || inputName));
d.add(inputName, new data.StringData((input.default as string) || inputName), input.description);
break;
}
}
@@ -1,11 +1,11 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {isMapping, isSequence} from "@github/actions-workflow-parser";
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
import {WorkflowContext} from "../context/workflow-context";
export function getJobContext(workflowContext: WorkflowContext): data.Dictionary {
export function getJobContext(workflowContext: WorkflowContext): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#job-context
const jobContext = new data.Dictionary();
const jobContext = new DescriptionDictionary();
const job = workflowContext.job;
if (!job) {
return jobContext;
@@ -21,7 +21,7 @@ export function getJobContext(workflowContext: WorkflowContext): data.Dictionary
// Services
const jobServices = job.services;
if (jobServices && isMapping(jobServices)) {
const servicesContext = new data.Dictionary();
const servicesContext = new DescriptionDictionary();
for (const service of jobServices) {
if (!isMapping(service.value)) {
continue;
@@ -1,7 +1,6 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {Job} from "@github/actions-workflow-parser/model/workflow-template";
import {BasicExpressionToken} from "@github/actions-workflow-parser/templates/tokens/basic-expression-token";
import {ExpressionToken} from "@github/actions-workflow-parser/templates/tokens/expression-token";
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token";
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
@@ -64,7 +63,7 @@ describe("matrix context", () => {
expect(workflowContext.job).toBeUndefined();
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new DescriptionDictionary());
});
it("strategy not defined", () => {
@@ -73,7 +72,7 @@ describe("matrix context", () => {
expect(workflowContext.job!.strategy).toBeUndefined();
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new DescriptionDictionary());
});
it("strategy is not a mapping token", () => {
@@ -81,7 +80,7 @@ describe("matrix context", () => {
expect(workflowContext.job!.strategy).toBeDefined();
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new DescriptionDictionary());
});
it("matrix is not defined", () => {
@@ -107,7 +106,7 @@ describe("matrix context", () => {
const workflowContext = contextFromStrategy(strategy);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new DescriptionDictionary());
});
});
@@ -161,7 +160,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Completion);
expect(context).toEqual(
new data.Dictionary({
new DescriptionDictionary({
key: "node",
value: new data.Array(new data.StringData("12"), new data.StringData("14"))
})
@@ -181,7 +180,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary({
new DescriptionDictionary({
key: "version",
value: new data.Null()
})
@@ -195,7 +194,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary({
new DescriptionDictionary({
key: "os",
value: new data.Array(new data.StringData("ubuntu-latest"), new data.StringData("windows-latest"))
})
@@ -210,7 +209,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
new DescriptionDictionary(
{
key: "os",
value: new data.Array(new data.StringData("ubuntu-latest"), new data.StringData("windows-latest"))
@@ -238,7 +237,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
new DescriptionDictionary(
{
key: "os",
value: new data.Array(
@@ -272,7 +271,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
new DescriptionDictionary(
{
key: "site",
value: new data.Array(new data.StringData("production"), new data.StringData("staging"))
@@ -306,7 +305,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
new DescriptionDictionary(
{
key: "os",
value: new data.Array(new data.StringData("macos-latest"), new data.StringData("windows-latest"))
@@ -340,7 +339,7 @@ describe("matrix context", () => {
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new DescriptionDictionary());
});
});
});
@@ -1,4 +1,4 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {isBasicExpression, isMapping, isSequence, isString} from "@github/actions-workflow-parser";
import {KeyValuePair} from "@github/actions-workflow-parser/templates/tokens/key-value-pair";
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
@@ -10,7 +10,7 @@ export function getMatrixContext(workflowContext: WorkflowContext, mode: Mode):
// https://docs.github.com/en/actions/learn-github-actions/contexts#matrix-context
const strategy = workflowContext.job?.strategy;
if (!strategy || !isMapping(strategy)) {
return new data.Dictionary();
return new DescriptionDictionary();
}
const matrix = strategy.find("matrix");
@@ -25,7 +25,7 @@ export function getMatrixContext(workflowContext: WorkflowContext, mode: Mode):
return new data.Null();
}
const d = new data.Dictionary();
const d = new DescriptionDictionary();
for (const [key, value] of properties) {
if (value === undefined) {
d.add(key, new data.Null());
@@ -1,10 +1,10 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {isScalar, isString} from "@github/actions-workflow-parser";
import {Job} from "@github/actions-workflow-parser/model/workflow-template";
import {WorkflowContext} from "../context/workflow-context";
export function getNeedsContext(workflowContext: WorkflowContext): data.Dictionary {
const d = new data.Dictionary();
export function getNeedsContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
if (!workflowContext.job || !workflowContext.job.needs) {
return d;
}
@@ -17,9 +17,9 @@ export function getNeedsContext(workflowContext: WorkflowContext): data.Dictiona
return d;
}
function needsJobContext(job?: Job): data.Dictionary {
function needsJobContext(job?: Job): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context
const d = new data.Dictionary();
const d = new DescriptionDictionary();
d.add("outputs", jobOutputs(job));
@@ -28,8 +28,8 @@ function needsJobContext(job?: Job): data.Dictionary {
return d;
}
function jobOutputs(job?: Job): data.Dictionary {
const d = new data.Dictionary();
function jobOutputs(job?: Job): DescriptionDictionary {
const d = new DescriptionDictionary();
if (!job?.outputs) {
return d;
}
@@ -1,9 +1,10 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {Step} from "@github/actions-workflow-parser/model/workflow-template";
import {WorkflowContext} from "../context/workflow-context";
import {getDescription} from "./descriptions";
export function getStepsContext(workflowContext: WorkflowContext): data.Dictionary {
const d = new data.Dictionary();
export function getStepsContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
if (!workflowContext.job?.steps) {
return d;
}
@@ -26,15 +27,15 @@ export function getStepsContext(workflowContext: WorkflowContext): data.Dictiona
return d;
}
function stepContext(): data.Dictionary {
function stepContext(): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context
const d = new data.Dictionary();
const d = new DescriptionDictionary();
d.add("outputs", new data.Null());
d.add("outputs", new data.Null(), getDescription("steps", "outputs"));
// Can be "success", "failure", "cancelled", or "skipped"
d.add("conclusion", new data.Null());
d.add("outcome", new data.Null());
d.add("conclusion", new data.Null(), getDescription("steps", "conclusion"));
d.add("outcome", new data.Null(), getDescription("steps", "outcome"));
return d;
}
@@ -1,22 +1,22 @@
import {data} from "@github/actions-expressions";
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {isMapping, isScalar, isString} from "@github/actions-workflow-parser";
import {WorkflowContext} from "../context/workflow-context";
import {scalarToData} from "../utils/scalar-to-data";
export function getStrategyContext(workflowContext: WorkflowContext): data.Dictionary {
export function getStrategyContext(workflowContext: WorkflowContext): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#strategy-context
const keys = ["fail-fast", "job-index", "job-total", "max-parallel"];
const strategy = workflowContext.job?.strategy;
if (!strategy || !isMapping(strategy)) {
return new data.Dictionary(
return new DescriptionDictionary(
...keys.map(key => {
return {key, value: new data.Null()};
})
);
}
const strategyContext = new data.Dictionary();
const strategyContext = new DescriptionDictionary();
for (const pair of strategy) {
if (!isString(pair.key)) {
continue;
@@ -0,0 +1,13 @@
import {data, wellKnownFunctions} from "@github/actions-expressions";
// Custom implementations for standard actions-expression functions used during validation and auto-completion.
// For example, for fromJson we'll most likely not have a valid input. In order to not throw, we'll always
// return an empty dictionary.
export const validatorFunctions = new Map(
Object.entries({
fromjson: {
...wellKnownFunctions.fromjson,
call: () => new data.Dictionary()
}
})
);
+81 -6
View File
@@ -1,6 +1,25 @@
import {hover} from "./hover";
import {isString} from "@github/actions-workflow-parser/.";
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
import {DescriptionProvider, hover, HoverConfig} from "./hover";
import {getPositionFromCursor} from "./test-utils/cursor-position";
function testHoverConfig(tokenValue: string, tokenKey: string, description?: string) {
return {
descriptionProvider: {
getDescription: async (_, token, __) => {
if (!isString(token)) {
throw new Error("Test provider only supports string tokens");
}
expect((token as StringToken).value).toEqual(tokenValue);
expect(token.definition!.key).toEqual(tokenKey);
return description;
}
} satisfies DescriptionProvider
} satisfies HoverConfig
}
describe("hover", () => {
it("on a key", async () => {
const input = `o|n: push
@@ -9,9 +28,7 @@ jobs:
runs-on: [self-hosted]`;
const result = await hover(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result?.contents).toEqual(
"The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows."
);
expect(result?.contents).toContain("The GitHub event that triggers the workflow.");
});
it("on a value", async () => {
@@ -44,8 +61,8 @@ jobs:
pe|rmissions: read-all`;
const result = await hover(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result?.contents).toEqual(
"You can modify the default permissions granted to the GITHUB_TOKEN, adding or removing access as required, so that you only allow the minimum required access."
expect(result?.contents).toContain(
"You can use `permissions` to modify the default permissions granted to the `GITHUB_TOKEN`"
);
});
@@ -78,4 +95,62 @@ jobs:
expect(result).not.toBeUndefined();
expect(result?.contents).toEqual("Runs your workflow when you push a commit or tag.");
});
it("shows context inherited from parent nodes", async () => {
const input = `
on: push
jobs:
build:
runs-on: [self-hosted]
steps:
- uses: actions/checkout@v2
with:
ref|: main
`;
const result = await hover(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
// The `ref` is a `string` definition and inherits the context from `step-with`
const expected = "**Context:** github, inputs, vars, needs, strategy, matrix, secrets, steps, job, runner, env, hashFiles(1,255)"
expect(result?.contents).toEqual(expected);
});
});
describe("hover with description provider", () => {
it("uses the description provider", async () => {
const input = `
on: push
jobs:
build:
runs-on: [self-hosted]
steps:
- uses: actions/checkout@v2
with:
ref|: main
`;
const result = await hover(...getPositionFromCursor(input), testHoverConfig("ref", "string", "The branch, tag or SHA to checkout."));
expect(result).not.toBeUndefined();
const expected = "The branch, tag or SHA to checkout.\n\n" +
"**Context:** github, inputs, vars, needs, strategy, matrix, secrets, steps, job, runner, env, hashFiles(1,255)"
expect(result?.contents).toEqual(expected);
});
it("falls back to the token description", async () => {
const input = `
on: push
jobs:
build:
runs-on: [self-hosted]
steps:
- uses|: actions/checkout@v2
`;
const result = await hover(...getPositionFromCursor(input), testHoverConfig("uses", "non-empty-string", undefined));
expect(result).not.toBeUndefined();
expect(result?.contents).toEqual("Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image.");
});
});
+45 -28
View File
@@ -1,49 +1,66 @@
import {parseWorkflow} from "@github/actions-workflow-parser";
import {convertWorkflowTemplate, parseWorkflow, ParseWorkflowResult} from "@github/actions-workflow-parser";
import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert";
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
import {File} from "@github/actions-workflow-parser/workflows/file";
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {Hover} from "vscode-languageserver-types";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {info} from "./log";
import {nullTrace} from "./nulltrace";
import {findToken} from "./utils/find-token";
import {mapRange} from "./utils/range";
export type DescriptionProvider = {
getDescription(context: WorkflowContext, token: TemplateToken, path: TemplateToken[]): Promise<string | undefined>;
};
export type HoverConfig = {
descriptionProvider?: DescriptionProvider;
};
// Render value description and Context when hovering over a key in a MappingToken
export async function hover(document: TextDocument, position: Position): Promise<Hover | null> {
export async function hover(document: TextDocument, position: Position, config?: HoverConfig): Promise<Hover | null> {
const file: File = {
name: document.uri,
content: document.getText()
};
const result = parseWorkflow(file.name, [file], nullTrace);
const {token} = findToken(position, result.value);
if (result.value && token) {
return getHover(token);
const {token, path} = findToken(position, result.value);
if (!token?.definition) {
return null;
}
return null;
info(`Calculating hover for token with definition ${token.definition.key}`);
let description = await getDescription(document, config, result, token, path);
const allowedContext = token.definitionInfo?.allowedContext;
if (allowedContext && allowedContext?.length > 0) {
// Only add padding if there is a description
description += `${description.length > 0 ? `\n\n` : ""}**Context:** ${allowedContext.join(", ")}`;
}
return {
contents: description,
range: mapRange(token.range)
} satisfies Hover;
}
function getHover(token: TemplateToken): Hover | null {
if (token.definition) {
info(`Calculating hover for token with definition ${token.definition.key}`);
let description = "";
if (token.description) {
description = token.description;
}
if (token.definition.evaluatorContext.length > 0) {
// Only add padding if there is a description
description += `${description.length > 0 ? `\n\n` : ""}**Context:** ${token.definition.evaluatorContext.join(
", "
)}`;
}
return {
contents: description,
range: mapRange(token.range)
} as Hover;
async function getDescription(
document: TextDocument,
config: HoverConfig | undefined,
result: ParseWorkflowResult | undefined,
token: TemplateToken,
path: TemplateToken[]
) {
const defaultDescription = token.description || "";
if (!result?.value || !config?.descriptionProvider) {
return defaultDescription;
}
return null;
const template = convertWorkflowTemplate(result.context, result.value, ErrorPolicy.TryConversion);
const workflowContext = getWorkflowContext(document.uri, template, path);
const description = await config.descriptionProvider.getDescription(workflowContext, token, path);
return description || defaultDescription;
}
@@ -21,4 +21,12 @@ describe("getPositionFromCursor", () => {
expect(position).toEqual({line: 0, character: 0});
expect(newDoc.getText()).toEqual("on: push\njobs:");
});
it("handles a cursor in the middle of the document", () => {
const input = "on: push\n jobs|:\n build:";
const [newDoc, position] = getPositionFromCursor(input);
expect(position).toEqual({line: 1, character: 6});
expect(newDoc.getText()).toEqual("on: push\n jobs:\n build:");
});
});
+11 -10
View File
@@ -1,5 +1,5 @@
import {TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range";
import {Range} from "vscode-languageserver-types";
import {Position as TokenPosition, TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range";
import {Position, Range} from "vscode-languageserver-types";
export function mapRange(range: TokenRange | undefined): Range {
if (!range) {
@@ -16,13 +16,14 @@ export function mapRange(range: TokenRange | undefined): Range {
}
return {
start: {
line: range.start[0] - 1,
character: range.start[1] - 1
},
end: {
line: range.end[0] - 1,
character: range.end[1] - 1
}
start: mapPosition(range.start),
end: mapPosition(range.end)
};
}
export function mapPosition(position: TokenPosition): Position {
return {
line: position[0] - 1,
character: position[1] - 1
};
}
@@ -37,7 +37,7 @@ jobs:
jobs:
build:
runs-on:
- dummy`);
- key`);
expect(newPos.character).toEqual(9);
});
@@ -53,7 +53,23 @@ jobs:
jobs:
build:
runs-on:
dummy:`);
key:`);
expect(newPos.character).toEqual(7);
});
it("does not transform expression lines", () => {
const [doc, pos] = getPositionFromCursor(`on: push
jobs:
build:
runs-on:
\${{ github| }}`);
const [newDoc, newPos] = transform(doc, pos);
expect(newDoc.getText()).toEqual(`on: push
jobs:
build:
runs-on:
\${{ github }}`);
expect(newPos.character).toEqual(16);
});
});
+25 -21
View File
@@ -1,7 +1,7 @@
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {Range} from "vscode-languageserver-types";
const DUMMY_KEY = "dummy";
const PLACEHOLDER_KEY = "key";
// Transform a document to work around YAML parsing issues
// Based on `_transform` in https://github.com/cschleiden/github-actions-parser/blob/main/src/lib/parser/complete.ts#L311
@@ -27,29 +27,33 @@ export function transform(doc: TextDocument, pos: Position): [TextDocument, Posi
// Special case for Actions, if this line contains an expression marker, do _not_ transform. This is
// an ugly fix for auto-completion in multi-line YAML strings. At this point in the process, we cannot
// determine if a line is in such a multi-line string.
if (line.indexOf("${{") === -1) {
const colon = line.indexOf(":");
if (colon === -1) {
const trimmedLine = line.trim();
if (trimmedLine === "" || trimmedLine === "-") {
// Node in sequence or empty line
let spacer = "";
if (trimmedLine === "-" && !line.endsWith(" ")) {
spacer = " ";
offset++;
}
if (line.indexOf("${{") !== -1) {
return [doc, pos];
}
line =
line.substring(0, linePos) + spacer + DUMMY_KEY + (trimmedLine === "-" ? "" : ":") + line.substring(linePos);
// Adjust pos by one to prevent a sequence node being marked as active
const containsColon = line.indexOf(":") !== -1;
if (!containsColon) {
const trimmedLine = line.trim();
if (trimmedLine === "" || trimmedLine === "-") {
// Pos in sequence or empty line
let spacer = "";
if (trimmedLine === "-" && !line.endsWith(" ")) {
spacer = " ";
offset++;
} else if (!trimmedLine.startsWith("-")) {
// Add `:` to end of line
line = line + ":";
}
} else {
offset = offset - 1;
line =
line.substring(0, linePos) +
spacer +
PLACEHOLDER_KEY +
(trimmedLine === "-" ? "" : ":") +
line.substring(linePos);
// Adjust pos by one to prevent a sequence node being marked as active
offset++;
} else if (!trimmedLine.startsWith("-")) {
// Add `:` to end of line
line = line + ":";
}
}
@@ -359,7 +359,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: step a
env:
env:
step_env: job_a_env
run: echo "hello \${{ env.step_env }}
`;
@@ -376,7 +376,7 @@ env:
jobs:
a:
runs-on: ubuntu-latest
env:
env:
envjoba: job_a_env
steps:
- name: step a
@@ -395,7 +395,7 @@ env:
jobs:
a:
runs-on: ubuntu-latest
env:
env:
envjoba: job_a_env
steps:
- name: step a
@@ -1185,6 +1185,9 @@ jobs:
- run: echo "hello \${{ github.event.inputs.name }}"
- run: echo "hello \${{ github.event.inputs.third-name }}"
- run: echo "hello \${{ github.event.inputs.random }}"
- run: echo \${{ fromJSON(inputs.random2) }}
- run: echo "hello \${{ inputs.random }}"
name: "\${{ fromJSON('test') == inputs.name }}"
`;
const result = await validate(createDocument("wf.yaml", input));
@@ -1202,8 +1205,54 @@ jobs:
}
},
severity: DiagnosticSeverity.Warning
},
{
message: "Context access might be invalid: random2",
range: {
end: {
character: 47,
line: 20
},
start: {
character: 16,
line: 20
}
},
severity: 2
},
{
message: "Context access might be invalid: random",
range: {
end: {
character: 43,
line: 21
},
start: {
character: 23,
line: 21
}
},
severity: 2
}
]);
});
it("allows any property in client_payload", async () => {
const input = `
on:
repository_dispatch:
types: [test]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ github.event.client_payload.anything }}
- run: echo \${{ github.event.client_payload.branch }}`
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
});
});
+9 -2
View File
@@ -1,4 +1,4 @@
import {Evaluator, Lexer, Parser} from "@github/actions-expressions";
import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@github/actions-expressions";
import {Expr} from "@github/actions-expressions/ast";
import {
convertWorkflowTemplate,
@@ -22,6 +22,7 @@ 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 {validatorFunctions} from "./expression-validation/functions";
import {error} from "./log";
import {nullTrace} from "./nulltrace";
import {findToken} from "./utils/find-token";
@@ -202,7 +203,7 @@ async function validateExpression(
try {
const context = await getContext(namedContexts, contextProviderConfig, workflowContext, Mode.Validation);
const e = new Evaluator(expr, wrapDictionary(context));
const e = new Evaluator(expr, wrapDictionary(context), validatorFunctions);
e.evaluate();
// Any invalid context access would've thrown an error via the `ErrorDictionary`, for now we don't have to check the actual
@@ -214,6 +215,12 @@ async function validateExpression(
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)
});
}
}
}
@@ -4,7 +4,7 @@ export interface Value {
/** Label of this value */
label: string;
/** Optional description to show when auto-completing or hovering */
/** Optional description to show when auto-completing */
description?: string;
/** Whether this value is deprecated */
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-workflow-parser",
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -40,7 +40,7 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@github/actions-expressions": "^0.1.79",
"@github/actions-expressions": "^0.1.94",
"yaml": "^2.0.0-8"
},
"engines": {
+2 -4
View File
@@ -92,16 +92,14 @@ jobs:
case "name": {
const nameKey = pair.key.assertString("name");
expect(nameKey.definition).not.toBeUndefined();
expect(nameKey.description).toBe("The name of the workflow.");
expect(nameKey.description).toContain("The name of the workflow");
break;
}
case "on": {
const onKey = pair.key.assertString("on");
const onValue = pair.value.assertString("push");
expect(onKey.definition).not.toBeUndefined();
expect(onKey.description).toBe(
"The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows."
);
expect(onKey.description).toContain("The GitHub event that triggers the workflow.");
expect(onValue.definition).not.toBeUndefined();
expect(onValue.description).toBe("Runs your workflow when you push a commit or tag.");
break;
@@ -451,7 +451,6 @@ class TemplateReader {
const allowedContext = definitionInfo.allowedContext;
const raw = token.source || token.value;
// Check if the value is definitely a literal
let startExpression: number = raw.indexOf(OPEN_EXPRESSION);
if (startExpression < 0) {
// Doesn't contain "${{"
+126 -125
View File
@@ -41,7 +41,7 @@
}
},
"workflow-name": {
"description": "The name of the workflow.",
"description": "The name of the workflow that GitHub displays on your repository's 'Actions' tab.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#name)",
"string": {}
},
"run-name": {
@@ -51,10 +51,10 @@
"vars"
],
"string": {},
"description": "The name for workflow runs generated from the workflow. GitHub displays the workflow run name in the list of workflow runs on your repository's 'Actions' tab."
"description": "The name for workflow runs generated from the workflow. GitHub displays the workflow run name in the list of workflow runs on your repository's 'Actions' tab.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#run-name)"
},
"on": {
"description": "The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows.",
"description": "The GitHub event that triggers the workflow. Events can be a single string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. View a full list of [events that trigger workflows](https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows).\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on)",
"one-of": [
"string",
"sequence",
@@ -71,7 +71,7 @@
}
},
"on-strict": {
"description": "The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows.",
"description": "The GitHub event that triggers the workflow. Events can be a single string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. View a full list of [events that trigger workflows](https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows).\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on)",
"one-of": [
"on-string-strict",
"on-sequence-strict",
@@ -79,7 +79,7 @@
]
},
"on-mapping-strict": {
"description": "The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows.",
"description": "The GitHub event that triggers the workflow. Events can be a single string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. View a full list of [events that trigger workflows](https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows).\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on)",
"mapping": {
"properties": {
"branch_protection_rule": "branch-protection-rule",
@@ -187,7 +187,7 @@
}
},
"branch-protection-rule-activity": {
"description": "The types of branch protection rule activity that trigger the workflow. Can be one or more of: created, edited, deleted.",
"description": "The types of branch protection rule activity that trigger the workflow. Supported activity types: `created`, `edited`, `deleted`.",
"one-of": [
"branch-protection-rule-activity-type",
"branch-protection-rule-activity-types"
@@ -226,7 +226,7 @@
}
},
"check-run-activity": {
"description": "The types of check run activity that trigger the workflow. Can be one or more of: completed, requested, rerequested, or requested-action.",
"description": "The types of check run activity that trigger the workflow. Supported activity types: `created`, `rerequested`, `completed`, `requested_action`.",
"one-of": [
"check-run-activity-type",
"check-run-activity-types"
@@ -266,10 +266,10 @@
}
},
"check-suite-activity": {
"description": "The types of check suite activity that trigger the workflow. Currently only completed is supported.",
"description": "The types of check suite activity that trigger the workflow. Supported activity types: `completed`.",
"one-of": [
"check-suite-activity-type",
"check-suite-activity-types"
"check-suite-activity-type",
"check-suite-activity-types"
]
},
"check-suite-activity-types": {
@@ -323,13 +323,13 @@
"null": {}
},
"discussion-string": {
"description": "Runs your workflow when a discussion in the workflow's repository is created or modified. For activity related to comments on a discussion, use the discussion_comment event.",
"description": "Runs your workflow when a discussion in the workflow's repository is created or modified. For activity related to comments on a discussion, use the `discussion_comment` event.",
"string": {
"constant": "discussion"
}
},
"discussion": {
"description": "Runs your workflow when a discussion in the workflow's repository is created or modified. For activity related to comments on a discussion, use the discussion_comment event.",
"description": "Runs your workflow when a discussion in the workflow's repository is created or modified. For activity related to comments on a discussion, use the `discussion_comment` event.",
"one-of": [
"null",
"discussion-mapping"
@@ -343,7 +343,7 @@
}
},
"discussion-activity": {
"description": "The types of discussion activity that trigger the workflow. Can be one or more of: created, edited, deleted, transferred, pinned, unpinned, labeled, unlabeled, locked, unlocked, category_changed, answered, or unanswered.",
"description": "The types of discussion activity that trigger the workflow. Supported activity types: `created`, `edited`, `deleted`, `transferred`, `pinned`, `unpinned`, `labeled`, `unlabeled`, `locked`, `unlocked`, `category_changed`, `answered`, `unanswered`.",
"one-of": [
"discussion-activity-type",
"discussion-activity-types"
@@ -372,13 +372,13 @@
]
},
"discussion-comment-string": {
"description": "Runs your workflow when a comment on a discussion in the workflow's repository is created or modified. For activity related to a discussion as opposed to comments on the discussion, use the discussion event.",
"description": "Runs your workflow when a comment on a discussion in the workflow's repository is created or modified. For activity related to a discussion as opposed to comments on the discussion, use the `discussion` event.",
"string": {
"constant": "discussion_comment"
}
},
"discussion-comment": {
"description": "Runs your workflow when a comment on a discussion in the workflow's repository is created or modified. For activity related to a discussion as opposed to comments on the discussion, use the discussion event.",
"description": "Runs your workflow when a comment on a discussion in the workflow's repository is created or modified. For activity related to a discussion as opposed to comments on the discussion, use the `discussion` event.",
"one-of": [
"null",
"discussion-comment-mapping"
@@ -392,7 +392,7 @@
}
},
"discussion-comment-activity": {
"description": "The types of discussion comment activity that trigger the workflow. Can be one or more of: created, edited, or deleted.",
"description": "The types of discussion comment activity that trigger the workflow. Supported activity types: `created`, `edited`, `deleted`.",
"one-of": [
"discussion-comment-activity-type",
"discussion-comment-activity-types"
@@ -451,7 +451,7 @@
}
},
"issue-comment-activity": {
"description": "The types of issue comment activity that trigger the workflow. Can be one or more of: created, edited, or deleted.",
"description": "The types of issue comment activity that trigger the workflow. Supported activity types: `created`, `edited`, `deleted`.",
"one-of": [
"issue-comment-activity-type",
"issue-comment-activity-types"
@@ -470,13 +470,13 @@
]
},
"issues-string": {
"description": "Runs your workflow when an issue or pull request comment is created, edited, or deleted.",
"description": "Runs your workflow when an issue in the workflow's repository is created or modified. For activity related to comments in an issue, use the `issue_comment` event.",
"string": {
"constant": "issues"
}
},
"issues": {
"description": "Runs your workflow when an issue or pull request comment is created, edited, or deleted.",
"description": "Runs your workflow when an issue in the workflow's repository is created or modified. For activity related to comments in an issue, use the `issue_comment` event.",
"one-of": [
"null",
"issues-mapping"
@@ -490,7 +490,7 @@
}
},
"issues-activity": {
"description": "The types of issue activity that trigger the workflow. Can be one or more of: opened, edited, deleted, transferred, pinned, unpinned, closed, reopened, assigned, unassigned, labeled, unlabeled, locked, unlocked, milestoned, or demilestoned.",
"description": "The types of issue activity that trigger the workflow. Supported activity types: `opened`, `edited`, `deleted`, `transferred`, `pinned`, `unpinned`, `closed`, `reopened`, `assigned`, `unassigned`, `labeled`, `unlabeled`, `locked`, `unlocked`, `milestoned`, `demilestoned`.",
"one-of": [
"issues-activity-type",
"issues-activity-types"
@@ -542,7 +542,7 @@
}
},
"label-activity": {
"description": "The types of label activity that trigger the workflow. Can be one or more of: created, edited, or deleted.",
"description": "The types of label activity that trigger the workflow. Supported activity types: `created`, `edited`, `deleted`.",
"one-of": [
"label-activity-type",
"label-activity-types"
@@ -581,7 +581,7 @@
}
},
"merge-group-activity": {
"description": "The types of label activity that trigger the workflow. Currently only checks_requested is supported.",
"description": "The types of merge group activity that trigger the workflow. Supported activity types: `checks_requested`.",
"one-of": [
"merge-group-activity-type",
"merge-group-activity-types"
@@ -618,7 +618,7 @@
}
},
"milestone-activity": {
"description": "The types of milestone activity that trigger the workflow. Can be one or more of: created, edited, opened, closed, or deleted.",
"description": "The types of milestone activity that trigger the workflow. Supported activity types: `created`, `closed`, `opened`, `edited`, `deleted`.",
"one-of": [
"milestone-activity-type",
"milestone-activity-types"
@@ -649,13 +649,13 @@
"null": {}
},
"project-string": {
"description": "Runs your workflow when a project board is created or modified. For activity related to cards or columns in a project board, use the project_card or project_column events instead.",
"description": "Runs your workflow when a project board is created or modified. For activity related to cards or columns in a project board, use the `project_card` or `project_column` events instead.",
"string": {
"constant": "project"
}
},
"project": {
"description": "Runs your workflow when a project board is created or modified. For activity related to cards or columns in a project board, use the project_card or project_column events instead.",
"description": "Runs your workflow when a project board is created or modified. For activity related to cards or columns in a project board, use the `project_card` or `project_column` events instead.",
"one-of": [
"null",
"project-mapping"
@@ -669,7 +669,7 @@
}
},
"project-activity": {
"description": "The types of project activity that trigger the workflow. Can be one or more of: created, edited, opened, closed, or deleted.",
"description": "The types of project activity that trigger the workflow. Supported activity types: `created`, `closed`, `reopened`, `edited`, `deleted`.",
"one-of": [
"project-activity-type",
"project-activity-types"
@@ -684,19 +684,19 @@
"allowed-values": [
"created",
"closed",
"opened",
"reopened",
"edited",
"deleted"
]
},
"project-card-string": {
"description": "Runs your workflow when a card on a project board is created or modified. For activity related to project boards or columns in a project board, use the project or project_column event instead.",
"description": "Runs your workflow when a card on a project board is created or modified. For activity related to project boards or columns in a project board, use the `project` or `project_column` event instead.",
"string": {
"constant": "project_card"
}
},
"project-card": {
"description": "Runs your workflow when a card on a project board is created or modified. For activity related to project boards or columns in a project board, use the project or project_column event instead.",
"description": "Runs your workflow when a card on a project board is created or modified. For activity related to project boards or columns in a project board, use the `project` or `project_column` event instead.",
"one-of": [
"null",
"project-card-mapping"
@@ -710,7 +710,7 @@
}
},
"project-card-activity": {
"description": "The types of project card activity that trigger the workflow. Can be one or more of: created, edited, moved, converted, or deleted.",
"description": "The types of project card activity that trigger the workflow. Supported activity types: `created`, `moved`, `converted`, `edited`, `deleted`.",
"one-of": [
"project-card-activity-type",
"project-card-activity-types"
@@ -731,13 +731,13 @@
]
},
"project-column-string": {
"description": "Runs your workflow when a column on a project board is created or modified. For activity related to project boards or cards in a project board, use the project or project_card event instead.",
"description": "Runs your workflow when a column on a project board is created or modified. For activity related to project boards or cards in a project board, use the `project` or `project_card` event instead.",
"string": {
"constant": "project_column"
}
},
"project-column": {
"description": "Runs your workflow when a column on a project board is created or modified. For activity related to project boards or cards in a project board, use the project or project_card event instead.",
"description": "Runs your workflow when a column on a project board is created or modified. For activity related to project boards or cards in a project board, use the `project` or `project_card` event instead.",
"one-of": [
"null",
"project-column-mapping"
@@ -751,7 +751,7 @@
}
},
"project-column-activity": {
"description": "The types of project column activity that trigger the workflow. Can be one or more of: created, updated, moved, or deleted.",
"description": "The types of project column activity that trigger the workflow. Supported activity types: `created`, `updated`, `moved`, `deleted`.",
"one-of": [
"project-column-activity-type",
"project-column-activity-types"
@@ -781,13 +781,13 @@
"null": {}
},
"pull-request-string": {
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated. For activity related to pull request reviews, pull request review comments, or pull request comments, use the pull_request_review, pull_request_review_comment, or issue_comment events instead.",
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. If no activity types are specified, the workflow runs when a pull request is opened, reopened, or when the head branch of the pull request is updated.",
"string": {
"constant": "pull_request"
}
},
"pull-request": {
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated. For activity related to pull request reviews, pull request review comments, or pull request comments, use the pull_request_review, pull_request_review_comment, or issue_comment events instead.",
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. If no activity types are specified, the workflow runs when a pull request is opened, reopened, or when the head branch of the pull request is updated.",
"one-of": [
"null",
"pull-request-mapping"
@@ -805,7 +805,7 @@
}
},
"pull-request-activity": {
"description": "The types of pull request activity that trigger the workflow. Can be one or more of: created, edited, opened, closed, or deleted.",
"description": "The types of pull request activity that trigger the workflow. Supported activity types: `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `closed`, `reopened`, `synchronize`, `converted_to_draft`, `ready_for_review`, `locked`, `unlocked`, `review_requested`, `review_request_removed`, `auto_merge_enabled`, `auto_merge_disabled`.",
"one-of": [
"pull-request-activity-type",
"pull-request-activity-types"
@@ -838,26 +838,26 @@
]
},
"pull-request-comment-string": {
"description": "Please use the issue_comment event instead.",
"description": "Please use the `issue_comment` event instead.",
"string": {
"constant": "pull_request_comment"
}
},
"pull-request-comment": {
"description": "Please use the issue_comment event instead.",
"description": "Please use the `issue_comment` event instead.",
"one-of": [
"null",
"issue-comment-mapping"
]
},
"pull-request-review-string": {
"description": "Runs your workflow when a pull request review is submitted, edited, or dismissed. A pull request review is a group of pull request review comments in addition to a body comment and a state. For activity related to pull request review comments or pull request comments, use the pull_request_review_comment or issue_comment events instead.",
"description": "Runs your workflow when a pull request review is submitted, edited, or dismissed. A pull request review is a group of pull request review comments in addition to a body comment and a state. For activity related to pull request review comments or pull request comments, use the `pull_request_review_comment` or `issue_comment` events instead.",
"string": {
"constant": "pull_request_review"
}
},
"pull-request-review": {
"description": "Runs your workflow when a pull request review is submitted, edited, or dismissed. A pull request review is a group of pull request review comments in addition to a body comment and a state. For activity related to pull request review comments or pull request comments, use the pull_request_review_comment or issue_comment events instead.",
"description": "Runs your workflow when a pull request review is submitted, edited, or dismissed. A pull request review is a group of pull request review comments in addition to a body comment and a state. For activity related to pull request review comments or pull request comments, use the `pull_request_review_comment` or `issue_comment` events instead.",
"one-of": [
"null",
"pull-request-review-mapping"
@@ -871,7 +871,7 @@
}
},
"pull-request-review-activity": {
"description": "The types of pull request review activity that trigger the workflow. Can be one or more of: submitted, edited, or dismissed.",
"description": "The types of pull request review activity that trigger the workflow. Supported activity types: `submitted`, `edited`, `dismissed`.",
"one-of": [
"pull-request-review-activity-type",
"pull-request-review-activity-types"
@@ -910,7 +910,7 @@
}
},
"pull-request-review-comment-activity": {
"description": "The types of pull request review comment activity that trigger the workflow. Can be one or more of: created, edited, or deleted.",
"description": "The types of pull request review comment activity that trigger the workflow. Supported activity types: `created`, `edited`, `deleted`.",
"one-of": [
"pull-request-review-comment-activity-type",
"pull-request-review-comment-activity-types"
@@ -929,13 +929,13 @@
]
},
"pull-request-target-string": {
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated. This event runs in the context of the base of the pull request, rather than in the context of the merge commit, as the pull_request event does. This prevents execution of unsafe code from the head of the pull request that could alter your repository or steal any secrets you use in your workflow. This event allows your workflow to do things like label or comment on pull requests from forks. Avoid using this event if you need to build or run code from the pull request.",
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. If no activity types are specified, the workflow runs when a pull request is opened, reopened, or when the head branch of the pull request is updated.\n\nThis event runs in the context of the base of the pull request, rather than in the context of the merge commit, as the `pull_request` event does. This prevents execution of unsafe code from the head of the pull request that could alter your repository or steal any secrets you use in your workflow. This event allows your workflow to do things like label or comment on pull requests from forks. Avoid using this event if you need to build or run code from the pull request.",
"string": {
"constant": "pull_request_target"
}
},
"pull-request-target": {
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated. This event runs in the context of the base of the pull request, rather than in the context of the merge commit, as the pull_request event does. This prevents execution of unsafe code from the head of the pull request that could alter your repository or steal any secrets you use in your workflow. This event allows your workflow to do things like label or comment on pull requests from forks. Avoid using this event if you need to build or run code from the pull request.",
"description": "Runs your workflow when activity on a pull request in the workflow's repository occurs. If no activity types are specified, the workflow runs when a pull request is opened, reopened, or when the head branch of the pull request is updated.\n\nThis event runs in the context of the base of the pull request, rather than in the context of the merge commit, as the `pull_request` event does. This prevents execution of unsafe code from the head of the pull request that could alter your repository or steal any secrets you use in your workflow. This event allows your workflow to do things like label or comment on pull requests from forks. Avoid using this event if you need to build or run code from the pull request.",
"one-of": [
"null",
"pull-request-target-mapping"
@@ -953,7 +953,7 @@
}
},
"pull-request-target-activity": {
"description": "The types of pull request activity that trigger the workflow. Can be one or more of: assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, or auto_merge_disabled.",
"description": "The types of pull request activity that trigger the workflow. Supported activity types: `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `closed`, `reopened`, `synchronize`, `converted_to_draft`, `ready_for_review`, `locked`, `unlocked`, `review_requested`, `review_request_removed`, `auto_merge_enabled`, `auto_merge_disabled`.",
"one-of": [
"pull-request-target-activity-type",
"pull-request-target-activity-types"
@@ -1031,7 +1031,7 @@
}
},
"registry-package-activity": {
"description": "The types of registry package activity that trigger the workflow. Can be one or more of: published or updated.",
"description": "The types of registry package activity that trigger the workflow. Supported activity types: `published`, `updated`.",
"one-of": [
"registry-package-activity-type",
"registry-package-activity-types"
@@ -1069,7 +1069,7 @@
}
},
"release-activity": {
"description": "The types of release activity that trigger the workflow. Can be one or more of: published, unpublished, created, edited, deleted, prereleased, or released.",
"description": "The types of release activity that trigger the workflow. Supported activity types: `published`, `unpublished`, `created`, `edited`, `deleted`, `prereleased`, `released`.",
"one-of": [
"release-activity-type",
"release-activity-types"
@@ -1092,25 +1092,25 @@
]
},
"schedule-string": {
"description": "The schedule event allows you to trigger a workflow at a scheduled time. You can schedule a workflow to run at specific UTC times using POSIX cron syntax. Scheduled workflows run on the latest commit on the default or base branch. The shortest interval you can run scheduled workflows is once every 5 minutes. GitHub Actions does not support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly, and @reboot.",
"description": "The `schedule` event allows you to trigger a workflow at a scheduled time.\n\nYou can schedule a workflow to run at specific UTC times using POSIX cron syntax. Scheduled workflows run on the latest commit on the default or base branch. The shortest interval you can run scheduled workflows is once every 5 minutes. GitHub Actions does not support the non-standard syntax `@yearly`, `@monthly`, `@weekly`, `@daily`, `@hourly`, and `@reboot`.",
"string": {
"constant": "schedule"
}
},
"schedule": {
"description": "The schedule event allows you to trigger a workflow at a scheduled time. You can schedule a workflow to run at specific UTC times using POSIX cron syntax. Scheduled workflows run on the latest commit on the default or base branch. The shortest interval you can run scheduled workflows is once every 5 minutes. GitHub Actions does not support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly, and @reboot.",
"description": "The `schedule` event allows you to trigger a workflow at a scheduled time.\n\nYou can schedule a workflow to run at specific UTC times using POSIX cron syntax. Scheduled workflows run on the latest commit on the default or base branch. The shortest interval you can run scheduled workflows is once every 5 minutes. GitHub Actions does not support the non-standard syntax `@yearly`, `@monthly`, `@weekly`, `@daily`, `@hourly`, and `@reboot`.",
"sequence": {
"item-type": "cron-mapping"
}
},
"status-string": {
"description": "Runs your workflow when the status of a Git commit changes. For example, commits can be marked as error, failure, pending, or success. If you want to provide more details about the status change, you may want to use the check_run event.",
"description": "Runs your workflow when the status of a Git commit changes. For example, commits can be marked as `error`, `failure`, `pending`, or `success`. If you want to provide more details about the status change, you may want to use the `check_run` event.",
"string": {
"constant": "status"
}
},
"status": {
"description": "Runs your workflow when the status of a Git commit changes. For example, commits can be marked as error, failure, pending, or success. If you want to provide more details about the status change, you may want to use the check_run event.",
"description": "Runs your workflow when the status of a Git commit changes. For example, commits can be marked as `error`, `failure`, `pending`, or `success`. If you want to provide more details about the status change, you may want to use the `check_run` event.",
"null": {}
},
"watch-string": {
@@ -1134,7 +1134,7 @@
}
},
"watch-activity": {
"description": "The types of watch activity that trigger the workflow. Currently, the only supported type is started.",
"description": "The types of watch activity that trigger the workflow. Supported activity types: `started`.",
"one-of": [
"watch-activity-type",
"watch-activity-types"
@@ -1151,13 +1151,13 @@
]
},
"workflow-run-string": {
"description": "This event occurs when a workflow run is requested or completed. It allows you to execute a workflow based on execution or completion of another workflow. The workflow started by the workflow_run event is able to access secrets and write tokens, even if the previous workflow was not. This is useful in cases where the previous workflow is intentionally not privileged, but you need to take a privileged action in a later workflow.",
"description": "This event occurs when a workflow run is requested or completed. It allows you to execute a workflow based on execution or completion of another workflow. The workflow started by the `workflow_run` event is able to access secrets and write tokens, even if the previous workflow was not. This is useful in cases where the previous workflow is intentionally not privileged, but you need to take a privileged action in a later workflow.",
"string": {
"constant": "workflow_run"
}
},
"workflow-run": {
"description": "This event occurs when a workflow run is requested or completed. It allows you to execute a workflow based on execution or completion of another workflow. The workflow started by the workflow_run event is able to access secrets and write tokens, even if the previous workflow was not. This is useful in cases where the previous workflow is intentionally not privileged, but you need to take a privileged action in a later workflow.",
"description": "This event occurs when a workflow run is requested or completed. It allows you to execute a workflow based on execution or completion of another workflow. The workflow started by the `workflow_run` event is able to access secrets and write tokens, even if the previous workflow was not. This is useful in cases where the previous workflow is intentionally not privileged, but you need to take a privileged action in a later workflow.",
"one-of": [
"null",
"workflow-run-mapping"
@@ -1174,14 +1174,14 @@
}
},
"workflow-run-workflows": {
"description": "The name of the workflow that triggers the workflow_run event. The workflow must be in the same repository as the workflow that uses the workflow_run event.",
"description": "The name of the workflow that triggers the `workflow_run` event. The workflow must be in the same repository as the workflow that uses the `workflow_run` event.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"workflow-run-activity": {
"description": "The types of workflow run activity that trigger the workflow. Can be one or more of: requested or completed.",
"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"
@@ -1200,55 +1200,55 @@
]
},
"event-branches": {
"description": "Use the branches filter to configure your workflow to only run when an event occurs on specific branches. If you use the branches filter and other filters (such as branches-ignore), the workflow will only run when all filters are satisfied.",
"description": "Use the `branches` filter when you want to include branch name patterns or when you want to both include and exclude branch name patterns. You cannot use both the `branches` and `branches-ignore` filters for the same event in a workflow.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"event-branches-ignore": {
"description": "You can use the branches-ignore filter to configure your workflow to not run when an event occurs on specific branches. If you use the branches-ignore filter and other filters (such as branches), the workflow will only run when all filters are satisfied.",
"description": "Use the `branches-ignore` filter when you only want to exclude branch name patterns. You cannot use both the `branches` and `branches-ignore` filters for the same event in a workflow.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"event-tags": {
"description": "Use the tags filter to configure your workflow to only run when an event occurs on specific tags. If you use the tags filter and other filters (such as tags-ignore), the workflow will only run when all filters are satisfied.",
"description": "Use the `tags` filter when you want to include tag name patterns or when you want to both include and exclude tag names patterns. You cannot use both the `tags` and `tags-ignore` filters for the same event in a workflow.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"event-tags-ignore": {
"description": "You can use the tags-ignore filter to configure your workflow to not run when an event occurs on specific tags. If you use the tags-ignore filter and other filters (such as tags), the workflow will only run when all filters are satisfied.",
"description": "Use the `tags-ignore` filter when you only want to exclude tag name patterns. You cannot use both the `tags` and `tags-ignore` filters for the same event in a workflow.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"event-paths": {
"description": "Configure your workflow to only run when an event changes specific files. If you use both the paths filter and other filters (such as paths-ignore), the workflow will only run when all filters are satisfied.",
"description": "Use the `paths` filter when you want to include file path patterns or when you want to both include and exclude file path patterns. You cannot use both the `paths` and `paths-ignore` filters for the same event in a workflow.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"event-paths-ignore": {
"description": "You can use the paths-ignore filter to configure your workflow to not run when an event changes specific files. If you use both the paths-ignore filter and other filters (such as paths), the workflow will only run when all filters are satisfied.",
"description": "Use the `paths-ignore` filter when you only want to exclude file path patterns. You cannot use both the `paths` and `paths-ignore` filters for the same event in a workflow.",
"one-of": [
"non-empty-string",
"sequence-of-non-empty-string"
]
},
"repository-dispatch-string": {
"description": "You can use the GitHub API to trigger a webhook event called repository_dispatch when you want to trigger a workflow for activity that happens outside of GitHub. For more information, see https://developer.github.com/v3/repos/#create-a-repository-dispatch-event. To trigger the custom repository_dispatch webhook event, you must send a POST request to a GitHub API endpoint and provide an event_type name to describe the activity type. To trigger a workflow run, you must also configure your workflow to use the repository_dispatch event.",
"description": "You can use the GitHub API to trigger a webhook event called `repository_dispatch` when you want to trigger a workflow for activity that happens outside of GitHub. For more information, visit the [online documentation](https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event).",
"string": {
"constant": "branch_protection_rule"
}
},
"repository-dispatch": {
"description": "You can use the GitHub API to trigger a webhook event called repository_dispatch when you want to trigger a workflow for activity that happens outside of GitHub. For more information, see https://developer.github.com/v3/repos/#create-a-repository-dispatch-event. To trigger the custom repository_dispatch webhook event, you must send a POST request to a GitHub API endpoint and provide an event_type name to describe the activity type. To trigger a workflow run, you must also configure your workflow to use the repository_dispatch event.",
"description": "You can use the GitHub API to trigger a webhook event called `repository_dispatch` when you want to trigger a workflow for activity that happens outside of GitHub. For more information, visit the [online documentation](https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event).",
"one-of": [
"null",
"repository-dispatch-mapping"
@@ -1262,13 +1262,13 @@
}
},
"workflow-call-string": {
"description": "Allows workflows to be reused by other workflows.",
"description": "The `workflow_call` event is used to indicate that a workflow can be called by another workflow. When a workflow is triggered with the `workflow_call` event, the event payload in the called workflow is the same event payload from the calling workflow.",
"string": {
"constant": "workflow_call"
}
},
"workflow-call": {
"description": "Allows workflows to be reused by other workflows.",
"description": "The `workflow_call` event is used to indicate that a workflow can be called by another workflow. When a workflow is triggered with the `workflow_call` event, the event payload in the called workflow is the same event payload from the calling workflow.",
"one-of": [
"null",
"workflow-call-mapping"
@@ -1284,7 +1284,7 @@
}
},
"workflow-call-inputs": {
"description": "When using the workflow_call keyword, you can optionally specify inputs that are passed to the called workflow from the caller workflow.",
"description": "Inputs that are passed to the called workflow from the caller workflow.",
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "workflow-call-input-definition"
@@ -1303,14 +1303,14 @@
},
"required": {
"type": "boolean",
"description": "A boolean to indicate whether the action requires the input parameter. Set to true when the parameter is required."
"description": "A boolean to indicate whether the action requires the input parameter. Set to `true` when the parameter is required."
},
"default": "workflow-call-input-default"
}
}
},
"workflow-call-input-type": {
"description": "Required if input is defined for the on.workflow_call keyword. The value of this parameter is a string specifying the data type of the input. This must be one of: boolean, number, or string.",
"description": "Required if input is defined for the `on.workflow_call` keyword. The value of this parameter is a string specifying the data type of the input. This must be one of: `boolean`, `number`, or `string`.",
"one-of": [
"input-type-string",
"input-type-boolean",
@@ -1343,7 +1343,7 @@
}
},
"workflow-call-input-default": {
"description": "The default value is used when an input parameter isn't specified in a workflow file.",
"description": "If a `default` parameter is not set, the default value of the input is `false` for boolean, `0` for a number, and `\"\"` for a string.",
"context": [
"github",
"inputs",
@@ -1356,7 +1356,7 @@
]
},
"workflow-call-secrets": {
"description": "A map of the secrets that can be used in the called workflow. Within the called workflow, you can use the secrets context to refer to a secret.",
"description": "A map of the secrets that can be used in the called workflow. Within the called workflow, you can use the `secrets` context to refer to a secret.",
"mapping": {
"loose-key-type": "workflow-call-secret-name",
"loose-value-type": "workflow-call-secret-definition"
@@ -1389,7 +1389,7 @@
}
},
"workflow-call-outputs": {
"description": "When using the workflow_call keyword, you can optionally specify outputs that are provided by the called workflow to the caller workflow.",
"description": "A reusable workflow may generate data that you want to use in the caller workflow. To use these outputs, you must specify them as the outputs of the reusable workflow.",
"mapping": {
"loose-key-type": "workflow-call-output-name",
"loose-value-type": "workflow-call-output-definition"
@@ -1399,7 +1399,7 @@
"string": {
"require-non-empty": true
},
"description": "A string identifier to associate with the output. The value of <output_id> is a map of the input's metadata. The <output_id> must be a unique identifier within the outputs object. The <output_id> must start with a letter or _ and contain only alphanumeric characters, -, or _."
"description": "A string identifier to associate with the output."
},
"workflow-call-output-definition": {
"mapping": {
@@ -1426,13 +1426,13 @@
"string": {}
},
"workflow-dispatch-string": {
"description": "You can now create workflows that are manually triggered with the new workflow_dispatch event. You will then see a 'Run workflow' button on the Actions tab, enabling you to easily trigger a run.",
"description": "The `workflow_dispatch` event allows you to manually trigger a workflow run. A workflow can be manually triggered using the GitHub API, GitHub CLI, or GitHub browser interface.",
"string": {
"constant": "workflow_dispatch"
}
},
"workflow-dispatch": {
"description": "You can now create workflows that are manually triggered with the new workflow_dispatch event. You will then see a 'Run workflow' button on the Actions tab, enabling you to easily trigger a run.",
"description": "The `workflow_dispatch` event allows you to manually trigger a workflow run. A workflow can be manually triggered using the GitHub API, GitHub CLI, or GitHub browser interface.",
"one-of": [
"null",
"workflow-dispatch-mapping"
@@ -1446,7 +1446,7 @@
}
},
"workflow-dispatch-inputs": {
"description": "Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables. Input ids with uppercase letters are converted to lowercase during runtime. We recommended using lowercase input ids.",
"description": "You can configure custom-defined input properties, default input values, and required inputs for the event directly in your workflow. When you trigger the event, you can provide the `ref` and any `inputs`. When the workflow runs, you can access the input values in the `inputs` context.",
"mapping": {
"loose-key-type": "workflow-dispatch-input-name",
"loose-value-type": "workflow-dispatch-input"
@@ -1481,7 +1481,7 @@
}
},
"workflow-dispatch-input-type": {
"description": "A string representing the type of the input. This must be one of: boolean, number, string, choice, or environment.",
"description": "A string representing the type of the input. This must be one of: `boolean`, `number`, `string`, `choice`, or `environment`.",
"one-of": [
"input-type-string",
"input-type-boolean",
@@ -1499,7 +1499,7 @@
]
},
"permissions": {
"description": "You can modify the default permissions granted to the GITHUB_TOKEN, adding or removing access as required, so that you only allow the minimum required access.",
"description": "You can use `permissions` to modify the default permissions granted to the `GITHUB_TOKEN`, adding or removing access as required, so that you only allow the minimum required access.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)",
"one-of": [
"permissions-mapping",
"permission-level-shorthand-read-all",
@@ -1526,7 +1526,7 @@
}
},
"permission-level-any": {
"description": "The permission level for the GITHUB_TOKEN.",
"description": "The permission level for the `GITHUB_TOKEN`.",
"one-of": [
"permission-level-read",
"permission-level-write",
@@ -1546,36 +1546,37 @@
]
},
"permission-level-read": {
"description": "The permission level for the GITHUB_TOKEN. Grants write permission for the specified scope.",
"description": "The permission level for the `GITHUB_TOKEN`. Grants `read` permission for the specified scope.",
"string": {
"constant": "read"
}
},
"permission-level-write": {
"description": "The permission level for the GITHUB_TOKEN. Grants write permission for the specified scope.",
"description": "The permission level for the `GITHUB_TOKEN`. Grants `write` permission for the specified scope.",
"string": {
"constant": "write"
}
},
"permission-level-no-access": {
"description": "The permission level for the GITHUB_TOKEN. Restricts all access for the specified scope.",
"description": "The permission level for the `GITHUB_TOKEN`. Restricts all access for the specified scope.",
"string": {
"constant": "none"
}
},
"permission-level-shorthand-read-all": {
"description": "The permission level for the GITHUB_TOKEN. Grants read access for all scopes.",
"description": "The permission level for the `GITHUB_TOKEN`. Grants `read` access for all scopes.",
"string": {
"constant": "read-all"
}
},
"permission-level-shorthand-write-all": {
"description": "The permission level for the GITHUB_TOKEN. Grants write access for all scopes.",
"description": "The permission level for the `GITHUB_TOKEN`. Grants `write` access for all scopes.",
"string": {
"constant": "write-all"
}
},
"workflow-defaults": {
"description": "Use `defaults` to create a map of default settings that will apply to all jobs in the workflow. You can also set default settings that are only available to a job.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#defaults)",
"mapping": {
"properties": {
"run": "workflow-defaults-run"
@@ -1591,7 +1592,7 @@
}
},
"workflow-env": {
"description": "To set custom environment variables, you need to specify the variables in the workflow file. You can define environment variables for a step, job, or entire workflow using the jobs.<job_id>.steps[*].env, jobs.<job_id>.env, and env keywords. For more information, see https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsenv",
"description": "A map of environment variables that are available to the steps of all jobs in the workflow. You can also set environment variables that are only available to the steps of a single job or to a single step.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#env)",
"context": [
"github",
"inputs",
@@ -1604,7 +1605,7 @@
}
},
"jobs": {
"description": "A workflow run is made up of one or more jobs. Jobs run in parallel by default. To run jobs sequentially, you can define dependencies on other jobs using the jobs.<job_id>.needs keyword. Each job runs in a fresh instance of the virtual environment specified by runs-on. You can run an unlimited number of jobs as long as you are within the workflow usage limits. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#usage-limits.",
"description": "A workflow run is made up of one or more `jobs`, which run in parallel by default. To run jobs sequentially, you can define dependencies on other jobs using the `jobs.<job_id>.needs` keyword. Each job runs in a runner environment specified by `runs-on`.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobs)",
"mapping": {
"loose-key-type": "job-id",
"loose-value-type": "job"
@@ -1614,10 +1615,10 @@
"string": {
"require-non-empty": true
},
"description": "A string identifier to associate with the job. The value of <job_id> is a map of the input's metadata. The <job_id> must be a unique identifier within the inputs object. The <job_id> must start with a letter or _ and contain only alphanumeric characters, -, or _."
"description": "A unique identifier for the job. The identifier must start with a letter or _ and contain only alphanumeric characters, -, or _."
},
"job": {
"description": "Each job must have an id to associate with the job. The key job_id is a string and its value is a map of the job's configuration data. You must replace <job_id> with a string that is unique to the jobs object. The <job_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.",
"description": "Each job must have an id to associate with the job. The key `job_id` is a string and its value is a map of the job's configuration data. You must replace `<job_id>` with a string that is unique to the jobs object. The `<job_id>` must start with a letter or _ and contain only alphanumeric characters, -, or _.",
"one-of": [
"job-factory",
"workflow-job"
@@ -1666,7 +1667,7 @@
"description": "The name of the job displayed on GitHub."
},
"uses": {
"description": "The location and version of a reusable workflow file to run as a job, of the form './{path/to}/{localfile}.yml' or '{owner}/{repo}/{path}/{filename}@{ref}'. {ref} can be a SHA, a release tag, or a branch name. Using the commit SHA is the safest for stability and security.",
"description": "The location and version of a reusable workflow file to run as a job. Use one of the following syntaxes:\n\n* `{owner}/{repo}/.github/workflows/{filename}@{ref}` for reusable workflows in public and private repositories.\n* `./.github/workflows/{filename}` for reusable workflows in the same repository.\n\n{ref} can be a SHA, a release tag, or a branch name. Using the commit SHA is the safest for stability and security.",
"type": "non-empty-string",
"required": true
},
@@ -1681,14 +1682,14 @@
}
},
"workflow-job-with": {
"description": "A map of inputs that are passed to the called workflow. Any inputs that you pass must match the input specifications defined in the called workflow. Unlike 'jobs.<job_id>.steps[*].with', the inputs you pass with 'jobs.<job_id>.with' are not be available as environment variables in the called workflow. Instead, you can reference the inputs by using the inputs context.",
"description": "When a job is used to call a reusable workflow, you can use `with` to provide a map of inputs that are passed to the called workflow.\n\nAny inputs that you pass must match the input specifications defined in the called workflow.",
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "scalar-needs-context"
}
},
"workflow-job-secrets": {
"description": "When a job is used to call a reusable workflow, you can use 'secrets' to provide a map of secrets that are passed to the called workflow. Any secrets that you pass must match the names defined in the called workflow.",
"description": "When a job is used to call a reusable workflow, you can use `secrets` to provide a map of secrets that are passed to the called workflow.\n\nAny secrets that you pass must match the names defined in the called workflow.",
"one-of": [
"workflow-job-secrets-mapping",
"workflow-job-secrets-inherit"
@@ -1706,14 +1707,14 @@
}
},
"needs": {
"description": "Identifies any jobs that must complete successfully before this job will run. It can be a string or array of strings. If a job fails, all jobs that need it are skipped unless the jobs use a conditional statement that causes the job to continue.",
"description": "Use `needs` to identify any jobs that must complete successfully before this job will run. It can be a string or array of strings. If a job fails, all jobs that need it are skipped unless the jobs use a conditional expression that causes the job to continue. If a run contains a series of jobs that need each other, a failure applies to all jobs in the dependency chain from the point of failure onwards.",
"one-of": [
"sequence-of-non-empty-string",
"non-empty-string"
]
},
"job-if": {
"description": "You can use the if conditional to prevent a job from running unless a condition is met. You can use any supported context and expression to create a conditional. Expressions in an if conditional do not require the bracketed expression syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.",
"description": "You can use the `if` conditional to prevent a job from running unless a condition is met. You can use any supported context and expression to create a conditional.",
"context": [
"github",
"inputs",
@@ -1749,7 +1750,7 @@
]
},
"strategy": {
"description": "A strategy creates a build matrix for your jobs. You can define different variations of an environment to run each job in.",
"description": "Use `strategy` to use a matrix strategy for your jobs. A matrix strategy lets you use variables in a single job definition to automatically create multiple job runs that are based on the combinations of the variables. ",
"context": [
"github",
"inputs",
@@ -1760,27 +1761,27 @@
"properties": {
"fail-fast": {
"type": "boolean",
"description": "When set to true, GitHub cancels all in-progress jobs if any matrix job fails. Default: true"
"description": "Setting `fail-fast` to `false` prevents GitHub from canceling all in-progress jobs if any matrix job fails. Default: `true`"
},
"max-parallel": {
"type": "number",
"description": "The maximum number of jobs that can run simultaneously when using a matrix job strategy. By default, GitHub will maximize the number of jobs run in parallel depending on the available runners on GitHub-hosted virtual machines."
"description": "The maximum number of jobs that can run simultaneously when using a matrix job strategy. By default, GitHub will maximize the number of jobs run in parallel depending on runner availability."
},
"matrix": "matrix"
}
}
},
"matrix": {
"description": "A build matrix is a set of different configurations of the virtual environment. For example you might run a job against more than one supported version of a language, operating system, or tool. Each configuration is a copy of the job that runs and reports a status. You can specify a matrix by supplying an array for the configuration options. For example, if the GitHub virtual environment supports Node.js versions 6, 8, and 10 you could specify an array of those versions in the matrix. When you define a matrix of operating systems, you must set the required runs-on keyword to the operating system of the current job, rather than hard-coding the operating system name. To access the operating system name, you can use the matrix.os context parameter to set runs-on. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.",
"description": "Use `matrix` to define a matrix of different job configurations. Within your matrix, define one or more variables followed by an array of values.",
"mapping": {
"properties": {
"include": {
"type": "matrix-filter",
"description": "Use include to expand existing matrix configurations or to add new configurations. The value of include is a list of objects."
"description": "Use `include` to expand existing matrix configurations or to add new configurations. The value of `include` is a list of objects.\n\nFor each object in the `include` list, the key:value pairs in the object will be added to each of the matrix combinations if none of the key:value pairs overwrite any of the original matrix values. If the object cannot be added to any of the matrix combinations, a new matrix combination will be created instead. Note that the original matrix values will not be overwritten, but added matrix values can be overwritten."
},
"exclude": {
"type": "matrix-filter",
"description": "To remove specific configurations defined in the matrix, use exclude. An excluded configuration only has to be a partial match for it to be excluded."
"description": "To remove specific configurations defined in the matrix, use `exclude`. An excluded configuration only has to be a partial match for it to be excluded."
}
},
"loose-key-type": "non-empty-string",
@@ -1799,7 +1800,7 @@
}
},
"runs-on": {
"description": "The type of machine to run the job on. The machine can be either a GitHub-hosted runner, or a self-hosted runner.",
"description": "Use `runs-on` to define the type of machine to run the job on.\n* The destination machine can be either a GitHub-hosted runner, larger runner, or a self-hosted runner.\n* You can target runners based on the labels assigned to them, or their group membership, or a combination of these.\n* You can provide `runs-on` as a single string or as an array of strings.\n* If you specify an array of strings, your workflow will execute on any runner that matches all of the specified `runs-on` values.\n* If you would like to run your workflow on multiple machines, use `jobs.<job_id>.strategy`.",
"context": [
"github",
"inputs",
@@ -1833,7 +1834,7 @@
]
},
"job-env": {
"description": "A map of environment variables that are available to all steps in the job.",
"description": "A map of variables that are available to all steps in the job.",
"context": [
"github",
"inputs",
@@ -1849,7 +1850,7 @@
}
},
"workflow-concurrency": {
"description": "Concurrency ensures that only a single workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. You can also specify concurrency at the workflow level.",
"description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression.\n\nYou can also specify `concurrency` at the job level.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency)",
"context": [
"github",
"inputs",
@@ -1861,7 +1862,7 @@
]
},
"job-concurrency": {
"description": "Concurrency ensures that only a single job using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. You can also specify concurrency at the workflow level.",
"description": "Concurrency ensures that only a single job using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the `secrets` context.\n\nYou can also specify `concurrency` at the workflow level.",
"context": [
"github",
"inputs",
@@ -1876,13 +1877,13 @@
]
},
"concurrency-mapping": {
"description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. You can also specify concurrency at the workflow level.",
"description": "Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression.\n\nYou can also specify `concurrency` at the job level.\n\n[Documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency)",
"mapping": {
"properties": {
"group": {
"type": "non-empty-string",
"required": true,
"description": "When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled."
"description": "When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be `pending`. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify `cancel-in-progress: true`."
},
"cancel-in-progress": {
"type": "boolean",
@@ -1921,7 +1922,7 @@
}
},
"job-environment-name": {
"description": "The name of the environment.",
"description": "The name of the environment used by the job.",
"context": [
"github",
"inputs",
@@ -1933,7 +1934,7 @@
"string": {}
},
"job-defaults": {
"description": "A map of default settings that will apply to all steps in the job.",
"description": "A map of default settings that will apply to all steps in the job. You can also set default settings for the entire workflow.",
"mapping": {
"properties": {
"run": "job-defaults-run"
@@ -1958,14 +1959,14 @@
}
},
"job-outputs": {
"description": "A map of outputs for a job. Job outputs are available to all downstream jobs that depend on this job.",
"description": "A map of outputs for a called workflow. Called workflow outputs are available to all downstream jobs in the caller workflow. Each output has an identifier, an optional `description,` and a `value`. The `value` must be set to the value of an output from a job within the called workflow.",
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "string-runner-context"
}
},
"steps": {
"description": "A job contains a sequence of tasks called steps. Steps can run commands, run setup tasks, or run an action in your repository, a public repository, or an action published in a Docker registry. Not all steps run actions, but all actions run as a step. Each step runs in its own process in the virtual environment and has access to the workspace and filesystem. Because steps run in their own process, changes to environment variables are not preserved between steps. GitHub provides built-in steps to set up and complete a job. Must contain either `uses` or `run`.",
"description": "A job contains a sequence of tasks called `steps`. Steps can run commands, run setup tasks, or run an action in your repository, a public repository, or an action published in a Docker registry. Not all steps run actions, but all actions run as a step. Each step runs in its own process in the runner environment and has access to the workspace and filesystem. Because steps run in their own process, changes to environment variables are not preserved between steps. GitHub provides built-in steps to set up and complete a job. Must contain either `uses` or `run`.",
"sequence": {
"item-type": "steps-item"
}
@@ -1985,7 +1986,7 @@
"timeout-minutes": "step-timeout-minutes",
"run": {
"type": "string-steps-context",
"description": "Runs command-line programs using the operating system's shell. If you do not provide a name, the step name will default to the text specified in the run command. Commands run using non-login shells by default. You can choose a different shell and customize the shell used to run commands. Each run keyword represents a new process and shell in the virtual environment. When you provide multi-line commands, each line runs in the same shell.",
"description": "Runs command-line programs using the operating system's shell. If you do not provide a `name`, the step name will default to the text specified in the `run` command. Commands run using non-login shells by default. You can choose a different shell and customize the shell used to run commands. Each `run` keyword represents a new process and shell in the virtual environment. When you provide multi-line commands, each line runs in the same shell.",
"required": true
},
"continue-on-error": "step-continue-on-error",
@@ -2004,7 +2005,7 @@
"continue-on-error": "step-continue-on-error",
"timeout-minutes": "step-timeout-minutes",
"uses": {
"description": "Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image (https://hub.docker.com/).",
"description": "Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image.",
"type": "non-empty-string",
"required": true
},
@@ -2029,13 +2030,13 @@
"hashFiles(1,255)"
],
"boolean": {},
"description": "Prevents a job from failing when a step fails. Set to true to allow a job to pass when this step fails."
"description": "Prevents a job from failing when a step fails. Set to `true` to allow a job to pass when this step fails."
},
"step-id": {
"string": {
"require-non-empty": true
},
"description": "A unique identifier for the step. You can use the id to reference the step in contexts."
"description": "A unique identifier for the step. You can use the `id` to reference the step in contexts."
},
"step-if": {
"context": [
@@ -2055,7 +2056,7 @@
"success(0,0)",
"hashFiles(1,255)"
],
"description": "You can use the if conditional to prevent a step from running unless a condition is met. You can use any supported context and expression to create a conditional. Expressions in an if conditional do not require the bracketed expression syntax.",
"description": "Use the `if` conditional to prevent a step from running unless a condition is met. Any supported context and expression can be used to create a conditional. Expressions in an `if` conditional do not require the bracketed expression syntax. When you use expressions in an `if` conditional, you may omit the expression syntax because GitHub automatically evaluates the `if` conditional as an expression.",
"string": {
"is-expression": true
}
@@ -2087,7 +2088,7 @@
]
},
"step-env": {
"description": "Sets environment variables for steps to use in the virtual environment. You can also set environment variables for the entire workflow or a job.",
"description": "Sets variables for steps to use in the runner environment. You can also set variables for the entire workflow or a job.",
"context": [
"github",
"inputs",
@@ -2144,7 +2145,7 @@
"description": "The maximum number of minutes to run the step before killing the process."
},
"step-with": {
"description": "A map of the input parameters defined by the action. Each input parameter is a key/value pair. Input parameters are set as environment variables. The variable is prefixed with INPUT_ and converted to upper case.",
"description": "A map of the input parameters defined by the action. Each input parameter is a key/value pair. Input parameters are set as variables. When you specify an input in a workflow file or use a default input value, GitHub creates a variable for the input with the name `INPUT_<VARIABLE_NAME>`. The variable created converts input names to uppercase letters and replaces spaces with `_`.",
"context": [
"github",
"inputs",
@@ -2165,7 +2166,7 @@
}
},
"container": {
"description": "A container to run any steps in a job that don't already specify a container. If you have steps that use both script and container actions, the container actions will run as sibling containers on the same network with the same volume mounts. If you do not set a container, all steps will run directly on the host specified by runs-on unless a step refers to an action configured to run in a container.",
"description": "A container to run any steps in a job that don't already specify a container. If you have steps that use both script and container actions, the container actions will run as sibling containers on the same network with the same volume mounts.\n\nIf you do not set a container, all steps will run directly on the host specified by runs-on unless a step refers to an action configured to run in a container.",
"context": [
"github",
"inputs",
@@ -2184,20 +2185,20 @@
"properties": {
"image": {
"type": "non-empty-string",
"description": "The Docker image to use as the container to run the job. The value can be the Docker Hub image name or a registry name."
"description": "Use `jobs.<job_id>.container.image` to define the Docker image to use as the container to run the action. The value can be the Docker Hub image or a registry name."
},
"options": {
"type": "non-empty-string",
"description": "Additional Docker container resource options. For a list of options, see https://docs.docker.com/engine/reference/commandline/create/#options."
"description": "Use `jobs.<job_id>.container.options` to configure additional Docker container resource options."
},
"env": "container-env",
"ports": {
"type": "sequence-of-non-empty-string",
"description": "Sets an array of ports to expose on the container."
"description": "Use `jobs.<job_id>.container.ports` to set an array of ports to expose on the container."
},
"volumes": {
"type": "sequence-of-non-empty-string",
"description": "Sets an array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host.\nTo specify a volume, you specify the source and destination path: <source>:<destinationPath>\nThe <source> is a volume name or an absolute path on the host machine, and <destinationPath> is an absolute path in the container."
"description": "Use `jobs.<job_id>.container.volumes` to set an array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host."
},
"credentials": "container-registry-credentials"
}
@@ -2233,7 +2234,7 @@
]
},
"container-registry-credentials": {
"description": "If the image's container registry requires authentication to pull the image, you can use credentials to set a map of the username and password. The credentials are the same values that you would provide to the `docker login` command.",
"description": "If the image's container registry requires authentication to pull the image, you can use `jobs.<job_id>.container.credentials` to set a map of the username and password. The credentials are the same values that you would provide to the `docker login` command.",
"context": [
"github",
"inputs",
@@ -2249,7 +2250,7 @@
}
},
"container-env": {
"description": "Sets an array of environment variables in the container.",
"description": "Use `jobs.<job_id>.container.env` to set a map of variables in the container.",
"mapping": {
"loose-key-type": "non-empty-string",
"loose-value-type": "string-runner-context"
@@ -2442,13 +2443,13 @@
"string": {
"require-non-empty": true
},
"description": "You can override the default shell settings in the runner's operating system using the shell keyword. You can use built-in shell keywords, or you can define a custom set of shell options."
"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": {
"require-non-empty": true
},
"description": "Using the working-directory keyword, you can specify the working directory of where to run the command."
"description": "The `working-directory` keyword specifies the working directory where the command is run."
},
"cron-mapping": {
"mapping": {
@@ -1,6 +1,6 @@
import {isCollection, isDocument, isMap, isPair, isScalar, isSeq, LineCounter, parseDocument, Scalar} from "yaml";
import {LinePos} from "yaml/dist/errors";
import {NodeBase} from "yaml/dist/nodes/Node";
import type {LinePos} from "yaml/dist/errors";
import type {NodeBase} from "yaml/dist/nodes/Node";
import {ObjectReader} from "../templates/object-reader";
import {EventType, ParseEvent} from "../templates/parse-event";
import {TemplateContext} from "../templates/template-context";
@@ -110,14 +110,8 @@ export class YamlObjectReader implements ObjectReader {
case "boolean":
return new BooleanToken(fileId, range, value, undefined);
case "string": {
// If the string is a YAML block string, include the original source
let source: string | undefined;
if (
(token.type === "BLOCK_LITERAL" || // | multi-line strings
token.type === "BLOCK_FOLDED") && // > multi-line strings
token.srcToken &&
token.srcToken.type === "block-scalar"
) {
if (token.srcToken && "source" in token.srcToken) {
source = token.srcToken.source;
}
@@ -85,7 +85,7 @@ on:
types:
- created
- closed
- opened
- reopened
- edited
- deleted
project_card:
@@ -327,7 +327,7 @@ jobs:
"types": [
"created",
"closed",
"opened",
"reopened",
"edited",
"deleted"
]
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "browser-playground",
"version": "0.1.79",
"version": "0.1.94",
"description": "",
"private": true,
"main": "index.js",
"type": "module",
"dependencies": {
"@github/actions-languageserver": "^0.1.79",
"@github/actions-languageserver": "^0.1.94",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
"monaco-languageclient": "^4.0.3",
@@ -1,10 +1 @@
import {BrowserMessageReader, BrowserMessageWriter, createConnection} from "vscode-languageserver/browser.js";
import {initConnection} from "@github/actions-languageserver/connection";
const messageReader = new BrowserMessageReader(self);
const messageWriter = new BrowserMessageWriter(self);
const connection = createConnection(messageReader, messageWriter);
initConnection(connection);
import "@github/actions-languageserver";
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.1.79"
"version": "0.1.94"
}
+238 -61
View File
@@ -18,7 +18,7 @@
},
"actions-expressions": {
"name": "@github/actions-expressions",
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.0.3",
@@ -34,11 +34,11 @@
},
"actions-languageserver": {
"name": "@github/actions-languageserver",
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"dependencies": {
"@github/actions-languageservice": "^0.1.79",
"@github/actions-workflow-parser": "^0.1.79",
"@github/actions-languageservice": "^0.1.94",
"@github/actions-workflow-parser": "^0.1.94",
"@octokit/rest": "^19.0.5",
"vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7",
@@ -46,8 +46,8 @@
},
"devDependencies": {
"@types/jest": "^29.0.3",
"fetch-mock": "^9.11.0",
"jest": "^29.0.3",
"nock": "^13.2.9",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
@@ -59,11 +59,11 @@
},
"actions-languageservice": {
"name": "@github/actions-languageservice",
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"dependencies": {
"@github/actions-expressions": "^0.1.79",
"@github/actions-workflow-parser": "^0.1.79",
"@github/actions-expressions": "^0.1.94",
"@github/actions-workflow-parser": "^0.1.94",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"yaml": "^2.1.1"
@@ -82,10 +82,10 @@
},
"actions-workflow-parser": {
"name": "@github/actions-workflow-parser",
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"dependencies": {
"@github/actions-expressions": "^0.1.79",
"@github/actions-expressions": "^0.1.94",
"yaml": "^2.0.0-8"
},
"devDependencies": {
@@ -104,10 +104,10 @@
}
},
"browser-playground": {
"version": "0.1.79",
"version": "0.1.94",
"license": "MIT",
"dependencies": {
"@github/actions-languageserver": "^0.1.79",
"@github/actions-languageserver": "^0.1.94",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
"monaco-languageclient": "^4.0.3",
@@ -655,6 +655,18 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -5242,6 +5254,17 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
"node_modules/core-js": {
"version": "3.27.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==",
"dev": true,
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -6339,6 +6362,71 @@
"bser": "2.1.1"
}
},
"node_modules/fetch-mock": {
"version": "9.11.0",
"resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz",
"integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==",
"dev": true,
"dependencies": {
"@babel/core": "^7.0.0",
"@babel/runtime": "^7.0.0",
"core-js": "^3.0.0",
"debug": "^4.1.1",
"glob-to-regexp": "^0.4.0",
"is-subset": "^0.1.1",
"lodash.isequal": "^4.5.0",
"path-to-regexp": "^2.2.1",
"querystring": "^0.2.0",
"whatwg-url": "^6.5.0"
},
"engines": {
"node": ">=4.0.0"
},
"funding": {
"type": "charity",
"url": "https://www.justgiving.com/refugee-support-europe"
},
"peerDependencies": {
"node-fetch": "*"
},
"peerDependenciesMeta": {
"node-fetch": {
"optional": true
}
}
},
"node_modules/fetch-mock/node_modules/path-to-regexp": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
"integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==",
"dev": true
},
"node_modules/fetch-mock/node_modules/tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
"integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
"dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/fetch-mock/node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
"dev": true
},
"node_modules/fetch-mock/node_modules/whatwg-url": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
"dev": true,
"dependencies": {
"lodash.sortby": "^4.7.0",
"tr46": "^1.0.1",
"webidl-conversions": "^4.0.2"
}
},
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -7696,6 +7784,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-subset": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
"integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==",
"dev": true
},
"node_modules/is-text-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
@@ -8844,6 +8938,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"dev": true
},
"node_modules/lodash.ismatch": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
@@ -8862,6 +8962,12 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
"dev": true
},
"node_modules/lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@@ -9514,21 +9620,6 @@
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"node_modules/nock": {
"version": "13.2.9",
"resolved": "https://registry.npmjs.org/nock/-/nock-13.2.9.tgz",
"integrity": "sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==",
"dev": true,
"dependencies": {
"debug": "^4.1.0",
"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.21",
"propagate": "^2.0.0"
},
"engines": {
"node": ">= 10.13"
}
},
"node_modules/node-addon-api": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
@@ -11021,15 +11112,6 @@
"read": "1"
}
},
"node_modules/propagate": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@@ -11103,6 +11185,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystring": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
"integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==",
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
"dev": true,
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -11571,6 +11663,12 @@
"node": ">=8"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"dev": true
},
"node_modules/regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -14316,6 +14414,15 @@
"@babel/helper-plugin-utils": "^7.19.0"
}
},
"@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -14428,12 +14535,12 @@
"@github/actions-languageserver": {
"version": "file:actions-languageserver",
"requires": {
"@github/actions-languageservice": "^0.1.79",
"@github/actions-workflow-parser": "^0.1.79",
"@github/actions-languageservice": "^0.1.94",
"@github/actions-workflow-parser": "^0.1.94",
"@octokit/rest": "^19.0.5",
"@types/jest": "^29.0.3",
"fetch-mock": "^9.11.0",
"jest": "^29.0.3",
"nock": "^13.2.9",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
@@ -14446,8 +14553,8 @@
"@github/actions-languageservice": {
"version": "file:actions-languageservice",
"requires": {
"@github/actions-expressions": "^0.1.79",
"@github/actions-workflow-parser": "^0.1.79",
"@github/actions-expressions": "^0.1.94",
"@github/actions-workflow-parser": "^0.1.94",
"@types/jest": "^29.0.3",
"jest": "^29.0.3",
"prettier": "^2.7.1",
@@ -14462,7 +14569,7 @@
"@github/actions-workflow-parser": {
"version": "file:actions-workflow-parser",
"requires": {
"@github/actions-expressions": "^0.1.79",
"@github/actions-expressions": "^0.1.94",
"@types/jest": "^29.0.3",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
@@ -17389,7 +17496,7 @@
"browser-playground": {
"version": "file:browser-playground",
"requires": {
"@github/actions-languageserver": "^0.1.79",
"@github/actions-languageserver": "^0.1.94",
"css-loader": "^6.7.2",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
@@ -18025,6 +18132,12 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
"core-js": {
"version": "3.27.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==",
"dev": true
},
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -18855,6 +18968,58 @@
"bser": "2.1.1"
}
},
"fetch-mock": {
"version": "9.11.0",
"resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz",
"integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==",
"dev": true,
"requires": {
"@babel/core": "^7.0.0",
"@babel/runtime": "^7.0.0",
"core-js": "^3.0.0",
"debug": "^4.1.1",
"glob-to-regexp": "^0.4.0",
"is-subset": "^0.1.1",
"lodash.isequal": "^4.5.0",
"path-to-regexp": "^2.2.1",
"querystring": "^0.2.0",
"whatwg-url": "^6.5.0"
},
"dependencies": {
"path-to-regexp": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
"integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==",
"dev": true
},
"tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
"integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
"dev": true
},
"whatwg-url": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
"dev": true,
"requires": {
"lodash.sortby": "^4.7.0",
"tr46": "^1.0.1",
"webidl-conversions": "^4.0.2"
}
}
}
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -19903,6 +20068,12 @@
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true
},
"is-subset": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
"integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==",
"dev": true
},
"is-text-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
@@ -20791,6 +20962,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"dev": true
},
"lodash.ismatch": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
@@ -20809,6 +20986,12 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
"dev": true
},
"lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@@ -21302,18 +21485,6 @@
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"nock": {
"version": "13.2.9",
"resolved": "https://registry.npmjs.org/nock/-/nock-13.2.9.tgz",
"integrity": "sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.21",
"propagate": "^2.0.0"
}
},
"node-addon-api": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
@@ -22411,12 +22582,6 @@
"read": "1"
}
},
"propagate": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
"dev": true
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@@ -22473,6 +22638,12 @@
"side-channel": "^1.0.4"
}
},
"querystring": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
"integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==",
"dev": true
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -22831,6 +23002,12 @@
"strip-indent": "^3.0.0"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"dev": true
},
"regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",