├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── grammars └── graphql.json ├── images └── github-graphql.gif ├── language └── language-configuration.json ├── package-lock.json ├── package.json ├── src ├── extension.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ └── index.ts ├── tsconfig.json ├── vsc-extension-quickstart.md └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "amodio.tsl-problem-matcher" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.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": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "npm: test-watch" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.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 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 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": [ 10 | "$ts-webpack-watch", 11 | "$tslint-webpack-watch" 12 | ], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never" 16 | }, 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | } 21 | }, 22 | { 23 | "type": "npm", 24 | "script": "test-watch", 25 | "problemMatcher": "$tsc-watch", 26 | "isBackground": true, 27 | "presentation": { 28 | "reveal": "never" 29 | }, 30 | "group": "build" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | vsc-extension-quickstart.md 9 | **/tsconfig.json 10 | **/.eslintrc.json 11 | **/*.map 12 | **/*.ts 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.2.0] - 2022-04-14 4 | 5 | - Thanks to @jason-dour for contributing configurable GitHub auth scopes via the `githubGraphql.scopes` setting 🎉 6 | 7 | ## [0.1.0] - 2022-04-03 8 | 9 | - Serializer has moved to the VS Code GraphQL Notebook extension, which remains compatible with VS Code for the Web. You can continue to open `*.github-graphql-nb` files with the generic GraphQL notebook serializer 10 | - This extension now provides a convenience layer for running GitHub API queries as an alternative to configuring an endpoint using a `graphql-config` file 11 | 12 | ## [0.0.7] - 2022-02-09 13 | 14 | - Use `rebornix.vscode-code-renderer` extension to render JSON notebook output 15 | - Fix GraphQL language configuration 16 | 17 | ## [0.0.6] - 2022-02-03 18 | 19 | - Add missing JSON indentation on web 20 | 21 | ## [0.0.5] - 2022-01-09 22 | 23 | - This extension now works in https://vscode.dev and https://github.dev 24 | - github-graphql-nb file types now open in the notebook UI by default 25 | 26 | ## [0.0.4] - 2022-01-06 27 | 28 | - Markdown cells now render correctly 29 | 30 | ## [0.0.3] - 2022-01-06 31 | 32 | - You can now use variables with your queries as follows: 33 | 34 | ```graphql 35 | query ($owner: String!, $repo: String!) { 36 | repository(owner: $owner, name: $repo) { 37 | name 38 | } 39 | } 40 | 41 | variables { 42 | "owner": "eamodio", 43 | "repo": "vscode-gitlens" 44 | } 45 | ``` 46 | 47 | (Thanks [@eamodio](https://github.com/eamodio)!) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub GraphQL Notebooks 2 | 3 | Interactively run [GitHub GraphQL](https://docs.github.com/en/graphql) queries and mutations right inside [VS Code notebooks](https://code.visualstudio.com/api/extension-guides/notebook)! 📓 4 | 5 | 6 | 7 | Get started: 8 | 1. File > New File... > GitHub GraphQL Notebook 9 | 2. Sign into GitHub through the built-in GitHub Authentication extension when prompted 10 | 3. Run and save your queries and mutations 11 | -------------------------------------------------------------------------------- /grammars/graphql.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GraphQL", 3 | "scopeName": "source.graphql", 4 | "fileTypes": ["graphql", "graphqls", "gql", "graphcool"], 5 | "patterns": [{ "include": "#graphql" }], 6 | "repository": { 7 | "graphql": { 8 | "patterns": [ 9 | { "include": "#graphql-comment" }, 10 | { "include": "#graphql-description-docstring" }, 11 | { "include": "#graphql-description-singleline" }, 12 | { "include": "#graphql-fragment-definition" }, 13 | { "include": "#graphql-directive-definition" }, 14 | { "include": "#graphql-type-interface" }, 15 | { "include": "#graphql-enum" }, 16 | { "include": "#graphql-scalar" }, 17 | { "include": "#graphql-union" }, 18 | { "include": "#graphql-schema" }, 19 | { "include": "#graphql-operation-def" }, 20 | { "include": "#literal-quasi-embedded" } 21 | ] 22 | }, 23 | "graphql-operation-def": { 24 | "patterns": [ 25 | { "include": "#graphql-query-mutation" }, 26 | { "include": "#graphql-name" }, 27 | { "include": "#graphql-variable-definitions" }, 28 | { "include": "#graphql-directive" }, 29 | { "include": "#graphql-selection-set" } 30 | ] 31 | }, 32 | "graphql-fragment-definition": { 33 | "name": "meta.fragment.graphql", 34 | "begin": "\\s*(?:(\\bfragment\\b)\\s*([_A-Za-z][_0-9A-Za-z]*)?\\s*(?:(\\bon\\b)\\s*([_A-Za-z][_0-9A-Za-z]*)))", 35 | "end": "(?<=})", 36 | "captures": { 37 | "1": { "name": "keyword.fragment.graphql" }, 38 | "2": { "name": "entity.name.fragment.graphql" }, 39 | "3": { "name": "keyword.on.graphql" }, 40 | "4": { "name": "support.type.graphql" } 41 | }, 42 | "patterns": [ 43 | { "include": "#graphql-comment" }, 44 | { "include": "#graphql-description-docstring" }, 45 | { "include": "#graphql-description-singleline" }, 46 | { "include": "#graphql-selection-set" }, 47 | { "include": "#graphql-directive" }, 48 | { "include": "#graphql-skip-newlines" }, 49 | { "include": "#literal-quasi-embedded" } 50 | ] 51 | }, 52 | "graphql-query-mutation": { 53 | "match": "\\s*\\b(query|mutation)\\b", 54 | "captures": { 55 | "1": { "name": "keyword.operation.graphql" } 56 | } 57 | }, 58 | "graphql-type-interface": { 59 | "name": "meta.type.interface.graphql", 60 | "begin": "\\s*\\b(?:(extends?)?\\b\\s*\\b(type)|(interface)|(input))\\b\\s*([_A-Za-z][_0-9A-Za-z]*)?", 61 | "end": "(?=.)", 62 | "applyEndPatternLast": 1, 63 | "captures": { 64 | "1": { "name": "keyword.type.graphql" }, 65 | "2": { "name": "keyword.type.graphql" }, 66 | "3": { "name": "keyword.interface.graphql" }, 67 | "4": { "name": "keyword.input.graphql" }, 68 | "5": { "name": "support.type.graphql" } 69 | }, 70 | "patterns": [ 71 | { 72 | "begin": "\\s*\\b(implements)\\b\\s*", 73 | "end": "\\s*(?={)", 74 | "beginCaptures": { 75 | "1": { "name": "keyword.implements.graphql" } 76 | }, 77 | "patterns": [ 78 | { 79 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 80 | "captures": { 81 | "1": { "name": "support.type.graphql" } 82 | } 83 | }, 84 | { "include": "#graphql-comment" }, 85 | { "include": "#graphql-description-docstring" }, 86 | { "include": "#graphql-description-singleline" }, 87 | { "include": "#graphql-directive" }, 88 | { "include": "#graphql-ampersand" }, 89 | { "include": "#graphql-comma" } 90 | ] 91 | }, 92 | { "include": "#graphql-comment" }, 93 | { "include": "#graphql-description-docstring" }, 94 | { "include": "#graphql-description-singleline" }, 95 | { "include": "#graphql-directive" }, 96 | { "include": "#graphql-type-object" }, 97 | { "include": "#literal-quasi-embedded" }, 98 | { "include": "#graphql-ignore-spaces" } 99 | ] 100 | }, 101 | "graphql-ignore-spaces": { 102 | "match": "\\s*" 103 | }, 104 | "graphql-type-object": { 105 | "name": "meta.type.object.graphql", 106 | "begin": "\\s*({)", 107 | "end": "\\s*(})", 108 | "beginCaptures": { 109 | "1": { "name": "punctuation.operation.graphql" } 110 | }, 111 | "endCaptures": { 112 | "1": { "name": "punctuation.operation.graphql" } 113 | }, 114 | "patterns": [ 115 | { "include": "#graphql-comment" }, 116 | { "include": "#graphql-description-docstring" }, 117 | { "include": "#graphql-description-singleline" }, 118 | { "include": "#graphql-object-type" }, 119 | { "include": "#graphql-type-definition" }, 120 | { "include": "#literal-quasi-embedded" } 121 | ] 122 | }, 123 | "graphql-type-definition": { 124 | "comment": "key (optionalArgs): Type", 125 | "begin": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?=\\s*\\(|:)", 126 | "end": "(?=\\s*(([_A-Za-z][_0-9A-Za-z]*)\\s*(\\(|:)|(})))|\\s*(,)", 127 | "beginCaptures": { 128 | "1": { "name": "variable.graphql" } 129 | }, 130 | "endCaptures": { 131 | "5": { "name": "punctuation.comma.graphql" } 132 | }, 133 | "patterns": [ 134 | { "include": "#graphql-comment" }, 135 | { "include": "#graphql-description-docstring" }, 136 | { "include": "#graphql-description-singleline" }, 137 | { "include": "#graphql-directive" }, 138 | { "include": "#graphql-variable-definitions" }, 139 | { "include": "#graphql-type-object" }, 140 | { "include": "#graphql-colon" }, 141 | { "include": "#graphql-input-types" }, 142 | { "include": "#literal-quasi-embedded" } 143 | ] 144 | }, 145 | "graphql-schema": { 146 | "begin": "\\s*\\b(schema)\\b", 147 | "end": "(?<=})", 148 | "beginCaptures": { 149 | "1": { "name": "keyword.schema.graphql" } 150 | }, 151 | "patterns": [ 152 | { 153 | "begin": "\\s*({)", 154 | "end": "\\s*(})", 155 | "beginCaptures": { 156 | "1": { "name": "punctuation.operation.graphql" } 157 | }, 158 | "endCaptures": { 159 | "1": { "name": "punctuation.operation.graphql" } 160 | }, 161 | "patterns": [ 162 | { 163 | "begin": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?=\\s*\\(|:)", 164 | "end": "(?=\\s*(([_A-Za-z][_0-9A-Za-z]*)\\s*(\\(|:)|(})))|\\s*(,)", 165 | "beginCaptures": { 166 | "1": { "name": "variable.arguments.graphql" } 167 | }, 168 | "endCaptures": { 169 | "5": { "name": "punctuation.comma.graphql" } 170 | }, 171 | "patterns": [ 172 | { 173 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 174 | "captures": { 175 | "1": { "name": "support.type.graphql" } 176 | } 177 | }, 178 | { "include": "#graphql-comment" }, 179 | { "include": "#graphql-description-docstring" }, 180 | { "include": "#graphql-description-singleline" }, 181 | { "include": "#graphql-colon" }, 182 | { "include": "#graphql-skip-newlines" } 183 | ] 184 | }, 185 | { "include": "#graphql-comment" }, 186 | { "include": "#graphql-description-docstring" }, 187 | { "include": "#graphql-description-singleline" }, 188 | { "include": "#graphql-skip-newlines" } 189 | ] 190 | }, 191 | { "include": "#graphql-comment" }, 192 | { "include": "#graphql-description-docstring" }, 193 | { "include": "#graphql-description-singleline" }, 194 | { "include": "#graphql-directive" }, 195 | { "include": "#graphql-skip-newlines" } 196 | ] 197 | }, 198 | "graphql-comment": { 199 | "patterns": [ 200 | { 201 | "comment": "need to prefix comment space with a scope else Atom's reflow cmd doesn't work", 202 | "name": "comment.line.graphql.js", 203 | "match": "(\\s*)(#).*", 204 | "captures": { 205 | "1": { 206 | "name": "punctuation.whitespace.comment.leading.graphql" 207 | } 208 | } 209 | }, 210 | { 211 | "name": "comment.line.graphql.js", 212 | "begin": "(\"\"\")", 213 | "end": "(\"\"\")", 214 | "beginCaptures": { 215 | "1": { 216 | "name": "punctuation.whitespace.comment.leading.graphql" 217 | } 218 | } 219 | }, 220 | { 221 | "name": "comment.line.graphql.js", 222 | "begin": "(\")", 223 | "end": "(\")", 224 | "beginCaptures": { 225 | "1": { 226 | "name": "punctuation.whitespace.comment.leading.graphql" 227 | } 228 | } 229 | } 230 | ] 231 | }, 232 | "graphql-description-singleline": { 233 | "name": "comment.line.number-sign.graphql", 234 | "match": "#(?=([^\"]*\"[^\"]*\")*[^\"]*$).*$" 235 | }, 236 | "graphql-description-docstring": { 237 | "name": "comment.block.graphql", 238 | "begin": "\"\"\"", 239 | "end": "\"\"\"" 240 | }, 241 | "graphql-variable-definitions": { 242 | "begin": "\\s*(\\()", 243 | "end": "\\s*(\\))", 244 | "captures": { 245 | "1": { "name": "meta.brace.round.graphql" } 246 | }, 247 | "patterns": [ 248 | { "include": "#graphql-comment" }, 249 | { "include": "#graphql-description-docstring" }, 250 | { "include": "#graphql-description-singleline" }, 251 | { "include": "#graphql-variable-definition" }, 252 | { "include": "#literal-quasi-embedded" } 253 | ] 254 | }, 255 | "graphql-variable-definition": { 256 | "comment": "variable: type = value,.... which may be a list", 257 | "name": "meta.variables.graphql", 258 | "begin": "\\s*(\\$?[_A-Za-z][_0-9A-Za-z]*)(?=\\s*\\(|:)", 259 | "end": "(?=\\s*((\\$?[_A-Za-z][_0-9A-Za-z]*)\\s*(\\(|:)|(}|\\))))|\\s*(,)", 260 | "beginCaptures": { 261 | "1": { "name": "variable.parameter.graphql" } 262 | }, 263 | "endCaptures": { 264 | "5": { "name": "punctuation.comma.graphql" } 265 | }, 266 | "patterns": [ 267 | { "include": "#graphql-comment" }, 268 | { "include": "#graphql-description-docstring" }, 269 | { "include": "#graphql-description-singleline" }, 270 | { "include": "#graphql-directive" }, 271 | { "include": "#graphql-colon" }, 272 | { "include": "#graphql-input-types" }, 273 | { "include": "#graphql-variable-assignment" }, 274 | { "include": "#literal-quasi-embedded" }, 275 | { "include": "#graphql-skip-newlines" } 276 | ] 277 | }, 278 | "graphql-input-types": { 279 | "patterns": [ 280 | { "include": "#graphql-scalar-type" }, 281 | { 282 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?:\\s*(!))?", 283 | "captures": { 284 | "1": { "name": "support.type.graphql" }, 285 | "2": { "name": "keyword.operator.nulltype.graphql" } 286 | } 287 | }, 288 | { 289 | "name": "meta.type.list.graphql", 290 | "begin": "\\s*(\\[)", 291 | "end": "\\s*(\\])(?:\\s*(!))?", 292 | "captures": { 293 | "1": { "name": "meta.brace.square.graphql" }, 294 | "2": { "name": "keyword.operator.nulltype.graphql" } 295 | }, 296 | "patterns": [ 297 | { "include": "#graphql-comment" }, 298 | { "include": "#graphql-description-docstring" }, 299 | { "include": "#graphql-description-singleline" }, 300 | { "include": "#graphql-input-types" }, 301 | { "include": "#graphql-comma" }, 302 | { "include": "#literal-quasi-embedded" } 303 | ] 304 | } 305 | ] 306 | }, 307 | "graphql-scalar": { 308 | "match": "\\s*\\b(scalar)\\b\\s*([_A-Za-z][_0-9A-Za-z]*)", 309 | "captures": { 310 | "1": { "name": "keyword.scalar.graphql" }, 311 | "2": { "name": "entity.scalar.graphql" } 312 | } 313 | }, 314 | "graphql-scalar-type": { 315 | "match": "\\s*\\b(Int|Float|String|Boolean|ID)\\b(?:\\s*(!))?", 316 | "captures": { 317 | "1": { "name": "support.type.builtin.graphql" }, 318 | "2": { "name": "keyword.operator.nulltype.graphql" } 319 | } 320 | }, 321 | "graphql-variable-assignment": { 322 | "begin": "\\s(=)", 323 | "end": "(?=[\n,)])", 324 | "applyEndPatternLast": 1, 325 | "beginCaptures": { 326 | "1": { "name": "punctuation.assignment.graphql" } 327 | }, 328 | "patterns": [{ "include": "#graphql-value" }] 329 | }, 330 | "graphql-comma": { 331 | "match": "\\s*(,)", 332 | "captures": { 333 | "1": { "name": "punctuation.comma.graphql" } 334 | } 335 | }, 336 | "graphql-ampersand": { 337 | "match": "\\s*(&)", 338 | "captures": { 339 | "1": { "name": "keyword.operator.logical.graphql" } 340 | } 341 | }, 342 | "graphql-colon": { 343 | "match": "\\s*(:)", 344 | "captures": { 345 | "1": { "name": "punctuation.colon.graphql" } 346 | } 347 | }, 348 | "graphql-union-mark": { 349 | "match": "\\s*(\\|)", 350 | "captures": { 351 | "1": { "name": "punctuation.union.graphql" } 352 | } 353 | }, 354 | "graphql-name": { 355 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 356 | "captures": { 357 | "1": { "name": "entity.name.function.graphql" } 358 | } 359 | }, 360 | "graphql-directive": { 361 | "begin": "\\s*((@)\\s*([_A-Za-z][_0-9A-Za-z]*))", 362 | "end": "(?=.)", 363 | "applyEndPatternLast": 1, 364 | "beginCaptures": { 365 | "1": { "name": "entity.name.function.directive.graphql" } 366 | }, 367 | "patterns": [ 368 | { "include": "#graphql-comment" }, 369 | { "include": "#graphql-description-docstring" }, 370 | { "include": "#graphql-description-singleline" }, 371 | { "include": "#graphql-arguments" }, 372 | { "include": "#literal-quasi-embedded" }, 373 | { "include": "#graphql-skip-newlines" } 374 | ] 375 | }, 376 | "graphql-directive-definition": { 377 | "begin": "\\s*(\\bdirective\\b)\\s*(@[_A-Za-z][_0-9A-Za-z]*)", 378 | "end": "(?=.)", 379 | "applyEndPatternLast": 1, 380 | "beginCaptures": { 381 | "1": { 382 | "name": "keyword.directive.graphql" 383 | }, 384 | "2": { 385 | "name": "entity.name.function.directive.graphql" 386 | }, 387 | "3": { "name": "keyword.on.graphql" }, 388 | "4": { "name": "support.type.graphql" } 389 | }, 390 | "patterns": [ 391 | { 392 | "include": "#graphql-variable-definitions" 393 | }, 394 | { 395 | "begin": "\\s*(\\bon\\b)\\s*([_A-Za-z]*)", 396 | "end": "(?=.)", 397 | "applyEndPatternLast": 1, 398 | "beginCaptures": { 399 | "1": { 400 | "name": "keyword.on.graphql" 401 | }, 402 | "2": { 403 | "name": "support.type.location.graphql" 404 | } 405 | }, 406 | "patterns": [ 407 | { 408 | "include": "#graphql-skip-newlines" 409 | }, 410 | { 411 | "include": "#graphql-comment" 412 | }, 413 | { 414 | "include": "#literal-quasi-embedded" 415 | }, 416 | { 417 | "match": "\\s*(\\|)\\s*([_A-Za-z]*)", 418 | "captures": { 419 | "2": { 420 | "name": "support.type.location.graphql" 421 | } 422 | } 423 | } 424 | ] 425 | }, 426 | { 427 | "include": "#graphql-skip-newlines" 428 | }, 429 | { 430 | "include": "#graphql-comment" 431 | }, 432 | { 433 | "include": "#literal-quasi-embedded" 434 | } 435 | ] 436 | }, 437 | "graphql-selection-set": { 438 | "name": "meta.selectionset.graphql", 439 | "begin": "\\s*({)", 440 | "end": "\\s*(})", 441 | "beginCaptures": { 442 | "1": { "name": "punctuation.operation.graphql" } 443 | }, 444 | "endCaptures": { 445 | "1": { "name": "punctuation.operation.graphql" } 446 | }, 447 | "patterns": [ 448 | { "include": "#graphql-comment" }, 449 | { "include": "#graphql-description-docstring" }, 450 | { "include": "#graphql-description-singleline" }, 451 | { "include": "#graphql-field" }, 452 | { "include": "#graphql-fragment-spread" }, 453 | { "include": "#graphql-inline-fragment" }, 454 | { "include": "#graphql-comma" }, 455 | { "include": "#native-interpolation" }, 456 | { "include": "#literal-quasi-embedded" } 457 | ] 458 | }, 459 | "graphql-field": { 460 | "patterns": [ 461 | { 462 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)\\s*(:)", 463 | "captures": { 464 | "1": { "name": "string.unquoted.alias.graphql" }, 465 | "2": { "name": "punctuation.colon.graphql" } 466 | } 467 | }, 468 | { 469 | "match": "\\s*([_A-Za-z][_0-9A-Za-z]*)", 470 | "captures": { 471 | "1": { "name": "variable.graphql" } 472 | } 473 | }, 474 | { "include": "#graphql-arguments" }, 475 | { "include": "#graphql-directive" }, 476 | { "include": "#graphql-selection-set" }, 477 | { "include": "#literal-quasi-embedded" }, 478 | { "include": "#graphql-skip-newlines" } 479 | ] 480 | }, 481 | "graphql-fragment-spread": { 482 | "begin": "\\s*(\\.\\.\\.)\\s*(?!\\bon\\b)([_A-Za-z][_0-9A-Za-z]*)", 483 | "end": "(?=.)", 484 | "applyEndPatternLast": 1, 485 | "captures": { 486 | "1": { "name": "keyword.operator.spread.graphql" }, 487 | "2": { "name": "variable.fragment.graphql" } 488 | }, 489 | "patterns": [ 490 | { "include": "#graphql-comment" }, 491 | { "include": "#graphql-description-docstring" }, 492 | { "include": "#graphql-description-singleline" }, 493 | { "include": "#graphql-selection-set" }, 494 | { "include": "#graphql-directive" }, 495 | { "include": "#literal-quasi-embedded" }, 496 | { "include": "#graphql-skip-newlines" } 497 | ] 498 | }, 499 | "graphql-inline-fragment": { 500 | "begin": "\\s*(\\.\\.\\.)\\s*(?:(\\bon\\b)\\s*([_A-Za-z][_0-9A-Za-z]*))?", 501 | "end": "(?=.)", 502 | "applyEndPatternLast": 1, 503 | "captures": { 504 | "1": { "name": "keyword.operator.spread.graphql" }, 505 | "2": { "name": "keyword.on.graphql" }, 506 | "3": { "name": "support.type.graphql" } 507 | }, 508 | "patterns": [ 509 | { "include": "#graphql-comment" }, 510 | { "include": "#graphql-description-docstring" }, 511 | { "include": "#graphql-description-singleline" }, 512 | { "include": "#graphql-selection-set" }, 513 | { "include": "#graphql-directive" }, 514 | { "include": "#graphql-skip-newlines" }, 515 | { "include": "#literal-quasi-embedded" } 516 | ] 517 | }, 518 | "graphql-arguments": { 519 | "name": "meta.arguments.graphql", 520 | "begin": "\\s*(\\()", 521 | "end": "\\s*(\\))", 522 | "beginCaptures": { 523 | "1": { "name": "meta.brace.round.directive.graphql" } 524 | }, 525 | "endCaptures": { 526 | "1": { "name": "meta.brace.round.directive.graphql" } 527 | }, 528 | "patterns": [ 529 | { "include": "#graphql-comment" }, 530 | { "include": "#graphql-description-docstring" }, 531 | { "include": "#graphql-description-singleline" }, 532 | { 533 | "begin": "\\s*([_A-Za-z][_0-9A-Za-z]*)(?:\\s*(:))", 534 | "end": "(?=\\s*(?:(?:([_A-Za-z][_0-9A-Za-z]*)\\s*(:))|\\)))|\\s*(,)", 535 | "beginCaptures": { 536 | "1": { "name": "variable.parameter.graphql" }, 537 | "2": { "name": "punctuation.colon.graphql" } 538 | }, 539 | "endCaptures": { 540 | "3": { "name": "punctuation.comma.graphql" } 541 | }, 542 | "patterns": [ 543 | { "include": "#graphql-comment" }, 544 | { "include": "#graphql-description-docstring" }, 545 | { "include": "#graphql-description-singleline" }, 546 | { "include": "#graphql-directive" }, 547 | { "include": "#graphql-value" }, 548 | { "include": "#graphql-skip-newlines" } 549 | ] 550 | }, 551 | { "include": "#literal-quasi-embedded" } 552 | ] 553 | }, 554 | "graphql-variable-name": { 555 | "match": "\\s*(\\$[_A-Za-z][_0-9A-Za-z]*)", 556 | "captures": { 557 | "1": { "name": "variable.graphql" } 558 | } 559 | }, 560 | "graphql-float-value": { 561 | "match": "\\s*(-?(0|[1-9][0-9]*)(\\.[0-9]+)?((e|E)(\\+|-)?[0-9]+)?)", 562 | "captures": { 563 | "1": { "name": "constant.numeric.float.graphql" } 564 | } 565 | }, 566 | "graphql-boolean-value": { 567 | "match": "\\s*\\b(true|false)\\b", 568 | "captures": { 569 | "1": { "name": "constant.language.boolean.graphql" } 570 | } 571 | }, 572 | "graphql-null-value": { 573 | "match": "\\s*\\b(null)\\b", 574 | "captures": { 575 | "1": { "name": "constant.language.null.graphql" } 576 | } 577 | }, 578 | "graphql-string-value": { 579 | "contentName": "string.quoted.double.graphql", 580 | "begin": "\\s*+((\"))", 581 | "end": "\\s*+(?:((\"))|(\n))", 582 | "beginCaptures": { 583 | "1": { "name": "string.quoted.double.graphql" }, 584 | "2": { "name": "punctuation.definition.string.begin.graphql" } 585 | }, 586 | "endCaptures": { 587 | "1": { "name": "string.quoted.double.graphql" }, 588 | "2": { "name": "punctuation.definition.string.end.graphql" }, 589 | "3": { "name": "invalid.illegal.newline.graphql" } 590 | }, 591 | "patterns": [ 592 | { "include": "#graphql-string-content" }, 593 | { "include": "#literal-quasi-embedded" } 594 | ] 595 | }, 596 | "graphql-string-content": { 597 | "patterns": [ 598 | { 599 | "name": "constant.character.escape.graphql", 600 | "match": "\\\\[/'\"\\\\nrtbf]" 601 | }, 602 | { 603 | "name": "constant.character.escape.graphql", 604 | "match": "\\\\u([0-9a-fA-F]{4})" 605 | } 606 | ] 607 | }, 608 | "graphql-enum": { 609 | "name": "meta.enum.graphql", 610 | "begin": "\\s*+\\b(enum)\\b\\s*([_A-Za-z][_0-9A-Za-z]*)", 611 | "end": "(?<=})", 612 | "beginCaptures": { 613 | "1": { "name": "keyword.enum.graphql" }, 614 | "2": { "name": "support.type.enum.graphql" } 615 | }, 616 | "patterns": [ 617 | { 618 | "name": "meta.type.object.graphql", 619 | "begin": "\\s*({)", 620 | "end": "\\s*(})", 621 | "beginCaptures": { 622 | "1": { "name": "punctuation.operation.graphql" } 623 | }, 624 | "endCaptures": { 625 | "1": { "name": "punctuation.operation.graphql" } 626 | }, 627 | "patterns": [ 628 | { "include": "#graphql-object-type" }, 629 | 630 | { "include": "#graphql-comment" }, 631 | { "include": "#graphql-description-docstring" }, 632 | { "include": "#graphql-description-singleline" }, 633 | { "include": "#graphql-directive" }, 634 | { "include": "#graphql-enum-value" }, 635 | { "include": "#literal-quasi-embedded" } 636 | ] 637 | }, 638 | { "include": "#graphql-comment" }, 639 | { "include": "#graphql-description-docstring" }, 640 | { "include": "#graphql-description-singleline" }, 641 | { "include": "#graphql-directive" } 642 | ] 643 | }, 644 | "graphql-enum-value": { 645 | "name": "constant.character.enum.graphql", 646 | "match": "\\s*(?!=\\b(true|false|null)\\b)([_A-Za-z][_0-9A-Za-z]*)" 647 | }, 648 | "graphql-value": { 649 | "patterns": [ 650 | { "include": "#graphql-comment" }, 651 | { "include": "#graphql-description-docstring" }, 652 | { "include": "#graphql-variable-name" }, 653 | { "include": "#graphql-float-value" }, 654 | { "include": "#graphql-string-value" }, 655 | { "include": "#graphql-boolean-value" }, 656 | { "include": "#graphql-null-value" }, 657 | { "include": "#graphql-enum-value" }, 658 | { "include": "#graphql-list-value" }, 659 | { "include": "#graphql-object-value" }, 660 | { "include": "#literal-quasi-embedded" } 661 | ] 662 | }, 663 | "graphql-list-value": { 664 | "patterns": [ 665 | { 666 | "name": "meta.listvalues.graphql", 667 | "begin": "\\s*+(\\[)", 668 | "end": "\\s*(\\])", 669 | "endCaptures": { 670 | "1": { "name": "meta.brace.square.graphql" } 671 | }, 672 | "beginCaptures": { 673 | "1": { "name": "meta.brace.square.graphql" } 674 | }, 675 | "patterns": [{ "include": "#graphql-value" }] 676 | } 677 | ] 678 | }, 679 | "graphql-object-value": { 680 | "patterns": [ 681 | { 682 | "name": "meta.objectvalues.graphql", 683 | "begin": "\\s*+({)", 684 | "end": "\\s*(})", 685 | "beginCaptures": { 686 | "1": { "name": "meta.brace.curly.graphql" } 687 | }, 688 | "endCaptures": { 689 | "1": { "name": "meta.brace.curly.graphql" } 690 | }, 691 | "patterns": [ 692 | { "include": "#graphql-object-field" }, 693 | { "include": "#graphql-value" } 694 | ] 695 | } 696 | ] 697 | }, 698 | "graphql-object-field": { 699 | "match": "\\s*(([_A-Za-z][_0-9A-Za-z]*))\\s*(:)", 700 | "captures": { 701 | "1": { "name": "constant.object.key.graphql" }, 702 | "2": { "name": "string.unquoted.graphql" }, 703 | "3": { "name": "punctuation.graphql" } 704 | } 705 | }, 706 | "graphql-union": { 707 | "begin": "\\s*\\b(union)\\b\\s*([_A-Za-z][_0-9A-Za-z]*)", 708 | "end": "(?=.)", 709 | "applyEndPatternLast": 1, 710 | "captures": { 711 | "1": { "name": "keyword.union.graphql" }, 712 | "2": { "name": "support.type.graphql" } 713 | }, 714 | "patterns": [ 715 | { 716 | "begin": "\\s*(=)\\s*([_A-Za-z][_0-9A-Za-z]*)", 717 | "end": "(?=.)", 718 | "applyEndPatternLast": 1, 719 | "captures": { 720 | "1": { "name": "punctuation.assignment.graphql" }, 721 | "2": { "name": "support.type.graphql" } 722 | }, 723 | "patterns": [ 724 | { "include": "#graphql-comment" }, 725 | { "include": "#graphql-description-docstring" }, 726 | { "include": "#graphql-description-singleline" }, 727 | { "include": "#graphql-skip-newlines" }, 728 | { "include": "#literal-quasi-embedded" }, 729 | { 730 | "match": "\\s*(\\|)\\s*([_A-Za-z][_0-9A-Za-z]*)", 731 | "captures": { 732 | "1": { "name": "punctuation.or.graphql" }, 733 | "2": { "name": "support.type.graphql" } 734 | } 735 | } 736 | ] 737 | }, 738 | { "include": "#graphql-comment" }, 739 | { "include": "#graphql-description-docstring" }, 740 | { "include": "#graphql-description-singleline" }, 741 | { "include": "#graphql-skip-newlines" }, 742 | { "include": "#literal-quasi-embedded" } 743 | ] 744 | }, 745 | "native-interpolation": { 746 | "name": "native.interpolation", 747 | "begin": "\\s*(\\${)", 748 | "end": "(})", 749 | "beginCaptures": { 750 | "1": { "name": "keyword.other.substitution.begin" } 751 | }, 752 | "endCaptures": { 753 | "1": { "name": "keyword.other.substitution.end" } 754 | }, 755 | "patterns": [ 756 | { "include": "source.js" }, 757 | { "include": "source.ts" }, 758 | { "include": "source.js.jsx" }, 759 | { "include": "source.tsx" } 760 | ] 761 | }, 762 | "graphql-skip-newlines": { 763 | "match": "\\s*\n" 764 | } 765 | } 766 | } 767 | -------------------------------------------------------------------------------- /images/github-graphql.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyceerhl/vscode-github-graphql-notebooks/e2210c2a4ad869208e0d6d60571c5d144246cd7b/images/github-graphql.gif -------------------------------------------------------------------------------- /language/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#", 4 | "blockComment": ["\"\"\"", "\"\"\""] 5 | }, 6 | "brackets": [["{", "}"], ["[", "]"], ["(", ")"]], 7 | "autoClosingPairs": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"], 11 | { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, 12 | ["'", "'"] 13 | ], 14 | "surroundingPairs": [ 15 | ["{", "}"], 16 | ["[", "]"], 17 | ["(", ")"], 18 | ["\"", "\""], 19 | ["'", "'"] 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-graphql-nb", 3 | "displayName": "GitHub GraphQL Notebooks", 4 | "publisher": "joyceerhl", 5 | "description": "Interactively run GitHub GraphQL queries and mutations in VS Code", 6 | "repository": { 7 | "url": "https://github.com/joyceerhl/vscode-github-graphql-notebooks" 8 | }, 9 | "version": "0.2.0", 10 | "license": "MIT", 11 | "engines": { 12 | "vscode": "^1.64.0" 13 | }, 14 | "categories": [ 15 | "Notebooks" 16 | ], 17 | "keywords": [ 18 | "GitHub", 19 | "GraphQL" 20 | ], 21 | "extensionDependencies": [ 22 | "joyceerhl.vscode-graphql-notebook" 23 | ], 24 | "activationEvents": [ 25 | "onNotebook:gqlnb", 26 | "onNotebook:github-graphql-nb" 27 | ], 28 | "browser": "./dist/browser/extension", 29 | "main": "./dist/extension", 30 | "contributes": { 31 | "configuration": { 32 | "title": "GitHub GraphQL Notebooks", 33 | "properties": { 34 | "githubGraphql.scopes": { 35 | "type": "array", 36 | "items": { 37 | "type": "string" 38 | }, 39 | "default": [ 40 | "repo", 41 | "workflow" 42 | ], 43 | "markdownDescription": "The authentication scopes to request when signing into GitHub. See [Available Scopes](https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes) in GitHub Documentation for more information.", 44 | "source": "resource" 45 | } 46 | } 47 | }, 48 | "languages": [ 49 | { 50 | "id": "graphql", 51 | "aliases": [ 52 | "GraphQL", 53 | "graphql" 54 | ], 55 | "configuration": "./language/language-configuration.json" 56 | } 57 | ], 58 | "grammars": [ 59 | { 60 | "language": "graphql", 61 | "path": "./grammars/graphql.json", 62 | "scopeName": "source.graphql" 63 | } 64 | ] 65 | }, 66 | "scripts": { 67 | "vscode:prepublish": "npm run package", 68 | "compile": "webpack", 69 | "watch": "webpack --watch", 70 | "package": "webpack --mode production --devtool hidden-source-map", 71 | "test-compile": "tsc -p ./", 72 | "test-watch": "tsc -watch -p ./", 73 | "pretest": "npm run test-compile && npm run lint", 74 | "lint": "eslint src --ext ts", 75 | "test": "node ./out/test/runTest.js", 76 | "serve": "npx serve --cors -l 5000", 77 | "tunnel": "npx localtunnel -p 5000" 78 | }, 79 | "dependencies": { 80 | "@octokit/core": "3.5.1", 81 | "cross-fetch": "3.1.5", 82 | "node-fetch": "3.2.3", 83 | "vsce": "^2.7.0" 84 | }, 85 | "devDependencies": { 86 | "@types/glob": "^7.1.3", 87 | "@types/mocha": "^8.2.2", 88 | "@types/node": "14.x", 89 | "@types/vscode": "^1.60.0", 90 | "@typescript-eslint/eslint-plugin": "^4.26.0", 91 | "@typescript-eslint/parser": "^4.26.0", 92 | "eslint": "^7.27.0", 93 | "glob": "^7.1.7", 94 | "mocha": "^9.1.4", 95 | "ts-loader": "^9.2.2", 96 | "typescript": "^4.3.2", 97 | "vscode-test": "^1.5.2", 98 | "webpack": "^5.38.1", 99 | "webpack-cli": "^4.7.0" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/core'; 2 | import fetch from 'node-fetch'; 3 | import * as vscode from 'vscode'; 4 | import { authentication, AuthenticationSession } from 'vscode'; 5 | 6 | export function activate(context: vscode.ExtensionContext) { 7 | context.subscriptions.push(new OctokitController()); 8 | } 9 | 10 | // this method is called when your extension is deactivated 11 | export function deactivate() {} 12 | 13 | 14 | var authorizationScopes = vscode.workspace.getConfiguration('githubGraphql').get('scopes') as string[]; 15 | 16 | const variablesRegex = /^\s*variables\s*(\{[^}]*\})\s*$/m; 17 | 18 | class OctokitController { 19 | private _octokit: Octokit | undefined; 20 | private _octokitDefaults: typeof Octokit | undefined; 21 | private _session: AuthenticationSession | undefined; 22 | private controller: vscode.NotebookController; 23 | 24 | constructor() { 25 | this.controller = vscode.notebooks.createNotebookController('github-graphql', 'gqlnb', 'GitHub GraphQL', (cells, notebook, c) => this.executeCells(cells, notebook, c)); 26 | vscode.workspace.onDidChangeConfiguration((e) => { 27 | if (e.affectsConfiguration('githubGraphql')) { 28 | authorizationScopes = vscode.workspace.getConfiguration('githubGraphql').get('scopes') ?? []; // Update our cached auth scopes 29 | this.clearAuthenticationSession(); // Clear out the existing auth session if requested scopes changed 30 | } 31 | }); 32 | } 33 | 34 | dispose() { 35 | return this.controller.dispose(); 36 | } 37 | 38 | private async executeCells(cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) { 39 | for (const cell of cells) { 40 | await this.executeCell(cell); 41 | } 42 | } 43 | 44 | private async executeCell(cell: vscode.NotebookCell) { 45 | const task = this.controller.createNotebookCellExecution(cell); 46 | task.start(Date.now()); 47 | 48 | const contents = cell.document.getText(); 49 | 50 | let code: string; 51 | let variables: Record | undefined; 52 | const match = contents.match(variablesRegex); 53 | if (match) { 54 | try { 55 | variables = JSON.parse(match[1]); 56 | } catch (e) { 57 | replaceOutput(task, `Unable to parse 'variables': ${String(e)}`); 58 | task.end(false, Date.now()); 59 | return; 60 | } 61 | 62 | code = contents.replace(variablesRegex, '').trim(); 63 | } else { 64 | code = contents.trim(); 65 | } 66 | 67 | let success = false; 68 | try { 69 | const resp = await this.graphql(code, variables); 70 | success = true; 71 | replaceOutput(task, resp); 72 | } catch (e) { 73 | replaceOutput(task, e); 74 | } 75 | task.end(success, Date.now()); 76 | } 77 | 78 | private async graphql(query: string, variables?: Record): Promise { 79 | if (this._octokit === undefined) { 80 | this._octokit = await this.octokit(); 81 | } 82 | 83 | try { 84 | return await this._octokit.graphql(query, variables); 85 | } catch (ex) { 86 | throw ex; 87 | } 88 | } 89 | 90 | private async octokit(options?: ConstructorParameters[0]) { 91 | if (this._octokitDefaults === undefined) { 92 | const session = await this.ensureAuthenticated(); 93 | 94 | if (vscode.env.uiKind === vscode.UIKind.Web) { 95 | async function fetchCore(url: string, options: { headers?: Record }) { 96 | if (options.headers !== undefined) { 97 | const { 'user-agent': userAgent, ...headers } = options.headers; 98 | if (userAgent) { 99 | options.headers = headers; 100 | } 101 | } 102 | return fetch(url, options); 103 | } 104 | 105 | this._octokitDefaults = Octokit.defaults({ 106 | auth: `token ${session.accessToken}`, 107 | request: { fetch: fetchCore }, 108 | }); 109 | } else { 110 | this._octokitDefaults = Octokit.defaults({ auth: `token ${session.accessToken}` }); 111 | } 112 | } 113 | 114 | const octokit = new this._octokitDefaults(options); 115 | return octokit; 116 | } 117 | 118 | private async ensureAuthenticated(force: boolean = false) { 119 | if (this._session === undefined) { 120 | async function waitUntilAuthenticated() { 121 | try { 122 | const session = await authentication.getSession('github', authorizationScopes as readonly string[], { 123 | createIfNone: true, 124 | }); 125 | if (session !== undefined) { 126 | return session; 127 | } 128 | } catch {} 129 | 130 | return new Promise(resolve => { 131 | async function getSession() { 132 | const session = await authentication.getSession('github', authorizationScopes as readonly string[], { 133 | createIfNone: true, 134 | }); 135 | if (session !== undefined) { 136 | resolve(session); 137 | } 138 | } 139 | 140 | const disposable = authentication.onDidChangeSessions(async e => { 141 | if (e.provider.id === 'github') { 142 | disposable.dispose(); 143 | await getSession(); 144 | } 145 | }); 146 | }); 147 | } 148 | 149 | this._session = await waitUntilAuthenticated(); 150 | const disposable = authentication.onDidChangeSessions(e => { 151 | if (this._session === undefined) { 152 | disposable.dispose(); 153 | return; 154 | } 155 | 156 | if (e.provider.id === 'github') { 157 | this.clearAuthenticationSession(); 158 | disposable.dispose(); 159 | } 160 | }); 161 | } 162 | 163 | return this._session; 164 | } 165 | 166 | private clearAuthenticationSession() { 167 | this._session = undefined; 168 | this._octokit = undefined; 169 | this._octokitDefaults = undefined; 170 | } 171 | } 172 | 173 | 174 | function replaceOutput(task: vscode.NotebookCellExecution, jsonData: unknown) { 175 | const stringified = JSON.stringify(jsonData, undefined, 4); 176 | let data; 177 | if (vscode.env.uiKind === vscode.UIKind.Web) { 178 | data = new TextEncoder().encode(stringified); 179 | } else { 180 | data = Buffer.from(stringified); 181 | } 182 | const item = new vscode.NotebookCellOutputItem(data, 'text/x-json'); 183 | const output = new vscode.NotebookCellOutput([item]); 184 | task.replaceOutput(output); 185 | } 186 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 3 | 4 | 'use strict'; 5 | 6 | const path = require('path'); 7 | 8 | async function getExtensionConfig(target, mode, env) { 9 | return { 10 | name: `extension:${target}`, 11 | entry: './src/extension.ts', 12 | target: target, 13 | mode: mode, 14 | devtool: 'nosources-source-map', 15 | externals: { 16 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 17 | // modules added here also need to be added in the .vsceignore file 18 | }, 19 | output: { 20 | path: target === 'webworker' ? path.join(__dirname, 'dist', 'browser') : path.join(__dirname, 'dist'), 21 | filename: 'extension.js', 22 | libraryTarget: 'commonjs2' 23 | }, 24 | resolve: { 25 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 26 | extensions: ['.ts', '.js'], 27 | alias: 28 | target === 'webworker' ? { 'node-fetch': 'cross-fetch' } : undefined, 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }; 44 | } 45 | 46 | module.exports = /** 47 | * @param {{ esbuild?: boolean; } | undefined } env 48 | * @param {{ mode: 'production' | 'development' | 'none' | undefined; }} argv 49 | * @returns { Promise } 50 | */ 51 | async function (env, argv) { 52 | const mode = argv.mode || 'none'; 53 | 54 | env = { 55 | esbuild: false, 56 | ...env, 57 | }; 58 | 59 | return Promise.all([ 60 | getExtensionConfig('node', mode, env), 61 | getExtensionConfig('webworker', mode, env), 62 | ]); 63 | }; 64 | --------------------------------------------------------------------------------