diff --git a/packages/apollo-cli/README.md b/packages/apollo-cli/README.md index 8d8cab4760..88005ac1e0 100644 --- a/packages/apollo-cli/README.md +++ b/packages/apollo-cli/README.md @@ -5,17 +5,19 @@ Apollo CLI brings together your GraphQL clients and servers with tools for validating your schema, linting your operations for compatibility with your server, and generating static types for improved client-side type safety. -* [Apollo CLI](#apollo-cli) -* [Usage](#usage) -* [Commands](#commands) -* [Configuration](#configuration) -* [Code Generation](#code-generation) -* [Contributing](#contributing) - + +- [Apollo CLI](#apollo-cli) +- [Usage](#usage) +- [Commands](#commands) +- [Configuration](#configuration) +- [Code Generation](#code-generation) +- [Contributing](#contributing) + # Usage + ```sh-session $ npm install -g apollo $ apollo COMMAND @@ -27,17 +29,19 @@ USAGE $ apollo COMMAND ... ``` + # Commands -* [`apollo codegen:generate [OUTPUT]`](#apollo-codegengenerate-output) -* [`apollo help [COMMAND]`](#apollo-help-command) -* [`apollo queries:check`](#apollo-queriescheck) -* [`apollo schema:check`](#apollo-schemacheck) -* [`apollo schema:download OUTPUT`](#apollo-schemadownload-output) -* [`apollo schema:publish`](#apollo-schemapublish) + +- [`apollo codegen:generate [OUTPUT]`](#apollo-codegengenerate-output) +- [`apollo help [COMMAND]`](#apollo-help-command) +- [`apollo queries:check`](#apollo-queriescheck) +- [`apollo schema:check`](#apollo-schemacheck) +- [`apollo schema:download OUTPUT`](#apollo-schemadownload-output) +- [`apollo schema:publish`](#apollo-schemapublish) ## `apollo codegen:generate [OUTPUT]` @@ -51,11 +55,11 @@ ARGUMENTS OUTPUT Directory to which generated files will be written. - For TypeScript/Flow generators, this specifies a directory relative to each source file by default. - - For TypeScript/Flow generators with the "outputFlat" flag is set, and for the Swift generator, this specifies a + - For TypeScript/Flow generators with the "outputFlat" flag is set, and for the Swift generator, this specifies a file or directory (absolute or relative to the current working directory) to which: - a file will be written for each query (if "output" is a directory) - all generated types will be written - - For all other types, this defines a file (absolute or relative to the current working directory) to which all + - For all other types, this defines a file (absolute or relative to the current working directory) to which all generated types are written. OPTIONS @@ -226,7 +230,7 @@ Let's take a look at a basic configuration file (`package.json` style): "engineKey": "my-engine-key" // use this key when connecting to Apollo Engine } }, - "queries": [ + "queries": [ // optional if you only have one schema { "schema": "myPrimaryBackend", // reference the previously defined schema "includes": [ "**/*.tsx" ], // load queries from .tsx files @@ -250,7 +254,7 @@ module.exports = { engineKey: "my-engine-key" // use this key when connecting to Apollo Engine } }, - queries: [ + queries: [ // optional if you only have one schema { schema: "myPrimaryBackend", // reference the previously defined schema includes: [ "**/*.tsx" ], // load queries from .tsx files diff --git a/packages/apollo-vscode/README.md b/packages/apollo-vscode/README.md new file mode 100644 index 0000000000..7d84e68df8 --- /dev/null +++ b/packages/apollo-vscode/README.md @@ -0,0 +1,61 @@ +# Apollo VSCode + +An all-in-one tooling experience for developing apps with Apollo + +- Get instant feedback and intelligent autocomplete as you write queries +- Run queries against your GraphQL server without leaving your editor +- View performance statistics next to your query definitions + +![Code completing queries](images/variable-argument-completion.gif) +![Running a query with variables](images/query-with-vars.gif) +![Viewing Engine statistics](images/engine-stats.png) + +## Features + +- Loads your GraphQL schemas and queries automatically from an [Apollo Config](https://github.com/apollographql/apollo-cli/blob/master/packages/apollo-cli/README.md#configuration) file +- Adds syntax highlighting for GraphQL files and `gql` templates inside JavaScript files +- Code-completes fields, arguments, types, and variables in your queries +- Lets you run queries, mutations, and subscriptions within the IDE with code-completion for variables +- Displays performance statistics from [Apollo Engine](https://www.apollographql.com/engine) inline with your queries +- Jump-to-definition for fragments and schema types +- Detects fragment references and shows them next to definitions + +## How to get it? + +Open up VS Code and search for the extension "Apollo". + +## How to get it set up? + +The extension searches for [Apollo Config](https://github.com/apollographql/apollo-cli/blob/master/packages/apollo-cli/README.md#configuration) definitions in `package.json` or `apollo.config.js` files. Apollo Config can be set up to pull a schema from an introspection or from a [published version on Apollo Engine](https://www.apollographql.com/docs/engine/features/schema-history.html). To run queries in your editor, the `endpoint` key must be set to the GraphQL endpoint to run queries against. To enable performance statistics, make sure to specify your Engine API key with the `engineKey` value. + +```js +// package.json + +{ + ..., + "apollo": { + "schemas": { + "mainSchema": { + "endpoint": "http://localhost:4000/graphql", + "engineKey": "my-apollo-engine-api-key" + } + } + } +} +``` + +## How do we run queries? + +The extension enables query execution when an `endpoint` is specified for the schema you are targeting. When you hit `Run query/mutations/subscription`, the extension will open a menu for you to input variables if there are any defined or directly run the query otherwise. + +Queries and mutations are executed the same network interface as Apollo Client. Subscriptions are configured to run against the `subscriptions` key in your schema or your endpoint with `http` replaced by `ws` if the key is not defined. + +## Troubleshooting + +### Extension not starting correctly? Check the logs! + +If you're having trouble with the extension not launching or not detecting your configuration files, you can check the language server logs. + +1. Open the VS Code output tab by running the command "View: Toggle Output" +2. Switch the output view to "Apollo GraphQL" +3. Check the logs and report any bugs you find! diff --git a/packages/apollo-vscode/images/engine-stats.png b/packages/apollo-vscode/images/engine-stats.png new file mode 100644 index 0000000000..e504cd362a Binary files /dev/null and b/packages/apollo-vscode/images/engine-stats.png differ diff --git a/packages/apollo-vscode/images/icon-apollo-teal-400x400.png b/packages/apollo-vscode/images/icon-apollo-teal-400x400.png new file mode 100644 index 0000000000..8fc113d3f3 Binary files /dev/null and b/packages/apollo-vscode/images/icon-apollo-teal-400x400.png differ diff --git a/packages/apollo-vscode/images/query-with-vars.gif b/packages/apollo-vscode/images/query-with-vars.gif new file mode 100644 index 0000000000..c367c52c9a Binary files /dev/null and b/packages/apollo-vscode/images/query-with-vars.gif differ diff --git a/packages/apollo-vscode/images/variable-argument-completion.gif b/packages/apollo-vscode/images/variable-argument-completion.gif new file mode 100644 index 0000000000..84585694b9 Binary files /dev/null and b/packages/apollo-vscode/images/variable-argument-completion.gif differ diff --git a/packages/apollo-vscode/package.json b/packages/apollo-vscode/package.json index fb7b463350..b25af1c5e0 100644 --- a/packages/apollo-vscode/package.json +++ b/packages/apollo-vscode/package.json @@ -1,5 +1,6 @@ { "name": "apollo-vscode", + "displayName": "Apollo GraphQL", "description": "A VS Code extension for Apollo GraphQL projects", "private": true, "author": "Apollo GraphQL", @@ -17,6 +18,7 @@ "Programming Languages", "Linters" ], + "icon": "images/icon-apollo-teal-400x400.png", "activationEvents": [ "workspaceContains:**/apollo.config.js", "workspaceContains:**/package.json" @@ -64,7 +66,7 @@ "build": "tsc -p .", "watch": "tsc -w -p ./", "update-vscode": "node ./node_modules/vscode/bin/install", - "packageExtension": "npm run build && ./node_modules/vsce/out/vsce package" + "packageExtension": "npm run build && ./node_modules/vsce/out/vsce package --baseContentUrl https://raw.githubusercontent.com/apollographql/apollo-cli/master/packages/apollo-vscode" }, "dependencies": { "vscode": "^1.1.17", diff --git a/packages/apollo-vscode/src/extension.ts b/packages/apollo-vscode/src/extension.ts index 6aa4ba5415..42f0d2596a 100644 --- a/packages/apollo-vscode/src/extension.ts +++ b/packages/apollo-vscode/src/extension.ts @@ -1,285 +1,285 @@ -import * as path from "path"; - -import { workspace, ExtensionContext, WebviewPanel, Uri } from "vscode"; -import * as vscode from "vscode"; -import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind -} from "vscode-languageclient"; - -function sideViewColumn() { - if (!vscode.window.activeTextEditor) { - return vscode.ViewColumn.One; - } - - switch (vscode.window.activeTextEditor.viewColumn) { - case vscode.ViewColumn.One: - return vscode.ViewColumn.Two; - case vscode.ViewColumn.Two: - return vscode.ViewColumn.Three; - default: - return vscode.window.activeTextEditor.viewColumn!; - } -} - -export function activate(context: ExtensionContext) { - const serverModule = context.asAbsolutePath(path.join("server", "server.js")); - const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; - - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { - module: serverModule, - transport: TransportKind.ipc, - options: debugOptions - } - }; - - const clientOptions: LanguageClientOptions = { - documentSelector: [ - "graphql", - "javascript", - "typescript", - "javascriptreact", - "typescriptreact" - ], - synchronize: { - fileEvents: [ - workspace.createFileSystemWatcher("**/apollo.config.js"), - workspace.createFileSystemWatcher("**/package.json"), - workspace.createFileSystemWatcher("**/*.{graphql,js,ts,jsx,tsx}") - ] - } - }; - - let currentPanel: WebviewPanel | undefined = undefined; - let currentCancellationID: number | undefined = undefined; - let currentMessageHandler: ((msg: any) => void) | undefined = undefined; - - const client = new LanguageClient( - "apollographql", - "Apollo GraphQL", - serverOptions, - clientOptions - ); - client.registerProposedFeatures(); - context.subscriptions.push(client.start()); - - const getApolloPanel = () => { - if (currentPanel) { - if (!currentPanel.visible) { - // If we already have a panel, show it in the target column - currentPanel.reveal(sideViewColumn()); - } - - return currentPanel; - } else { - // Otherwise, create a new panel - currentPanel = vscode.window.createWebviewPanel( - "apolloPanel", - "", - sideViewColumn(), - { - enableScripts: true, - localResourceRoots: [ - vscode.Uri.file(path.join(context.extensionPath, "webview-content")) - ] - } - ); - - // Reset when the current panel is closed - currentPanel.onDidDispose( - () => { - currentPanel = undefined; - - if (currentCancellationID) { - client.sendNotification("apollographql/cancelQuery", { - cancellationID: currentCancellationID - }); - - currentCancellationID = undefined; - } - }, - null, - context.subscriptions - ); - - currentPanel!.webview.onDidReceiveMessage( - message => { - if (currentMessageHandler) { - currentMessageHandler(message); - } - }, - undefined, - context.subscriptions - ); - - currentPanel!.onDidDispose(() => { - currentMessageHandler = undefined; - }); - - return currentPanel; - } - }; - - client.onReady().then(() => { - client.onNotification( - "apollographql/requestVariables", - ({ query, endpoint, headers, requestedVariables, schema }) => { - getApolloPanel().title = "GraphQL Query Variables"; - - if (currentCancellationID) { - client.sendNotification("apollographql/cancelQuery", { - cancellationID: currentCancellationID - }); - - currentCancellationID = undefined; - } - - currentMessageHandler = message => { - switch (message.type) { - case "started": - currentPanel!.webview.postMessage({ - type: "setMode", - content: { - type: "VariablesInput", - requestedVariables: requestedVariables, - schema: schema - } - }); - break; - - case "variables": - client.sendNotification("apollographql/runQueryWithVariables", { - query, - endpoint, - headers, - variables: message.content - }); - - currentMessageHandler = undefined; - break; - } - }; - - const mediaPath = - vscode.Uri.file(path.join(context.extensionPath, "webview-content")) - .with({ - scheme: "vscode-resource" - }) - .toString() + "/"; - - currentMessageHandler({ type: "started" }); - currentPanel!.webview.html = ` - - -
- - - - - `; - } - ); - - client.onNotification( - "apollographql/queryResult", - ({ result, cancellationID }) => { - getApolloPanel().title = "GraphQL Query Result"; - - if (currentCancellationID !== cancellationID) { - if (currentCancellationID) { - client.sendNotification("apollographql/cancelQuery", { - cancellationID: currentCancellationID - }); - } - - currentCancellationID = cancellationID; - } - - currentMessageHandler = message => { - switch (message.type) { - case "started": - currentPanel!.webview.postMessage({ - type: "setMode", - content: { - type: "ResultViewer", - result - } - }); - - currentMessageHandler = undefined; - - break; - } - }; - - const mediaPath = - vscode.Uri.file(path.join(context.extensionPath, "webview-content")) - .with({ - scheme: "vscode-resource" - }) - .toString() + "/"; - - currentMessageHandler({ type: "started" }); - currentPanel!.webview.html = ` - - -
- - - - - `; - } - ); - - const engineDecoration = vscode.window.createTextEditorDecorationType({}); - let latestDecs: any[] | undefined = undefined; - - const updateDecorations = () => { - if (vscode.window.activeTextEditor && latestDecs) { - const editor = vscode.window.activeTextEditor!; - const decorations: vscode.DecorationOptions[] = latestDecs - .filter( - d => - d.document === - vscode.window.activeTextEditor!.document.uri.toString() - ) - .map(dec => { - return { - range: editor.document.lineAt(dec.range.start.line).range, - renderOptions: { - after: { - contentText: `# ${dec.message}`, - textDecoration: "none; padding-left: 15px; opacity: 0.5" - } - } - }; - }); - - vscode.window.activeTextEditor!.setDecorations( - engineDecoration, - decorations - ); - } - }; - - client.onNotification("apollographql/engineDecorations", (...decs) => { - latestDecs = decs; - updateDecorations(); - }); - - vscode.window.onDidChangeActiveTextEditor(() => { - updateDecorations(); - }); - - vscode.workspace.registerTextDocumentContentProvider("graphql-schema", { - provideTextDocumentContent(uri: Uri) { - return uri.query; - } - }); - }); -} +import * as path from "path"; + +import { workspace, ExtensionContext, WebviewPanel, Uri } from "vscode"; +import * as vscode from "vscode"; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind +} from "vscode-languageclient"; + +function sideViewColumn() { + if (!vscode.window.activeTextEditor) { + return vscode.ViewColumn.One; + } + + switch (vscode.window.activeTextEditor.viewColumn) { + case vscode.ViewColumn.One: + return vscode.ViewColumn.Two; + case vscode.ViewColumn.Two: + return vscode.ViewColumn.Three; + default: + return vscode.window.activeTextEditor.viewColumn!; + } +} + +export function activate(context: ExtensionContext) { + const serverModule = context.asAbsolutePath(path.join("server", "server.js")); + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions + } + }; + + const clientOptions: LanguageClientOptions = { + documentSelector: [ + "graphql", + "javascript", + "typescript", + "javascriptreact", + "typescriptreact" + ], + synchronize: { + fileEvents: [ + workspace.createFileSystemWatcher("**/apollo.config.js"), + workspace.createFileSystemWatcher("**/package.json"), + workspace.createFileSystemWatcher("**/*.{graphql,js,ts,jsx,tsx}") + ] + } + }; + + let currentPanel: WebviewPanel | undefined = undefined; + let currentCancellationID: number | undefined = undefined; + let currentMessageHandler: ((msg: any) => void) | undefined = undefined; + + const client = new LanguageClient( + "apollographql", + "Apollo GraphQL", + serverOptions, + clientOptions + ); + client.registerProposedFeatures(); + context.subscriptions.push(client.start()); + + const getApolloPanel = () => { + if (currentPanel) { + if (!currentPanel.visible) { + // If we already have a panel, show it in the target column + currentPanel.reveal(sideViewColumn()); + } + + return currentPanel; + } else { + // Otherwise, create a new panel + currentPanel = vscode.window.createWebviewPanel( + "apolloPanel", + "", + sideViewColumn(), + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.file(path.join(context.extensionPath, "webview-content")) + ] + } + ); + + // Reset when the current panel is closed + currentPanel.onDidDispose( + () => { + currentPanel = undefined; + + if (currentCancellationID) { + client.sendNotification("apollographql/cancelQuery", { + cancellationID: currentCancellationID + }); + + currentCancellationID = undefined; + } + }, + null, + context.subscriptions + ); + + currentPanel!.webview.onDidReceiveMessage( + message => { + if (currentMessageHandler) { + currentMessageHandler(message); + } + }, + undefined, + context.subscriptions + ); + + currentPanel!.onDidDispose(() => { + currentMessageHandler = undefined; + }); + + return currentPanel; + } + }; + + client.onReady().then(() => { + client.onNotification( + "apollographql/requestVariables", + ({ query, endpoint, headers, requestedVariables, schema }) => { + getApolloPanel().title = "GraphQL Query Variables"; + + if (currentCancellationID) { + client.sendNotification("apollographql/cancelQuery", { + cancellationID: currentCancellationID + }); + + currentCancellationID = undefined; + } + + currentMessageHandler = message => { + switch (message.type) { + case "started": + currentPanel!.webview.postMessage({ + type: "setMode", + content: { + type: "VariablesInput", + requestedVariables: requestedVariables, + schema: schema + } + }); + break; + + case "variables": + client.sendNotification("apollographql/runQueryWithVariables", { + query, + endpoint, + headers, + variables: message.content + }); + + currentMessageHandler = undefined; + break; + } + }; + + const mediaPath = + vscode.Uri.file(path.join(context.extensionPath, "webview-content")) + .with({ + scheme: "vscode-resource" + }) + .toString() + "/"; + + currentMessageHandler({ type: "started" }); + currentPanel!.webview.html = ` + + +
+ + + + + `; + } + ); + + client.onNotification( + "apollographql/queryResult", + ({ result, cancellationID }) => { + getApolloPanel().title = "GraphQL Query Result"; + + if (currentCancellationID !== cancellationID) { + if (currentCancellationID) { + client.sendNotification("apollographql/cancelQuery", { + cancellationID: currentCancellationID + }); + } + + currentCancellationID = cancellationID; + } + + currentMessageHandler = message => { + switch (message.type) { + case "started": + currentPanel!.webview.postMessage({ + type: "setMode", + content: { + type: "ResultViewer", + result + } + }); + + currentMessageHandler = undefined; + + break; + } + }; + + const mediaPath = + vscode.Uri.file(path.join(context.extensionPath, "webview-content")) + .with({ + scheme: "vscode-resource" + }) + .toString() + "/"; + + currentMessageHandler({ type: "started" }); + currentPanel!.webview.html = ` + + +
+ + + + + `; + } + ); + + const engineDecoration = vscode.window.createTextEditorDecorationType({}); + let latestDecs: any[] | undefined = undefined; + + const updateDecorations = () => { + if (vscode.window.activeTextEditor && latestDecs) { + const editor = vscode.window.activeTextEditor!; + const decorations: vscode.DecorationOptions[] = latestDecs + .filter( + d => + d.document === + vscode.window.activeTextEditor!.document.uri.toString() + ) + .map(dec => { + return { + range: editor.document.lineAt(dec.range.start.line).range, + renderOptions: { + after: { + contentText: `# ${dec.message}`, + textDecoration: "none; padding-left: 15px; opacity: 0.5" + } + } + }; + }); + + vscode.window.activeTextEditor!.setDecorations( + engineDecoration, + decorations + ); + } + }; + + client.onNotification("apollographql/engineDecorations", (...decs) => { + latestDecs = decs; + updateDecorations(); + }); + + vscode.window.onDidChangeActiveTextEditor(() => { + updateDecorations(); + }); + + vscode.workspace.registerTextDocumentContentProvider("graphql-schema", { + provideTextDocumentContent(uri: Uri) { + return uri.query; + } + }); + }); +}