├── .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 | --------------------------------------------------------------------------------