diff --git a/languageserver/package.json b/languageserver/package.json index 89018ca..fd4aaed 100644 --- a/languageserver/package.json +++ b/languageserver/package.json @@ -43,6 +43,7 @@ "@github/actions-languageservice": "^0.1.169", "@github/actions-workflow-parser": "^0.1.169", "@octokit/rest": "^19.0.7", + "@octokit/types": "^9.0.0", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "yaml": "^2.1.3" diff --git a/languageserver/src/context-providers/secrets.ts b/languageserver/src/context-providers/secrets.ts index 837124f..d369ce4 100644 --- a/languageserver/src/context-providers/secrets.ts +++ b/languageserver/src/context-providers/secrets.ts @@ -84,7 +84,7 @@ export async function getSecrets( secretsMap.set(secret.value.toLowerCase(), { key: secret.value, value: new data.StringData("***"), - description: `Secret for environment \`${environmentName}\`` + description: `Secret for environment \`${environmentName || ""}\`` }) ); diff --git a/languageserver/src/context-providers/steps.ts b/languageserver/src/context-providers/steps.ts index eea315b..c89aea3 100644 --- a/languageserver/src/context-providers/steps.ts +++ b/languageserver/src/context-providers/steps.ts @@ -51,7 +51,7 @@ export async function getStepsContext( const stepContext = new DescriptionDictionary(); for (const {key, value, description} of defaultStepContext.pairs()) { switch (key) { - case "outputs": + case "outputs": { const outputs = await getActionOutputs(octokit, cache, action); if (!outputs) { stepContext.add(key, value, description); @@ -63,6 +63,7 @@ export async function getStepsContext( } stepContext.add("outputs", outputsDict); break; + } default: stepContext.add(key, value, description); } diff --git a/languageserver/src/context-providers/variables.ts b/languageserver/src/context-providers/variables.ts index aa3121b..b590406 100644 --- a/languageserver/src/context-providers/variables.ts +++ b/languageserver/src/context-providers/variables.ts @@ -73,7 +73,7 @@ export async function getVariables( variablesMap.set(variable.key.toLowerCase(), { key: variable.key, value: new data.StringData(variable.value.coerceString()), - description: `${variable.value.coerceString()} - Variable for environment \`${environmentName}\`` + description: `${variable.value.coerceString()} - Variable for environment \`${environmentName || ""}\`` }) ); diff --git a/languageserver/src/description-providers/action-description.test.ts b/languageserver/src/description-providers/action-description.test.ts index 72a0bd8..85df767 100644 --- a/languageserver/src/description-providers/action-description.test.ts +++ b/languageserver/src/description-providers/action-description.test.ts @@ -25,7 +25,7 @@ async function getDescription(mock: fetchMock.FetchMockSandbox) { } }), new TTLCache(), - workflowContext.step! + workflowContext.step! // eslint-disable-line @typescript-eslint/no-non-null-assertion ); } diff --git a/languageserver/src/description-providers/action-input.test.ts b/languageserver/src/description-providers/action-input.test.ts index 0e2ce7d..bedea85 100644 --- a/languageserver/src/description-providers/action-input.test.ts +++ b/languageserver/src/description-providers/action-input.test.ts @@ -27,7 +27,7 @@ async function getDescription(input: string, mock: fetchMock.FetchMockSandbox) { } }), new TTLCache(), - workflowContext.step!, + workflowContext.step!, // eslint-disable-line @typescript-eslint/no-non-null-assertion new StringToken(undefined, undefined, input, undefined) ); } diff --git a/languageserver/src/test-utils/workflow-context.ts b/languageserver/src/test-utils/workflow-context.ts index edde697..4138bb5 100644 --- a/languageserver/src/test-utils/workflow-context.ts +++ b/languageserver/src/test-utils/workflow-context.ts @@ -1,19 +1,13 @@ import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context"; -import {convertWorkflowTemplate, parseWorkflow, TraceWriter} from "@github/actions-workflow-parser"; +import {convertWorkflowTemplate, NoOperationTraceWriter, parseWorkflow} from "@github/actions-workflow-parser"; import {isJob} from "@github/actions-workflow-parser/model/type-guards"; -const nullTrace: TraceWriter = { - info: x => {}, - verbose: x => {}, - error: x => {} -}; - export async function createWorkflowContext( workflow: string, job?: string, stepIndex?: number ): Promise { - const parsed = parseWorkflow({name: "test.yaml", content: workflow}, nullTrace); + const parsed = parseWorkflow({name: "test.yaml", content: workflow}, new NoOperationTraceWriter()); if (!parsed.value) { throw new Error("Failed to parse workflow"); } diff --git a/languageserver/src/utils/action-metadata.ts b/languageserver/src/utils/action-metadata.ts index c23cfae..be6cd4d 100644 --- a/languageserver/src/utils/action-metadata.ts +++ b/languageserver/src/utils/action-metadata.ts @@ -3,6 +3,7 @@ import {error} from "@github/actions-languageservice/log"; import {Octokit, RestEndpointMethodTypes} from "@octokit/rest"; import {parse} from "yaml"; import {TTLCache} from "./cache"; +import {errorMessage, errorStatus} from "./error"; export async function fetchActionMetadata( client: Octokit, @@ -17,15 +18,15 @@ export async function fetchActionMetadata( } // https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions - return parse(metadata); + return parse(metadata) as ActionMetadata; } async function getActionMetadata(client: Octokit, action: ActionReference): Promise { let resp: RestEndpointMethodTypes["repos"]["getContent"]["response"]; try { resp = await fetchAction(client, action); - } catch (e: any) { - error(`Failed to fetch action metadata for ${actionIdentifier(action)}: '${e?.message || ""}'`); + } catch (e) { + error(`Failed to fetch action metadata for ${actionIdentifier(action)}: '${errorMessage(e)}'`); return; } @@ -50,9 +51,9 @@ async function fetchAction(client: Octokit, action: ActionReference) { ref: action.ref, path: action.path ? `${action.path}/action.yml` : "action.yml" }); - } catch (e: any) { + } catch (e) { // If action.yml doesn't exist, try action.yaml - if (e.status === 404) { + if (errorStatus(e) === 404) { return await client.repos.getContent({ owner: action.owner, repo: action.name, diff --git a/languageserver/src/utils/error.ts b/languageserver/src/utils/error.ts new file mode 100644 index 0000000..843eaad --- /dev/null +++ b/languageserver/src/utils/error.ts @@ -0,0 +1,27 @@ +import {RequestError} from "@octokit/types"; + +export function errorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + if (typeof error === "string") { + return error; + } + + if ("name" in (error as RequestError)) { + return (error as RequestError).name; + } + + const status = errorStatus(error); + if (status) { + return `HTTP ${status}`; + } + + return "Unknown error"; +} + +export function errorStatus(error: unknown): number | undefined { + if ("status" in (error as RequestError)) { + return (error as RequestError).status; + } +} diff --git a/languageserver/src/utils/repo-permission.ts b/languageserver/src/utils/repo-permission.ts index 5cb760d..29ab78f 100644 --- a/languageserver/src/utils/repo-permission.ts +++ b/languageserver/src/utils/repo-permission.ts @@ -3,6 +3,7 @@ import {Octokit} from "@octokit/rest"; import {RepositoryContext} from "../initializationOptions"; import {TTLCache} from "./cache"; import {getUsername} from "./username"; +import {errorStatus} from "./error"; export type RepoPermission = "admin" | "write" | "read" | "none"; @@ -37,8 +38,9 @@ async function fetchRepoPermission(octokit: Octokit, repo: RepositoryContext, us }); const permission = res.data?.permission; return permission; - } catch (e: any) { - if (e.status === 404 || e.status === 403) { + } catch (e) { + const status = errorStatus(e); + if (status === 404 || status === 403) { return "none"; } throw e; diff --git a/languageserver/src/value-providers/runs-on.ts b/languageserver/src/value-providers/runs-on.ts index 47d4962..7f9969e 100644 --- a/languageserver/src/value-providers/runs-on.ts +++ b/languageserver/src/value-providers/runs-on.ts @@ -3,6 +3,7 @@ import {Value} from "@github/actions-languageservice/value-providers/config"; import {DEFAULT_RUNNER_LABELS} from "@github/actions-languageservice/value-providers/default"; import {Octokit} from "@octokit/rest"; import {TTLCache} from "../utils/cache"; +import {errorMessage} from "../utils/error"; // Limitation: getRunnerLabels returns default hosted labels and labels for repository self-hosted runners. // It doesn't return labels for organization runners visible to the repository. @@ -35,7 +36,7 @@ async function fetchRunnerLabels(client: Octokit, owner: string, name: string): } } } catch (e) { - log(`Failure to retrieve runner labels: ${e}`); + log(`Failure to retrieve runner labels: ${errorMessage(e)}`); } return labels; diff --git a/package-lock.json b/package-lock.json index 2a45c5a..89c03a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -157,6 +157,7 @@ "@github/actions-languageservice": "^0.1.169", "@github/actions-workflow-parser": "^0.1.169", "@octokit/rest": "^19.0.7", + "@octokit/types": "^9.0.0", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "yaml": "^2.1.3" @@ -174,6 +175,19 @@ "node": ">= 16.15" } }, + "languageserver/node_modules/@octokit/openapi-types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==" + }, + "languageserver/node_modules/@octokit/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", + "dependencies": { + "@octokit/openapi-types": "^16.0.0" + } + }, "languageservice": { "name": "@github/actions-languageservice", "version": "0.1.169", @@ -14830,6 +14844,7 @@ "@github/actions-languageservice": "^0.1.169", "@github/actions-workflow-parser": "^0.1.169", "@octokit/rest": "^19.0.7", + "@octokit/types": "*", "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", "jest": "^29.0.3", @@ -14840,6 +14855,21 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "yaml": "^2.1.3" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==" + }, + "@octokit/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", + "requires": { + "@octokit/openapi-types": "^16.0.0" + } + } } }, "@github/actions-languageservice": {