From 9572c0b86afabcbec7b3f8002ed8b192055fbba1 Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Wed, 30 Oct 2019 17:59:35 +0200 Subject: [PATCH 1/3] added custom extensions docs --- CUSTOM_EXTENSION.md | 130 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 CUSTOM_EXTENSION.md diff --git a/CUSTOM_EXTENSION.md b/CUSTOM_EXTENSION.md new file mode 100644 index 000000000..e912cabff --- /dev/null +++ b/CUSTOM_EXTENSION.md @@ -0,0 +1,130 @@ +## Writing your own GraphQL-CLI Extension + +`graphql-cli` allow you to write your own plugin/extenion, and intergrate external tools and configuration, and run it from a single CLI. + +The current implementationo of `graphql-cli` is using [Commander](https://github.com/tj/commander.js#common-option-types-boolean-and-value) to manage it's CLI commands, and it exposes a ready-to-use `Commander` instance that you can extend and add logic to it. + +Plugins and extension are treated as NodeJS module by the `graphql-cli`, so it means you can use JavaScript/TypeScript/Any other super-set of JavaScript to write your extension. It means that you plugin will be loaded by it's name under `node_modules` - for example `graphql-cli my-custom-plugin ...`. + +`graphql-cli` also support `graphql-config`, so it can help you easily load your GraphQL schema, operations and configuration from a unified config file. + +> If you are wrapping an existing tool that has it's own CLI already, consider to expose a programtic API so it will be easier to consume. + +### TL;DR + +We have a ready-to-use boilerplate for that purpose, [you can find it here](https://github.com/dotansimha/graphql-cli-plugin-example). + +Also, inside this repo, under `packages/commands` you can find a set of plugins implementation you can use as reference. + +### Getting Started + +Start by creating a simple JavaScript/TypeScript project, according to your preference, and have your `index` file exporting a variable called `plugin`, structed as object, with `init` method. + +The `init` method will get trigged by the CLI host, and will pass the following to your method: + +- `cwd` - The current directory. +- `program` - A `commander` instance you can use to register your CLI commands. +- `loadConfig` - A method you can use to load a GraphQL schema or documents, based on `graphql-config`. +- `reportError` - Helper method that allow you to report errors back to the GraphQL CLI, and effect the exit code of the CLI host. It's useful if you are dealing with async code in your extension. + +It should be similar to this if you are using plain JavaScript: + +```js +module.exports = { + plugin: { + init: ({ cwd, program, loadConfig, reportError }) => { + // Your plugin code here, you can use "program" to register sub-commands. + } + } +}; +``` + +Or, with TypeScript: + +```ts +import { plugin } from '@test-graphql-cli/common'; + +export const plugin: CliPlugin = { + init({ cwd, program, loadConfig, reportError }) { + // Your plugin code here + } +}; +``` + +## Registering CLI sub-commands + +To register your CLI commands, use the `program` instance: + +```ts +program.command('my-plugin').action(async (cmd: string) => { + // do something +}); +``` + +Now, your plugin will be avaiable to use with the following command: `graphql my-plugin`. + +You can also add custom validations, flags, default values and much more with Commander. [You can read the documentation here](https://github.com/tj/commander.js#common-option-types-boolean-and-value). + +## Testing your plugin locally + +To test your plugin locally, install `graphql-cli` in your project as a `devDependency`, and run the following command: + +``` +graphql ./src/index.js +``` + +If you registerd sub-commands, you should be able to run those this way: + +``` +graphql ./src/index.js do-something +``` + +> The path should point to the entry point of your script, and if you are using TypeScript - point to the compile file. + +## Loading GraphQL Schema + +To easily load GraphQL schema, you can use `loadConfig` to get it from a `graphql-config` file: + +```ts +import { plugin } from '@test-graphql-cli/common'; + +export const plugin: CliPlugin = { + async init({ cwd, program, loadConfig, reportError }) { + const config = await loadConfig(); + const schema = await config.getSchema(); + } +}; +``` + +> You can also extend the `loadConfig` behavior by specifying custom loaders and extensions. + +If you are using `graphql-config` to define your configuration, and you wish to load your extenion config from it, do: + +```ts +type MyConfig = { ... }; + +const extensionConfig = await config.extension('my-plugin'); +``` + +## Error Handling + +If you wish to fail the execution of your plugin and report it back to GraphQL CLI host, you should use `reportError`: + +```ts +import { plugin } from '@test-graphql-cli/common'; + +export const plugin: CliPlugin = { + async init({ cwd, program, loadConfig, reportError }) { + try { + // do something risky + // or, throw: + + if (somethingIsMissing) { + return reportError(new Error(`Ooops, something is missing`)); + } + } catch (e) { + reportError(e); + } + } +}; +``` From 7401cbabdd9c23e08555413bf1aff32c97208fe8 Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Wed, 30 Oct 2019 17:59:41 +0200 Subject: [PATCH 2/3] fix subcommands issues --- packages/cli/src/cli.ts | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index cbef1bc99..7e63db813 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -4,9 +4,7 @@ import { LoadConfigOptions } from '@test-graphql-cli/common'; import { loadConfig } from 'graphql-config'; export async function cli(argv = process.argv): Promise { - try { - const rootCommand = argv[2]; if (!rootCommand || rootCommand === '') { @@ -33,30 +31,34 @@ export async function cli(argv = process.argv): Promise { cwd: process.cwd(), program, reportError, - loadConfig: (loadConfigOptions: LoadConfigOptions = {}) => loadConfig({ - rootDir: process.cwd(), - throwOnEmpty: false, - throwOnMissing: false, - ...loadConfigOptions, - }).then(c => { - const projectNames = Object.keys(c.projects); - if (projectName && !projectNames.includes(projectName)) { - throw new Error(` + loadConfig: (loadConfigOptions: LoadConfigOptions = {}) => + loadConfig({ + rootDir: process.cwd(), + throwOnEmpty: false, + throwOnMissing: false, + ...loadConfigOptions + }).then(c => { + const projectNames = Object.keys(c.projects); + if (projectName && !projectNames.includes(projectName)) { + throw new Error(` You don't have project ${projectName}. Available projects are ${projectNames.join(',')}. `); - } - if (!projectNames.includes('default') && projectNames.length > 0) { - throw new Error(` + } + if (!projectNames.includes('default') && projectNames.length > 0) { + throw new Error(` You don't have 'default' project so you need to specify a project name. Available projects are ${projectNames.join(',')}. - `) - } - projectName = 'default'; - return c.getProject(projectName); - }), + `); + } + projectName = 'default'; + return c.getProject(projectName); + }) }); + // Remove the root object before running, to allow develoeprs to write + // their own sub-commands. + argv.splice(2, 1); program.parse(argv); if (program.project) { @@ -66,7 +68,6 @@ export async function cli(argv = process.argv): Promise { if (program.require) { await import(program.require); } - } catch (e) { console.error(e); process.exit(1); From d7bdf79ee0ae2fbf43c9aa4e7edf5e314d80f04a Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Wed, 30 Oct 2019 18:05:01 +0200 Subject: [PATCH 3/3] docs fixes --- CONTRIBUTING.md | 6 +++++- README.md | 6 +++++- CUSTOM_EXTENSION.md => docs/CUSTOM_EXTENSION.md | 2 +- MIGRATION.md => docs/MIGRATION.md | 0 4 files changed, 11 insertions(+), 3 deletions(-) rename CUSTOM_EXTENSION.md => docs/CUSTOM_EXTENSION.md (94%) rename MIGRATION.md => docs/MIGRATION.md (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e81040eed..e9f5f7148 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,12 @@ # Contributing guide +We are using Yarn workspaces, so make sure you have the latest version of Yarn installed. + ## Building project +To build the entire monorepo, start by installing the dependencies by running `yarn` in the root directory, and then: + ```sh -yarn install yarn build ``` @@ -13,3 +16,4 @@ yarn build cd packages/cli npm link . ``` + diff --git a/README.md b/README.md index 5ab86f4e1..3219274e7 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The equivalent npm global install will also work. ## Migration from 3.x.x to 4.x.x -**Important: many aspects of GraphQL CLI syntax and structure have changed in 4.x.x.** Please check out the [Migration Guide](MIGRATION.md) to learn more. +**Important: many aspects of GraphQL CLI syntax and structure have changed in 4.x.x.** Please check out the [Migration Guide](./docs/MIGRATION.md) to learn more. ## Usage / Initialization @@ -126,6 +126,10 @@ extensions: More plugins are definitely welcome! Please check the existing ones to see how to use GraphQL Config and GraphQL CLI API. +## Writing you own plugin + +GraphQL CLI supports custom plugins, [you can find a tutorial and example here](./docs/CUSTOM_EXTENSION.md) + ## Help & Community [![Discord Chat](https://img.shields.io/discord/625400653321076807)](https://discord.gg/xud7bH9) Join our [Discord chat](https://discord.gg/xud7bH9) if you run into issues or have questions. We're excited to welcome you to the community! diff --git a/CUSTOM_EXTENSION.md b/docs/CUSTOM_EXTENSION.md similarity index 94% rename from CUSTOM_EXTENSION.md rename to docs/CUSTOM_EXTENSION.md index e912cabff..7bb49e46c 100644 --- a/CUSTOM_EXTENSION.md +++ b/docs/CUSTOM_EXTENSION.md @@ -2,7 +2,7 @@ `graphql-cli` allow you to write your own plugin/extenion, and intergrate external tools and configuration, and run it from a single CLI. -The current implementationo of `graphql-cli` is using [Commander](https://github.com/tj/commander.js#common-option-types-boolean-and-value) to manage it's CLI commands, and it exposes a ready-to-use `Commander` instance that you can extend and add logic to it. +The current implementation of `graphql-cli` is using [Commander](https://github.com/tj/commander.js#common-option-types-boolean-and-value) to manage it's CLI commands, and it exposes a ready-to-use `Commander` instance that you can extend and add logic to it. Plugins and extension are treated as NodeJS module by the `graphql-cli`, so it means you can use JavaScript/TypeScript/Any other super-set of JavaScript to write your extension. It means that you plugin will be loaded by it's name under `node_modules` - for example `graphql-cli my-custom-plugin ...`. diff --git a/MIGRATION.md b/docs/MIGRATION.md similarity index 100% rename from MIGRATION.md rename to docs/MIGRATION.md