├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── README.md
├── images
└── logo.png
├── jest.config.js
├── package-lock.json
├── package.json
├── snippets
├── Step.yaml
└── Workflow.yaml
├── src
├── extension.ts
└── yaml-support
│ ├── yaml-constant.ts
│ ├── yaml-schema.ts
│ └── yaml-snippet.ts
├── tsconfig.json
├── tslint.json
└── types
└── jest.d.ts
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 |
3 | on: push
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v1
10 | - name: Use Node.js ${{ matrix.node-version }}
11 | uses: actions/setup-node@v1
12 | with:
13 | node-version: ${{ matrix.node-version }}
14 | - name: npm install
15 | run: npm ci
16 | env:
17 | CI: "true"
18 | - name: npm test
19 | run: npm run test:unit
20 | env:
21 | CI: "true"
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | .vscode-test/
4 | *.vsix
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"],
14 | "outFiles": ["${workspaceFolder}/out/**/*.js"],
15 | "preLaunchTask": "npm: watch"
16 | },
17 | {
18 | "name": "Extension Tests",
19 | "type": "extensionHost",
20 | "request": "launch",
21 | "runtimeExecutable": "${execPath}",
22 | "args": [
23 | "--extensionDevelopmentPath=${workspaceFolder}",
24 | "--extensionTestsPath=${workspaceFolder}/out/test"
25 | ],
26 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"],
27 | "preLaunchTask": "npm: watch"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | "editor.renderWhitespace": "all"
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | out/**/*.map
5 | src/**
6 | tsconfig.json
7 | **/__tests__/**/*.+(ts|tsx|js)
8 | **/?(*.)+(spec|test).+(ts|tsx|js)
9 | types/**
10 | jest.config.js
11 | tslint.json
12 | .github/**
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## **3.0.0**
4 |
5 | - `redhat.vscode-yaml` already defines schemas for the workflows and actions so we just defer to them instead of conflicting.
6 |
7 | ## **2.2.2**
8 |
9 | - The value of an event can also be `null`
10 |
11 | ## **2.2.1**
12 |
13 | - Links don't actually work properly
14 |
15 | ## **2.2.0**
16 |
17 | - Add `post` field in action schema
18 | - Add `color` and `icon` enums in action schema
19 | - Add links to documentation.
20 |
21 | ## **2.1.0**
22 |
23 | - Add support for `action.yml` validation
24 |
25 | ## **2.0.0**
26 |
27 | - Initial release with support for validation, hover and auto completion
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GitHub Actions YAML Extension
2 |
3 | Provides Github Actions YAML support via [yaml-language-server](https://github.com/redhat-developer/yaml-language-server).
4 |
5 | ## Features
6 |
7 | ### 1. YAML validation
8 |
9 | - Apply schema validation to GitHub Actions
10 | - Detects errors such as:
11 | - Invalid property value type
12 | - Out of enum scope
13 | - Required property is missing
14 | - Unexpected property
15 |
16 | ### 2. Auto completion
17 |
18 | - Generate input template for whole GitHub Action
19 | - Generate input template for an object (_if provided by schema_)
20 |
21 | > **Including required properties and optional properties with default value**
22 |
23 | - Support properties intellisense (_if provided by schema_)
24 | - Enumerated property value recommendation (_if provided by schema_)
25 |
26 | > **Intellisense is automatically triggered by what you have typed, but you can also hit _Ctrl + Space_ to get what you can type**.
27 |
28 | ### 3. Hover support
29 |
30 | - Hovering over a property shows description (_if provided by schema_)
31 |
32 | ## Developer support
33 |
34 | 1. Install prerequisites:
35 | - latest [Visual Studio Code](https://code.visualstudio.com/)
36 | - [Node.js](https://nodejs.org/) v6.0.0 or higher
37 | 2. Fork this repository.
38 | 3. Build this project.
39 |
40 | ```bash
41 | # clone your forked repository
42 | $ git clone https://github.com/{your-github-name}/vscode-github-actions
43 | $ cd vscode-github-actions
44 | # install npm dependencies
45 | $ npm install
46 | # compile
47 | $ npm run compile
48 | # open the project in vscode
49 | $ code .
50 | ```
51 |
52 | 4. Make changes as necessary and the run the code using F5.
53 | Refer to VS Code [documentation](https://code.visualstudio.com/docs/extensions/debugging-extensions) on how to run and debug the extension.
54 | 5. Create a pull-request to this repository and we will review, merge it and publish new version extension regularly.
55 |
56 | ## License
57 |
58 | MIT
59 |
60 | **All contributions are welcome!**
61 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lona/vscode-github-actions/198c51b11ca17875edd4fc644f0d61a55487956d/images/logo.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testMatch: [
3 | "**/__tests__/**/*.+(ts|tsx|js)",
4 | "**/?(*.)+(spec|test).+(ts|tsx|js)"
5 | ],
6 | transform: {
7 | "^.+\\.(ts|tsx)$": "ts-jest"
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-github-actions",
3 | "displayName": "GitHub Actions",
4 | "description": "YAML schema validation and auto-completion for GitHub Actions.",
5 | "version": "3.0.1",
6 | "publisher": "me-dutour-mathieu",
7 | "engines": {
8 | "vscode": "^1.25.0"
9 | },
10 | "license": "MIT",
11 | "categories": [
12 | "Snippets",
13 | "Programming Languages",
14 | "Other"
15 | ],
16 | "keywords": [
17 | "yaml",
18 | "github",
19 | "actions",
20 | "workflow"
21 | ],
22 | "icon": "images/logo.png",
23 | "bugs": {
24 | "url": "https://github.com/Lona/vscode-github-actions/issues"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/Lona/vscode-github-actions.git"
29 | },
30 | "activationEvents": [
31 | "onLanguage:yaml"
32 | ],
33 | "main": "./out/src/extension",
34 | "contributes": {},
35 | "extensionDependencies": [
36 | "redhat.vscode-yaml"
37 | ],
38 | "scripts": {
39 | "vscode:prepublish": "npm run compile",
40 | "compile": "npm run compile:ts",
41 | "compile:ts": "tsc -p ./",
42 | "compile:schema": "find ./src/schemas -type f -maxdepth 1 -exec \"js-yaml '{}'\" \\;",
43 | "watch": "tsc -watch -p ./",
44 | "postinstall": "node ./node_modules/vscode/bin/install",
45 | "test:e2e": "npm run compile && node ./node_modules/vscode/bin/test",
46 | "test:unit": "jest --no-watchman"
47 | },
48 | "dependencies": {
49 | "fuse.js": "^3.4.5",
50 | "js-yaml": "^3.13.1"
51 | },
52 | "devDependencies": {
53 | "@types/jest": "^24.0.23",
54 | "@types/js-yaml": "^3.12.1",
55 | "@types/node": "^12.0.0",
56 | "ajv": "^6.10.2",
57 | "jest": "^24.9.0",
58 | "jest-json-schema": "^2.1.0",
59 | "ts-jest": "^24.2.0",
60 | "tslint": "^5.20.0",
61 | "typescript": "^3.6.4",
62 | "vscode": "^1.1.36"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/snippets/Step.yaml:
--------------------------------------------------------------------------------
1 | name: Step
2 | label: GitHub Actions Step
3 | description: Create a GitHub Actions Step
4 | body: |2
5 | - name: $1
6 | run: $2
7 |
--------------------------------------------------------------------------------
/snippets/Workflow.yaml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions Workflow
2 | label: GitHub Actions Workflow
3 | description: Create an GitHub Actions Workflow
4 | body: |2
5 | on: $1
6 |
7 | jobs:
8 | $2:
9 | runs-on: $3
10 |
11 | steps:
12 | - name: $4
13 | run: $5
14 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import { ExtensionContext, languages, window } from "vscode";
2 | import { registerYamlSchemaSupport } from "./yaml-support/yaml-schema";
3 | import { YamlCompletionProvider } from "./yaml-support/yaml-snippet";
4 |
5 | export const output = window.createOutputChannel("vscode-github-actions");
6 | export let extensionPath: string;
7 |
8 | // this method is called when your extension is activated
9 | // your extension is activated the very first time the command is executed
10 | export async function activate(context: ExtensionContext) {
11 | extensionPath = context.extensionPath;
12 | const subscriptions = [
13 | // Completion providers
14 | languages.registerCompletionItemProvider(
15 | "yaml",
16 | new YamlCompletionProvider()
17 | ),
18 | ];
19 | await registerYamlSchemaSupport();
20 | subscriptions.forEach((element) => {
21 | context.subscriptions.push(element);
22 | }, this);
23 | }
24 |
25 | export function deactivate() {
26 | // this method is called when your extension is deactivated
27 | }
28 |
--------------------------------------------------------------------------------
/src/yaml-support/yaml-constant.ts:
--------------------------------------------------------------------------------
1 | import { join } from "path";
2 |
3 | export const VSCODE_YAML_EXTENSION_ID = "redhat.vscode-yaml";
4 |
5 | export const SNIPPETS_ROOT_PATH = join(__dirname, "../../../snippets");
6 |
--------------------------------------------------------------------------------
/src/yaml-support/yaml-schema.ts:
--------------------------------------------------------------------------------
1 | import { Extension, extensions, window } from "vscode";
2 | import { VSCODE_YAML_EXTENSION_ID } from "./yaml-constant";
3 |
4 | // The function signature exposed by vscode-yaml:
5 | // 1. the requestSchema api will be called by vscode-yaml extension to decide whether the schema can be handled by this
6 | // contributor, if it returns undefined, means it doesn't support this yaml file, vscode-yaml will ask other contributors
7 | // 2. the requestSchemaContent api will give the parameter uri returned by the first api, and ask for the json content(after stringify) of
8 | // the schema
9 | declare type YamlSchemaContributor = (
10 | schema: string,
11 | requestSchema: (resource: string) => string,
12 | requestSchemaContent: (uri: string) => string
13 | ) => void;
14 |
15 | export async function registerYamlSchemaSupport(): Promise {
16 | await activateYamlExtension();
17 | }
18 |
19 | // Find redhat.vscode-yaml extension and try to activate it to get the yaml contributor
20 | async function activateYamlExtension(): Promise<{
21 | registerContributor: YamlSchemaContributor;
22 | }> {
23 | const ext: Extension = extensions.getExtension(VSCODE_YAML_EXTENSION_ID);
24 | if (!ext) {
25 | window.showWarningMessage(
26 | "Please install 'YAML Support by Red Hat' via the Extensions pane."
27 | );
28 | return;
29 | }
30 | const yamlPlugin = await ext.activate();
31 |
32 | if (!yamlPlugin || !yamlPlugin.registerContributor) {
33 | window.showWarningMessage(
34 | "The installed Red Hat YAML extension doesn't support Kubernetes Intellisense. Please upgrade 'YAML Support by Red Hat' via the Extensions pane."
35 | );
36 | return;
37 | }
38 | return yamlPlugin;
39 | }
40 |
--------------------------------------------------------------------------------
/src/yaml-support/yaml-snippet.ts:
--------------------------------------------------------------------------------
1 | import { readdirSync, readFileSync } from "fs";
2 | import * as Fuse from "fuse.js";
3 | import { safeLoad } from "js-yaml";
4 | import { join } from "path";
5 | import {
6 | CompletionItem,
7 | CompletionItemKind,
8 | CompletionItemProvider,
9 | Position,
10 | SnippetString,
11 | TextDocument
12 | } from "vscode";
13 | import { SNIPPETS_ROOT_PATH } from "./yaml-constant";
14 |
15 | /// Internal representation of a yaml code snippet corresponding to CompletionItemProvider
16 | interface ICodeSnippet {
17 | readonly name: string;
18 | readonly label: string;
19 | readonly description: string;
20 | readonly body: string;
21 | }
22 |
23 | /**
24 | * A yaml completion provider provides yaml code snippets for GitHub Actions.
25 | */
26 | export class YamlCompletionProvider implements CompletionItemProvider {
27 | // Storing all loaded yaml code snippets from snippets folder
28 | private snippets: ICodeSnippet[] = [];
29 |
30 | // Default constructor
31 | public constructor() {
32 | this.loadCodeSnippets();
33 | }
34 |
35 | // Provide code snippets for vscode
36 | public provideCompletionItems(doc: TextDocument, pos: Position) {
37 | const wordPos = doc.getWordRangeAtPosition(pos);
38 | const word = doc.getText(wordPos);
39 |
40 | return this.filterCodeSnippets(word).map(snippet => {
41 | const item = new CompletionItem(
42 | snippet.label,
43 | CompletionItemKind.Snippet
44 | );
45 | item.insertText = new SnippetString(snippet.body);
46 | item.documentation = snippet.description;
47 | return item;
48 | });
49 | }
50 |
51 | // Load yaml code snippets from snippets folder
52 | private loadCodeSnippets(): void {
53 | this.snippets = readdirSync(SNIPPETS_ROOT_PATH)
54 | .filter(filename => filename.endsWith(".yaml"))
55 | .map(filename =>
56 | this.readYamlCodeSnippet(join(SNIPPETS_ROOT_PATH, filename))
57 | );
58 | }
59 |
60 | // Filter all internal code snippets using the parameter word
61 | private filterCodeSnippets(word: string): ICodeSnippet[] {
62 | const searcher = new Fuse(this.snippets, { keys: ["name"] });
63 | return searcher.search(word.toLowerCase());
64 | }
65 |
66 | // Parse a yaml snippet file into a CodeSnippet
67 | private readYamlCodeSnippet(filename: string): ICodeSnippet {
68 | return safeLoad(readFileSync(filename, "utf-8")) as ICodeSnippet;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "moduleResolution": "node",
7 | "lib": ["es6"],
8 | "sourceMap": true,
9 | "rootDir": "."
10 | },
11 | "exclude": ["node_modules", ".vscode-test"]
12 | }
13 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:recommended"],
4 | "jsRules": {},
5 | "rules": {
6 | "max-line-length": false,
7 | "trailing-comma": false,
8 | "arrow-parens": false
9 | },
10 | "rulesDirectory": []
11 | }
12 |
--------------------------------------------------------------------------------
/types/jest.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace jest {
2 | interface Matchers {
3 | toBeValidSchema(): R;
4 | toMatchSchema(schema: any): R;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------