Merge branch 'main' into elbrenn/secret-complete
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "browser-playground",
|
"name": "browser-playground",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-languageserver": "^0.1.153",
|
"@github/actions-languageserver": "^0.1.157",
|
||||||
"monaco-editor-webpack-plugin": "^7.0.1",
|
"monaco-editor-webpack-plugin": "^7.0.1",
|
||||||
"monaco-editor-workers": "^0.34.2",
|
"monaco-editor-workers": "^0.34.2",
|
||||||
"monaco-languageclient": "^4.0.3",
|
"monaco-languageclient": "^4.0.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# actions-expressions
|
# actions/expressions
|
||||||
|
|
||||||
`actions-expressions` is a library to parse and evaluate GitHub Actions [expressions](https://docs.github.com/actions/learn-github-actions/expressions).
|
`@actions/expressions` is a library to parse and evaluate GitHub Actions [expressions](https://docs.github.com/actions/learn-github-actions/expressions).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@github/actions-expressions",
|
"name": "@github/actions-expressions",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"source": "./src/index.ts",
|
"source": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# actions-languageserver
|
# actions/languageserver
|
||||||
|
|
||||||
`actions-languageserver` hosts the `actions-languageservice` and makes it available via the [language server protocol](https://microsoft.github.io/language-server-protocol/) (LSP) as a standalone language server.
|
`actions-languageserver` hosts the `actions-languageservice` and makes it available via the [language server protocol](https://microsoft.github.io/language-server-protocol/) (LSP) as a standalone language server.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@github/actions-languageserver",
|
"name": "@github/actions-languageserver",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"description": "Language server for GitHub Actions",
|
"description": "Language server for GitHub Actions",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -38,8 +38,8 @@
|
|||||||
"watch": "tsc --build tsconfig.build.json --watch"
|
"watch": "tsc --build tsconfig.build.json --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-languageservice": "^0.1.153",
|
"@github/actions-languageservice": "^0.1.157",
|
||||||
"@github/actions-workflow-parser": "^0.1.153",
|
"@github/actions-workflow-parser": "^0.1.157",
|
||||||
"@octokit/rest": "^19.0.7",
|
"@octokit/rest": "^19.0.7",
|
||||||
"vscode-languageserver": "^8.0.2",
|
"vscode-languageserver": "^8.0.2",
|
||||||
"vscode-languageserver-textdocument": "^1.0.7",
|
"vscode-languageserver-textdocument": "^1.0.7",
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ import {DescriptionProvider} from "@github/actions-languageservice/hover";
|
|||||||
import {Octokit} from "@octokit/rest";
|
import {Octokit} from "@octokit/rest";
|
||||||
import {getActionInputDescription} from "./description-providers/action-input";
|
import {getActionInputDescription} from "./description-providers/action-input";
|
||||||
import {TTLCache} from "./utils/cache";
|
import {TTLCache} from "./utils/cache";
|
||||||
|
import {getActionDescription} from "./description-providers/action-description";
|
||||||
|
|
||||||
export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider {
|
export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider {
|
||||||
const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => {
|
const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => {
|
||||||
if (!client) {
|
if (!client || !context.step) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = path[path.length - 1];
|
const parent = path[path.length - 1];
|
||||||
if (context.step && parent.definition?.key === "step-with") {
|
if (parent.definition?.key === "step-with") {
|
||||||
return await getActionInputDescription(client, cache, context.step, token);
|
return await getActionInputDescription(client, cache, context.step, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parent.definition?.key === "step-uses") {
|
||||||
|
return await getActionDescription(client, cache, context.step);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {Octokit} from "@octokit/rest";
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import {createWorkflowContext} from "../test-utils/workflow-context";
|
||||||
|
import {TTLCache} from "../utils/cache";
|
||||||
|
import {getActionDescription} from "./action-description";
|
||||||
|
import {actionsCheckoutMetadata} from "../test-utils/action-metadata";
|
||||||
|
|
||||||
|
const workflow = `
|
||||||
|
name: Hello World
|
||||||
|
on: workflow_dispatch
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
`;
|
||||||
|
|
||||||
|
async function getDescription(mock: fetchMock.FetchMockSandbox) {
|
||||||
|
const workflowContext = await createWorkflowContext(workflow, "build", 0);
|
||||||
|
|
||||||
|
return await getActionDescription(
|
||||||
|
new Octokit({
|
||||||
|
request: {
|
||||||
|
fetch: mock
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new TTLCache(),
|
||||||
|
workflowContext.step!
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("action descriptions", () => {
|
||||||
|
it("actions/checkout description", async () => {
|
||||||
|
const mock = fetchMock
|
||||||
|
.sandbox()
|
||||||
|
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata);
|
||||||
|
|
||||||
|
expect(await getDescription(mock)).toEqual(
|
||||||
|
"[**Checkout**](https://www.github.com/actions/checkout/tree/v3/)\n\nCheckout a Git repository at a particular version"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("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(mock)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invalid metadata", async () => {
|
||||||
|
const mock = fetchMock
|
||||||
|
.sandbox()
|
||||||
|
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", {});
|
||||||
|
|
||||||
|
expect(await getDescription(mock)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {actionUrl, parseActionReference} from "@github/actions-languageservice/action";
|
||||||
|
import {isActionStep} from "@github/actions-workflow-parser/model/type-guards";
|
||||||
|
import {Step} from "@github/actions-workflow-parser/model/workflow-template";
|
||||||
|
import {Octokit} from "@octokit/rest";
|
||||||
|
import {fetchActionMetadata} from "../utils/action-metadata";
|
||||||
|
import {TTLCache} from "../utils/cache";
|
||||||
|
|
||||||
|
export async function getActionDescription(client: Octokit, cache: TTLCache, step: Step): Promise<string | undefined> {
|
||||||
|
if (!isActionStep(step)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const action = parseActionReference(step.uses.value);
|
||||||
|
if (!action) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = await fetchActionMetadata(client, cache, action);
|
||||||
|
if (!metadata?.name || !metadata?.description) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `[**${metadata.name}**](${actionUrl(action)})\n\n${metadata.description}`;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import fetchMock from "fetch-mock";
|
|||||||
import {createWorkflowContext} from "../test-utils/workflow-context";
|
import {createWorkflowContext} from "../test-utils/workflow-context";
|
||||||
import {TTLCache} from "../utils/cache";
|
import {TTLCache} from "../utils/cache";
|
||||||
import {getActionInputDescription} from "./action-input";
|
import {getActionInputDescription} from "./action-input";
|
||||||
|
import {actionsCheckoutMetadata} from "../test-utils/action-metadata";
|
||||||
|
|
||||||
const workflow = `
|
const workflow = `
|
||||||
name: Hello World
|
name: Hello World
|
||||||
@@ -16,49 +17,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- 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) {
|
async function getDescription(input: string, mock: fetchMock.FetchMockSandbox) {
|
||||||
const workflowContext = await createWorkflowContext(workflow, "build", 0);
|
const workflowContext = await createWorkflowContext(workflow, "build", 0);
|
||||||
|
|
||||||
@@ -74,11 +32,11 @@ async function getDescription(input: string, mock: fetchMock.FetchMockSandbox) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("action descriptions", () => {
|
describe("action input descriptions", () => {
|
||||||
it("optional input", async () => {
|
it("optional input", async () => {
|
||||||
const mock = fetchMock
|
const mock = fetchMock
|
||||||
.sandbox()
|
.sandbox()
|
||||||
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
|
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata);
|
||||||
|
|
||||||
expect(await getDescription("repository", mock)).toEqual(
|
expect(await getDescription("repository", mock)).toEqual(
|
||||||
"Repository name with owner. For example, actions/checkout"
|
"Repository name with owner. For example, actions/checkout"
|
||||||
@@ -88,7 +46,7 @@ describe("action descriptions", () => {
|
|||||||
it("required input", async () => {
|
it("required input", async () => {
|
||||||
const mock = fetchMock
|
const mock = fetchMock
|
||||||
.sandbox()
|
.sandbox()
|
||||||
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
|
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata);
|
||||||
|
|
||||||
expect(await getDescription("ref", mock)).toEqual("The branch, tag or SHA to checkout.\n\n**Required**");
|
expect(await getDescription("ref", mock)).toEqual("The branch, tag or SHA to checkout.\n\n**Required**");
|
||||||
});
|
});
|
||||||
@@ -96,7 +54,7 @@ describe("action descriptions", () => {
|
|||||||
it("deprecated input", async () => {
|
it("deprecated input", async () => {
|
||||||
const mock = fetchMock
|
const mock = fetchMock
|
||||||
.sandbox()
|
.sandbox()
|
||||||
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
|
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata);
|
||||||
|
|
||||||
expect(await getDescription("repo", mock)).toEqual(
|
expect(await getDescription("repo", mock)).toEqual(
|
||||||
"Repository name with owner. For example, actions/checkout\n\n**Deprecated**"
|
"Repository name with owner. For example, actions/checkout\n\n**Deprecated**"
|
||||||
@@ -106,7 +64,7 @@ describe("action descriptions", () => {
|
|||||||
it("invalid input", async () => {
|
it("invalid input", async () => {
|
||||||
const mock = fetchMock
|
const mock = fetchMock
|
||||||
.sandbox()
|
.sandbox()
|
||||||
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata);
|
.getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata);
|
||||||
|
|
||||||
expect(await getDescription("typo", mock)).toBeUndefined();
|
expect(await getDescription("typo", mock)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// A simplified version of the action.yml file from actions/checkout
|
||||||
|
const checkoutMetadataContent = `
|
||||||
|
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
|
||||||
|
export const actionsCheckoutMetadata = {
|
||||||
|
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(checkoutMetadataContent).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"
|
||||||
|
}
|
||||||
|
};
|
||||||
+38
-10
@@ -1,4 +1,4 @@
|
|||||||
# actions-languageservice
|
# actions/languageservice
|
||||||
|
|
||||||
This package contains the logic for the GitHub Actions workflows language server.
|
This package contains the logic for the GitHub Actions workflows language server.
|
||||||
|
|
||||||
@@ -22,14 +22,6 @@ The language service features use three sources of information:
|
|||||||
* _value providers_ which can dynamically add values to the schema, for example, the list of available labels for a repository when validating `runs-on`.
|
* _value providers_ which can dynamically add values to the schema, for example, the list of available labels for a repository when validating `runs-on`.
|
||||||
* _context providers_ which can dynamically provide available contexts used in [expressions](https://docs.github.com/actions/reference/context-and-expression-syntax-for-github-actions#about-contexts-and-expressions). For example, the contents of the `github.event` context for a given workflow file.
|
* _context providers_ which can dynamically provide available contexts used in [expressions](https://docs.github.com/actions/reference/context-and-expression-syntax-for-github-actions#about-contexts-and-expressions). For example, the contents of the `github.event` context for a given workflow file.
|
||||||
|
|
||||||
##### Value Providers
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
##### Context Providers
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
#### Validation
|
#### Validation
|
||||||
|
|
||||||
Validate a workflow file, returns an array of [`Diagnostic`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic) objects.
|
Validate a workflow file, returns an array of [`Diagnostic`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic) objects.
|
||||||
@@ -60,7 +52,43 @@ const hover = await hover(document, {line: 0, character: 1}); // { contents: { k
|
|||||||
|
|
||||||
#### Auto-completion
|
#### Auto-completion
|
||||||
|
|
||||||
TODO
|
```typescript
|
||||||
|
import {complete} from "@actions/languageservice";
|
||||||
|
|
||||||
|
const document = {
|
||||||
|
uri: "file:///path/to/file",
|
||||||
|
getText: () => `on:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo hello`
|
||||||
|
};
|
||||||
|
|
||||||
|
// Trigger completion for `on: |`
|
||||||
|
const suggestions = await complete(document, {line: 0, character: 4});
|
||||||
|
```
|
||||||
|
|
||||||
|
will return
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
[{
|
||||||
|
"documentation": {
|
||||||
|
"kind": "markdown",
|
||||||
|
"value": "Runs your workflow when branch protection rules in the workflow repository are changed.",
|
||||||
|
},
|
||||||
|
"label": "branch_protection_rule",
|
||||||
|
"textEdit": {
|
||||||
|
"newText": "branch_protection_rule",
|
||||||
|
"range": {
|
||||||
|
"end": {"character": 4, "line": 0,},
|
||||||
|
"start": {"character": 4, "line": 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//... other events
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@github/actions-languageservice",
|
"name": "@github/actions-languageservice",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"description": "Language service for GitHub Actions",
|
"description": "Language service for GitHub Actions",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
"watch": "tsc --build tsconfig.build.json --watch"
|
"watch": "tsc --build tsconfig.build.json --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-expressions": "^0.1.153",
|
"@github/actions-expressions": "^0.1.157",
|
||||||
"@github/actions-workflow-parser": "^0.1.153",
|
"@github/actions-workflow-parser": "^0.1.157",
|
||||||
"vscode-languageserver-textdocument": "^1.0.7",
|
"vscode-languageserver-textdocument": "^1.0.7",
|
||||||
"vscode-languageserver-types": "^3.17.2",
|
"vscode-languageserver-types": "^3.17.2",
|
||||||
"yaml": "^2.1.1"
|
"yaml": "^2.1.1"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
|
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
|
||||||
export type ActionMetadata = {
|
export type ActionMetadata = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
inputs?: ActionInputs;
|
inputs?: ActionInputs;
|
||||||
outputs?: ActionOutputs;
|
outputs?: ActionOutputs;
|
||||||
};
|
};
|
||||||
@@ -63,3 +65,10 @@ export function actionIdentifier(ref: ActionReference): string {
|
|||||||
}
|
}
|
||||||
return `${ref.owner}/${ref.name}/${ref.ref}`;
|
return `${ref.owner}/${ref.name}/${ref.ref}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function actionUrl(actionRef: ActionReference): string {
|
||||||
|
// TODO: Support base uri for GHES
|
||||||
|
const gitHubBaseUri = "https://www.github.com/";
|
||||||
|
|
||||||
|
return `${gitHubBaseUri}${actionRef.owner}/${actionRef.name}/tree/${actionRef.ref}/${actionRef.path || ""}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -320,6 +320,37 @@ on:
|
|||||||
expect(result).toHaveLength(0);
|
expect(result).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("null strings still give suggestions", async () => {
|
||||||
|
const input = `
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
one:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
`;
|
||||||
|
const result = await complete(...getPositionFromCursor(input));
|
||||||
|
expect(result).toHaveLength(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("complete from behind a colon will replace it", async () => {
|
||||||
|
const input = `
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
one:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
`;
|
||||||
|
const result = await complete(...getPositionFromCursor(input));
|
||||||
|
expect(result).toHaveLength(16);
|
||||||
|
let textEdit = result[0].textEdit as TextEdit;
|
||||||
|
expect(textEdit.range).toEqual({
|
||||||
|
start: {line: 5, character: 4},
|
||||||
|
end: {line: 5, character: 5}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("well known mapping keys have descriptions", async () => {
|
it("well known mapping keys have descriptions", async () => {
|
||||||
const input = `
|
const input = `
|
||||||
o|
|
o|
|
||||||
|
|||||||
@@ -108,7 +108,19 @@ export async function complete(
|
|||||||
|
|
||||||
// Get the length of the current word
|
// Get the length of the current word
|
||||||
const val = line.match(/[\w_-]*$/)?.[0].length || 0;
|
const val = line.match(/[\w_-]*$/)?.[0].length || 0;
|
||||||
replaceRange = Range.create({line: position.line, character: position.character - val}, position);
|
// Check if we need to remove a trailing colon
|
||||||
|
const charAfterPos = textDocument.getText({
|
||||||
|
start: {line: position.line, character: position.character},
|
||||||
|
end: {line: position.line, character: position.character + 1}
|
||||||
|
});
|
||||||
|
if (charAfterPos === ":") {
|
||||||
|
replaceRange = Range.create(
|
||||||
|
{line: position.line, character: position.character - val},
|
||||||
|
{line: position.line, character: position.character + 1}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
replaceRange = Range.create({line: position.line, character: position.character - val}, position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return values.map(value => {
|
return values.map(value => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {isJob} from "@github/actions-workflow-parser/model/type-guards";
|
|||||||
import {File} from "@github/actions-workflow-parser/workflows/file";
|
import {File} from "@github/actions-workflow-parser/workflows/file";
|
||||||
import {TextDocument} from "vscode-languageserver-textdocument";
|
import {TextDocument} from "vscode-languageserver-textdocument";
|
||||||
import {DocumentLink} from "vscode-languageserver-types";
|
import {DocumentLink} from "vscode-languageserver-types";
|
||||||
import {parseActionReference} from "./action";
|
import {actionUrl, parseActionReference} from "./action";
|
||||||
import {nullTrace} from "./nulltrace";
|
import {nullTrace} from "./nulltrace";
|
||||||
import {mapRange} from "./utils/range";
|
import {mapRange} from "./utils/range";
|
||||||
|
|
||||||
@@ -26,9 +26,6 @@ export async function documentLinks(document: TextDocument): Promise<DocumentLin
|
|||||||
// Add links to referenced actions
|
// Add links to referenced actions
|
||||||
const actionLinks: DocumentLink[] = [];
|
const actionLinks: DocumentLink[] = [];
|
||||||
|
|
||||||
// TODO: Support base uri for GHES
|
|
||||||
const gitHubBaseUri = "https://www.github.com/";
|
|
||||||
|
|
||||||
for (const job of template?.jobs || []) {
|
for (const job of template?.jobs || []) {
|
||||||
if (!job || !isJob(job)) {
|
if (!job || !isJob(job)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -40,9 +37,7 @@ export async function documentLinks(document: TextDocument): Promise<DocumentLin
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${gitHubBaseUri}${actionRef.owner}/${actionRef.name}/tree/${actionRef.ref}/${
|
const url = actionUrl(actionRef);
|
||||||
actionRef.path || ""
|
|
||||||
}`;
|
|
||||||
|
|
||||||
actionLinks.push({
|
actionLinks.push({
|
||||||
range: mapRange(step.uses.range),
|
range: mapRange(step.uses.range),
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ jobs:
|
|||||||
- uses|: actions/checkout@v2
|
- uses|: actions/checkout@v2
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await hover(...getPositionFromCursor(input), testHoverConfig("uses", "non-empty-string", undefined));
|
const result = await hover(...getPositionFromCursor(input), testHoverConfig("uses", "step-uses", undefined));
|
||||||
expect(result).not.toBeUndefined();
|
expect(result).not.toBeUndefined();
|
||||||
expect(result?.contents).toEqual(
|
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."
|
"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."
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/in
|
|||||||
export function isPotentiallyExpression(token: TemplateToken): boolean {
|
export function isPotentiallyExpression(token: TemplateToken): boolean {
|
||||||
const isAlwaysExpression =
|
const isAlwaysExpression =
|
||||||
token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression;
|
token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression;
|
||||||
const containsExpression = isString(token) && token.value.indexOf(OPEN_EXPRESSION) >= 0;
|
const containsExpression = isString(token) && token.value != null && token.value.indexOf(OPEN_EXPRESSION) >= 0;
|
||||||
return isAlwaysExpression || containsExpression;
|
return isAlwaysExpression || containsExpression;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const validationConfig: ValidationConfig = {
|
|||||||
switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
|
switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
|
||||||
case "actions/checkout@v3":
|
case "actions/checkout@v3":
|
||||||
metadata = {
|
metadata = {
|
||||||
|
name: "Checkout",
|
||||||
|
description: "Checkout a Git repository at a particular version",
|
||||||
inputs: {
|
inputs: {
|
||||||
repository: {
|
repository: {
|
||||||
description: "Repository name with owner",
|
description: "Repository name with owner",
|
||||||
@@ -24,6 +26,9 @@ const validationConfig: ValidationConfig = {
|
|||||||
break;
|
break;
|
||||||
case "actions/setup-node@v1":
|
case "actions/setup-node@v1":
|
||||||
metadata = {
|
metadata = {
|
||||||
|
name: "Setup Node.js environment",
|
||||||
|
description:
|
||||||
|
"Setup a Node.js environment by adding problem matchers and optionally downloading and adding it to the PATH.",
|
||||||
inputs: {
|
inputs: {
|
||||||
version: {
|
version: {
|
||||||
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
|
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
|
||||||
@@ -35,6 +40,8 @@ const validationConfig: ValidationConfig = {
|
|||||||
break;
|
break;
|
||||||
case "actions/deploy-pages@main":
|
case "actions/deploy-pages@main":
|
||||||
metadata = {
|
metadata = {
|
||||||
|
name: "Deploy GitHub Pages site",
|
||||||
|
description: "A GitHub Action to deploy an artifact as a GitHub Pages site",
|
||||||
inputs: {
|
inputs: {
|
||||||
token: {
|
token: {
|
||||||
required: true,
|
required: true,
|
||||||
@@ -46,6 +53,8 @@ const validationConfig: ValidationConfig = {
|
|||||||
break;
|
break;
|
||||||
case "actions/cache@v1":
|
case "actions/cache@v1":
|
||||||
metadata = {
|
metadata = {
|
||||||
|
name: "Cache",
|
||||||
|
description: "Cache artifacts like dependencies and build outputs to improve workflow execution time",
|
||||||
inputs: {
|
inputs: {
|
||||||
path: {
|
path: {
|
||||||
description: "A directory to store and save the cache",
|
description: "A directory to store and save the cache",
|
||||||
@@ -63,7 +72,10 @@ const validationConfig: ValidationConfig = {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "actions/action-no-input@v1":
|
case "actions/action-no-input@v1":
|
||||||
metadata = {};
|
metadata = {
|
||||||
|
name: "Action with no inputs",
|
||||||
|
description: "An action with no inputs"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"version": "0.1.153"
|
"version": "0.1.157"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+17
-17
@@ -111,10 +111,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browser-playground": {
|
"browser-playground": {
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-languageserver": "^0.1.153",
|
"@github/actions-languageserver": "^0.1.157",
|
||||||
"monaco-editor-webpack-plugin": "^7.0.1",
|
"monaco-editor-webpack-plugin": "^7.0.1",
|
||||||
"monaco-editor-workers": "^0.34.2",
|
"monaco-editor-workers": "^0.34.2",
|
||||||
"monaco-languageclient": "^4.0.3",
|
"monaco-languageclient": "^4.0.3",
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
},
|
},
|
||||||
"expressions": {
|
"expressions": {
|
||||||
"name": "@github/actions-expressions",
|
"name": "@github/actions-expressions",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.0.3",
|
"@types/jest": "^29.0.3",
|
||||||
@@ -151,11 +151,11 @@
|
|||||||
},
|
},
|
||||||
"languageserver": {
|
"languageserver": {
|
||||||
"name": "@github/actions-languageserver",
|
"name": "@github/actions-languageserver",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-languageservice": "^0.1.153",
|
"@github/actions-languageservice": "^0.1.157",
|
||||||
"@github/actions-workflow-parser": "^0.1.153",
|
"@github/actions-workflow-parser": "^0.1.157",
|
||||||
"@octokit/rest": "^19.0.7",
|
"@octokit/rest": "^19.0.7",
|
||||||
"vscode-languageserver": "^8.0.2",
|
"vscode-languageserver": "^8.0.2",
|
||||||
"vscode-languageserver-textdocument": "^1.0.7",
|
"vscode-languageserver-textdocument": "^1.0.7",
|
||||||
@@ -176,11 +176,11 @@
|
|||||||
},
|
},
|
||||||
"languageservice": {
|
"languageservice": {
|
||||||
"name": "@github/actions-languageservice",
|
"name": "@github/actions-languageservice",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-expressions": "^0.1.153",
|
"@github/actions-expressions": "^0.1.157",
|
||||||
"@github/actions-workflow-parser": "^0.1.153",
|
"@github/actions-workflow-parser": "^0.1.157",
|
||||||
"vscode-languageserver-textdocument": "^1.0.7",
|
"vscode-languageserver-textdocument": "^1.0.7",
|
||||||
"vscode-languageserver-types": "^3.17.2",
|
"vscode-languageserver-types": "^3.17.2",
|
||||||
"yaml": "^2.1.1"
|
"yaml": "^2.1.1"
|
||||||
@@ -14265,10 +14265,10 @@
|
|||||||
},
|
},
|
||||||
"workflow-parser": {
|
"workflow-parser": {
|
||||||
"name": "@github/actions-workflow-parser",
|
"name": "@github/actions-workflow-parser",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-expressions": "^0.1.153",
|
"@github/actions-expressions": "^0.1.157",
|
||||||
"cronstrue": "^2.21.0",
|
"cronstrue": "^2.21.0",
|
||||||
"yaml": "^2.0.0-8"
|
"yaml": "^2.0.0-8"
|
||||||
},
|
},
|
||||||
@@ -14827,8 +14827,8 @@
|
|||||||
"@github/actions-languageserver": {
|
"@github/actions-languageserver": {
|
||||||
"version": "file:languageserver",
|
"version": "file:languageserver",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@github/actions-languageservice": "^0.1.153",
|
"@github/actions-languageservice": "^0.1.157",
|
||||||
"@github/actions-workflow-parser": "^0.1.153",
|
"@github/actions-workflow-parser": "^0.1.157",
|
||||||
"@octokit/rest": "^19.0.7",
|
"@octokit/rest": "^19.0.7",
|
||||||
"@types/jest": "^29.0.3",
|
"@types/jest": "^29.0.3",
|
||||||
"fetch-mock": "^9.11.0",
|
"fetch-mock": "^9.11.0",
|
||||||
@@ -14845,8 +14845,8 @@
|
|||||||
"@github/actions-languageservice": {
|
"@github/actions-languageservice": {
|
||||||
"version": "file:languageservice",
|
"version": "file:languageservice",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@github/actions-expressions": "^0.1.153",
|
"@github/actions-expressions": "^0.1.157",
|
||||||
"@github/actions-workflow-parser": "^0.1.153",
|
"@github/actions-workflow-parser": "^0.1.157",
|
||||||
"@types/jest": "^29.0.3",
|
"@types/jest": "^29.0.3",
|
||||||
"jest": "^29.0.3",
|
"jest": "^29.0.3",
|
||||||
"prettier": "^2.8.3",
|
"prettier": "^2.8.3",
|
||||||
@@ -14863,7 +14863,7 @@
|
|||||||
"@github/actions-workflow-parser": {
|
"@github/actions-workflow-parser": {
|
||||||
"version": "file:workflow-parser",
|
"version": "file:workflow-parser",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@github/actions-expressions": "^0.1.153",
|
"@github/actions-expressions": "^0.1.157",
|
||||||
"@types/jest": "^29.0.3",
|
"@types/jest": "^29.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||||
"@typescript-eslint/parser": "^5.40.0",
|
"@typescript-eslint/parser": "^5.40.0",
|
||||||
@@ -17857,7 +17857,7 @@
|
|||||||
"browser-playground": {
|
"browser-playground": {
|
||||||
"version": "file:browser-playground",
|
"version": "file:browser-playground",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@github/actions-languageserver": "^0.1.153",
|
"@github/actions-languageserver": "^0.1.157",
|
||||||
"css-loader": "^6.7.2",
|
"css-loader": "^6.7.2",
|
||||||
"monaco-editor-webpack-plugin": "^7.0.1",
|
"monaco-editor-webpack-plugin": "^7.0.1",
|
||||||
"monaco-editor-workers": "^0.34.2",
|
"monaco-editor-workers": "^0.34.2",
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# actions/workflow-parser
|
||||||
|
|
||||||
|
`@actions/workflow-parser` is a library to parse GitHub Actions [workflows](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The [package](https://www.npmjs.com/package/@actions/workflow-parser) contains TypeScript types and compiled ECMAScript modules.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @actions/workflow-parser
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The parser is driven by a custom schema defined in [`worflows-v1.0.json`](./src/workflow-v1.0.json).
|
||||||
|
|
||||||
|
### Simple example
|
||||||
|
|
||||||
|
`parseWorkflow` parses the workflow YAML into an intermediate representation and validates that it conforms to the schema. Any parsing errors are returned in the `errors` property of the result context.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
var trace: TraceWriter;
|
||||||
|
|
||||||
|
const result = parseWorkflow(
|
||||||
|
{
|
||||||
|
name: "test.yaml",
|
||||||
|
content: `on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo 'hello'`
|
||||||
|
},
|
||||||
|
trace
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`convertWorkflowTemplate` then takes that intermediate representation and converts it to a [`WorkflowTemplate`](./src/workflow-template.ts) object, which is a more convenient representation for working with workflows.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const workflowTemplate = await convertWorkflowTemplate(result.context, result.value);
|
||||||
|
|
||||||
|
// workflowTemplate.jobs[0].id === "build"
|
||||||
|
// workflowTemplate.jobs[0].steps[0].run === "echo 'hello'"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](../CONTRIBUTING.md) at the root of the repository for general guidelines and recommendations.
|
||||||
|
|
||||||
|
Similar to [`actions/expressions](../expressions/), this project is just one of multiple implementations of the GitHub Actions workflow parser. We therefore cannot accept contributions that significantly modify the schema or the overall behavior of the parser. If you would like to propose changes to Actions itself, please use our [Community Forum](https://github.com/community/community/discussions/categories/actions-and-packages).
|
||||||
|
|
||||||
|
If you do want to contribute, please run [prettier](https://prettier.io/) to format your code and add unit tests as appropriate before submitting your PR. [./testdata](./testdata) contains test cases that all implementations should pass, please also make sure those tests are still passing.
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
or to watch for changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run watch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
or to watch for changes and run tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test-watch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run format-check
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the terms of the MIT open source license. Please refer to [MIT](../LICENSE) for the full terms.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@github/actions-workflow-parser",
|
"name": "@github/actions-workflow-parser",
|
||||||
"version": "0.1.153",
|
"version": "0.1.157",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"source": "./src/index.ts",
|
"source": "./src/index.ts",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"watch": "tsc --build tsconfig.build.json --watch"
|
"watch": "tsc --build tsconfig.build.json --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/actions-expressions": "^0.1.153",
|
"@github/actions-expressions": "^0.1.157",
|
||||||
"cronstrue": "^2.21.0",
|
"cronstrue": "^2.21.0",
|
||||||
"yaml": "^2.0.0-8"
|
"yaml": "^2.0.0-8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -203,14 +203,15 @@ class TemplateReader {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Duplicate
|
// Duplicate
|
||||||
const upperKey = nextKey.value.toUpperCase();
|
if (nextKey.value) {
|
||||||
if (upperKeys[upperKey]) {
|
const upperKey = nextKey.value.toUpperCase();
|
||||||
this._context.error(nextKey, `'${nextKey.value}' is already defined`);
|
if (upperKeys[upperKey]) {
|
||||||
this.skipValue();
|
this._context.error(nextKey, `'${nextKey.value}' is already defined`);
|
||||||
continue;
|
this.skipValue();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
upperKeys[upperKey] = true;
|
||||||
}
|
}
|
||||||
upperKeys[upperKey] = true;
|
|
||||||
|
|
||||||
// Well known
|
// Well known
|
||||||
const nextPropertyDef = this._schema.matchPropertyAndFilter(mappingDefinitions, nextKey.value);
|
const nextPropertyDef = this._schema.matchPropertyAndFilter(mappingDefinitions, nextKey.value);
|
||||||
if (nextPropertyDef) {
|
if (nextPropertyDef) {
|
||||||
@@ -339,13 +340,15 @@ class TemplateReader {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Duplicate
|
// Duplicate
|
||||||
const upperKey = nextKey.value.toUpperCase();
|
if (nextKey.value) {
|
||||||
if (upperKeys[upperKey]) {
|
const upperKey = nextKey.value.toUpperCase();
|
||||||
this._context.error(nextKey, `'${nextKey.value}' is already defined`);
|
if (upperKeys[upperKey]) {
|
||||||
this.skipValue();
|
this._context.error(nextKey, `'${nextKey.value}' is already defined`);
|
||||||
continue;
|
this.skipValue();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
upperKeys[upperKey] = true;
|
||||||
}
|
}
|
||||||
upperKeys[upperKey] = true;
|
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
this.validate(nextKey, keyDefinition);
|
this.validate(nextKey, keyDefinition);
|
||||||
@@ -443,7 +446,7 @@ class TemplateReader {
|
|||||||
|
|
||||||
private parseScalar(token: LiteralToken, definitionInfo: DefinitionInfo): ScalarToken {
|
private parseScalar(token: LiteralToken, definitionInfo: DefinitionInfo): ScalarToken {
|
||||||
// Not a string
|
// Not a string
|
||||||
if (!isString(token)) {
|
if (!isString(token) || !token.value) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2044,8 +2044,7 @@
|
|||||||
"continue-on-error": "step-continue-on-error",
|
"continue-on-error": "step-continue-on-error",
|
||||||
"timeout-minutes": "step-timeout-minutes",
|
"timeout-minutes": "step-timeout-minutes",
|
||||||
"uses": {
|
"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.",
|
"type": "step-uses",
|
||||||
"type": "non-empty-string",
|
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"with": "step-with",
|
"with": "step-with",
|
||||||
@@ -2053,6 +2052,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"step-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.",
|
||||||
|
"string": {
|
||||||
|
"require-non-empty": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"step-continue-on-error": {
|
"step-continue-on-error": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
Reference in New Issue
Block a user