Use fetchActionMetadata to check action validity (#147)
This commit is contained in:
@@ -24,9 +24,9 @@ import {getFileProvider} from "./file-provider";
|
|||||||
import {InitializationOptions, RepositoryContext} from "./initializationOptions";
|
import {InitializationOptions, RepositoryContext} from "./initializationOptions";
|
||||||
import {onCompletion} from "./on-completion";
|
import {onCompletion} from "./on-completion";
|
||||||
import {ReadFileRequest, Requests} from "./request";
|
import {ReadFileRequest, Requests} from "./request";
|
||||||
|
import {fetchActionMetadata} from "./utils/action-metadata";
|
||||||
import {TTLCache} from "./utils/cache";
|
import {TTLCache} from "./utils/cache";
|
||||||
import {valueProviders} from "./value-providers";
|
import {valueProviders} from "./value-providers";
|
||||||
import {getActionInputs} from "./value-providers/action-inputs";
|
|
||||||
|
|
||||||
export function initConnection(connection: Connection) {
|
export function initConnection(connection: Connection) {
|
||||||
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
|
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
|
||||||
@@ -102,9 +102,9 @@ export function initConnection(connection: Connection) {
|
|||||||
const config: ValidationConfig = {
|
const config: ValidationConfig = {
|
||||||
valueProviderConfig: valueProviders(client, repoContext, cache),
|
valueProviderConfig: valueProviders(client, repoContext, cache),
|
||||||
contextProviderConfig: contextProviders(client, repoContext, cache),
|
contextProviderConfig: contextProviders(client, repoContext, cache),
|
||||||
getActionInputs: async action => {
|
fetchActionMetadata: async action => {
|
||||||
if (client) {
|
if (client) {
|
||||||
return await getActionInputs(client, cache, action);
|
return await fetchActionMetadata(client, cache, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import {ActionReference, ActionInputs, ActionOutputs, actionIdentifier} from "@github/actions-languageservice/action";
|
import {ActionReference, ActionMetadata, actionIdentifier} from "@github/actions-languageservice/action";
|
||||||
import {error} from "@github/actions-languageservice/log";
|
import {error} from "@github/actions-languageservice/log";
|
||||||
import {Octokit, RestEndpointMethodTypes} from "@octokit/rest";
|
import {Octokit, RestEndpointMethodTypes} from "@octokit/rest";
|
||||||
import {parse} from "yaml";
|
import {parse} from "yaml";
|
||||||
import {TTLCache} from "./cache";
|
import {TTLCache} from "./cache";
|
||||||
|
|
||||||
export type ActionMetadata = {
|
|
||||||
inputs?: ActionInputs;
|
|
||||||
outputs?: ActionOutputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function fetchActionMetadata(
|
export async function fetchActionMetadata(
|
||||||
client: Octokit,
|
client: Octokit,
|
||||||
cache: TTLCache,
|
cache: TTLCache,
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
|
||||||
|
export type ActionMetadata = {
|
||||||
|
inputs?: ActionInputs;
|
||||||
|
outputs?: ActionOutputs;
|
||||||
|
};
|
||||||
|
|
||||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
|
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
|
||||||
export type ActionInput = {
|
export type ActionInput = {
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export async function validateAction(
|
|||||||
step: Step | undefined,
|
step: Step | undefined,
|
||||||
config: ValidationConfig | undefined
|
config: ValidationConfig | undefined
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!isMapping(stepToken) || !step || !isActionStep(step) || !config?.getActionInputs) {
|
if (!isMapping(stepToken) || !step || !isActionStep(step) || !config?.fetchActionMetadata) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ export async function validateAction(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionInputs = await config.getActionInputs(action);
|
const actionMetadata = await config.fetchActionMetadata(action);
|
||||||
if (actionInputs === undefined) {
|
if (actionMetadata === undefined) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
severity: DiagnosticSeverity.Error,
|
severity: DiagnosticSeverity.Error,
|
||||||
range: mapRange(step.uses.range),
|
range: mapRange(step.uses.range),
|
||||||
@@ -50,6 +50,11 @@ export async function validateAction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const actionInputs = actionMetadata.inputs;
|
||||||
|
if (actionInputs === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const [input, inputToken] of stepInputs) {
|
for (const [input, inputToken] of stepInputs) {
|
||||||
if (!actionInputs[input]) {
|
if (!actionInputs[input]) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {DiagnosticSeverity} from "vscode-languageserver-types";
|
import {DiagnosticSeverity} from "vscode-languageserver-types";
|
||||||
import {ActionInput, ActionReference} from "./action";
|
import {ActionMetadata, ActionReference} from "./action";
|
||||||
import {registerLogger} from "./log";
|
import {registerLogger} from "./log";
|
||||||
import {createDocument} from "./test-utils/document";
|
import {createDocument} from "./test-utils/document";
|
||||||
import {TestLogger} from "./test-utils/logger";
|
import {TestLogger} from "./test-utils/logger";
|
||||||
@@ -9,53 +9,63 @@ import {ValueProviderKind} from "./value-providers/config";
|
|||||||
registerLogger(new TestLogger());
|
registerLogger(new TestLogger());
|
||||||
|
|
||||||
const validationConfig: ValidationConfig = {
|
const validationConfig: ValidationConfig = {
|
||||||
getActionInputs: async (ref: ActionReference) => {
|
fetchActionMetadata: async (ref: ActionReference) => {
|
||||||
let inputs: Record<string, ActionInput> | undefined = undefined;
|
let metadata: ActionMetadata | undefined = undefined;
|
||||||
switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
|
switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
|
||||||
case "actions/checkout@v3":
|
case "actions/checkout@v3":
|
||||||
inputs = {
|
metadata = {
|
||||||
repository: {
|
inputs: {
|
||||||
description: "Repository name with owner",
|
repository: {
|
||||||
default: "${{ github.repository }}"
|
description: "Repository name with owner",
|
||||||
|
default: "${{ github.repository }}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "actions/setup-node@v1":
|
case "actions/setup-node@v1":
|
||||||
inputs = {
|
metadata = {
|
||||||
version: {
|
inputs: {
|
||||||
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
|
version: {
|
||||||
deprecationMessage:
|
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
|
||||||
"The version property will not be supported after October 1, 2019. Use node-version instead"
|
deprecationMessage:
|
||||||
|
"The version property will not be supported after October 1, 2019. Use node-version instead"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "actions/deploy-pages@main":
|
case "actions/deploy-pages@main":
|
||||||
inputs = {
|
metadata = {
|
||||||
token: {
|
inputs: {
|
||||||
required: true,
|
token: {
|
||||||
description: "token to use",
|
required: true,
|
||||||
default: "${{ github.token }}"
|
description: "token to use",
|
||||||
|
default: "${{ github.token }}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "actions/cache@v1":
|
case "actions/cache@v1":
|
||||||
inputs = {
|
metadata = {
|
||||||
path: {
|
inputs: {
|
||||||
description: "A directory to store and save the cache",
|
path: {
|
||||||
required: true
|
description: "A directory to store and save the cache",
|
||||||
},
|
required: true
|
||||||
key: {
|
},
|
||||||
description: "An explicit key for restoring and saving the cache",
|
key: {
|
||||||
required: true
|
description: "An explicit key for restoring and saving the cache",
|
||||||
},
|
required: true
|
||||||
"restore-keys": {
|
},
|
||||||
description: "An ordered list of keys to use for restoring the cache if no cache hit occurred for key",
|
"restore-keys": {
|
||||||
required: false
|
description: "An ordered list of keys to use for restoring the cache if no cache hit occurred for key",
|
||||||
|
required: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case "actions/action-no-input@v1":
|
||||||
|
metadata = {};
|
||||||
}
|
}
|
||||||
return inputs;
|
return metadata;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,6 +113,20 @@ jobs:
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("action does not define inputs", async () => {
|
||||||
|
const input = `
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/action-no-input@v1
|
||||||
|
`;
|
||||||
|
const result = await validate(createDocument("wf.yaml", input), validationConfig);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("invalid input", async () => {
|
it("invalid input", async () => {
|
||||||
const input = `
|
const input = `
|
||||||
on: push
|
on: push
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {File} from "@github/actions-workflow-parser/workflows/file";
|
|||||||
import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provider";
|
import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provider";
|
||||||
import {TextDocument} from "vscode-languageserver-textdocument";
|
import {TextDocument} from "vscode-languageserver-textdocument";
|
||||||
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
|
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
|
||||||
import {ActionInputs, ActionReference} from "./action";
|
import {ActionMetadata, ActionReference} from "./action";
|
||||||
|
|
||||||
import {ContextProviderConfig} from "./context-providers/config";
|
import {ContextProviderConfig} from "./context-providers/config";
|
||||||
import {getContext, Mode} from "./context-providers/default";
|
import {getContext, Mode} from "./context-providers/default";
|
||||||
@@ -35,7 +35,7 @@ import {defaultValueProviders} from "./value-providers/default";
|
|||||||
export type ValidationConfig = {
|
export type ValidationConfig = {
|
||||||
valueProviderConfig?: ValueProviderConfig;
|
valueProviderConfig?: ValueProviderConfig;
|
||||||
contextProviderConfig?: ContextProviderConfig;
|
contextProviderConfig?: ContextProviderConfig;
|
||||||
getActionInputs?(action: ActionReference): Promise<ActionInputs | undefined>;
|
fetchActionMetadata?(action: ActionReference): Promise<ActionMetadata | undefined>;
|
||||||
fileProvider?: FileProvider;
|
fileProvider?: FileProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user