Support descriptions for contexts

This commit is contained in:
Christopher Schleiden
2023-01-10 17:44:48 -08:00
parent eb25801e47
commit 43fecc8648
22 changed files with 407 additions and 150 deletions
@@ -1,4 +1,4 @@
import {data} from "@github/actions-expressions";
import {data, 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";
@@ -23,7 +23,7 @@ export function contextProviders(
const getContext = async (
name: string,
defaultContext: data.Dictionary | undefined,
defaultContext: DescriptionDictionary | undefined,
workflowContext: WorkflowContext
) => {
switch (name) {
@@ -46,8 +46,13 @@ export function contextProviders(
const secrets = await getSecrets(octokit, cache, repo, environmentName);
defaultContext = defaultContext || new data.Dictionary();
secrets.forEach(secret => defaultContext!.add(secret.value, new data.StringData("***")));
defaultContext = defaultContext || new DescriptionDictionary();
secrets.repoSecrets.forEach(secret =>
defaultContext!.add(secret.value, new data.StringData("***"), "Repository secret")
);
secrets.environmentSecrets.forEach(secret =>
defaultContext!.add(secret.value, new data.StringData("***"), `Secret for environment \`${environmentName}\``)
);
return defaultContext;
}
case "steps": {
@@ -8,28 +8,21 @@ export async function getSecrets(
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 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,4 +1,4 @@
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";
@@ -80,18 +80,29 @@ it("adds action outputs", async () => {
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);