From 27e2772adb30186ee59d47ee03782008877cbd9d Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Thu, 5 Aug 2021 09:58:20 -0500 Subject: [PATCH] Revert "chore: refactor rover-client in pursuit of structured output (#557)" This reverts commit cdd43c109b7d73c49bd1b2a2d9ca6a5292f30aaa. --- ARCHITECTURE.md | 90 +- Cargo.lock | 39 +- Cargo.toml | 6 +- crates/rover-client/.eslintrc.json | 60 - crates/rover-client/Cargo.toml | 11 +- crates/rover-client/package-lock.json | 5593 ----------------- crates/rover-client/package.json | 14 - crates/rover-client/src/blocking/client.rs | 29 +- crates/rover-client/src/blocking/mod.rs | 2 - .../src/blocking/studio_client.rs | 49 +- crates/rover-client/src/error.rs | 82 +- crates/rover-client/src/headers.rs | 61 + crates/rover-client/src/introspection/mod.rs | 2 + .../rover-client/src/introspection/schema.rs | 271 + crates/rover-client/src/lib.rs | 14 +- .../is_federated/is_federated_query.graphql | 7 - .../src/operations/config/is_federated/mod.rs | 5 - .../operations/config/is_federated/types.rs | 18 - .../src/operations/config/who_am_i/mod.rs | 5 - .../src/operations/config/who_am_i/types.rs | 31 - .../src/operations/graph/check/mod.rs | 5 - .../src/operations/graph/check/runner.rs | 61 - .../src/operations/graph/check/types.rs | 103 - .../graph/fetch/fetch_query.graphql | 11 - .../src/operations/graph/fetch/mod.rs | 5 - .../src/operations/graph/fetch/types.rs | 18 - .../src/operations/graph/introspect/mod.rs | 7 - .../src/operations/graph/introspect/runner.rs | 41 - .../src/operations/graph/introspect/types.rs | 22 - .../src/operations/graph/publish/mod.rs | 7 - .../src/operations/graph/publish/types.rs | 152 - .../src/operations/subgraph/check/mod.rs | 5 - .../src/operations/subgraph/check/runner.rs | 117 - .../src/operations/subgraph/check/types.rs | 84 - .../subgraph/delete/delete_mutation.graphql | 20 - .../src/operations/subgraph/delete/mod.rs | 5 - .../src/operations/subgraph/delete/runner.rs | 164 - .../src/operations/subgraph/delete/types.rs | 42 - .../src/operations/subgraph/fetch/mod.rs | 5 - .../src/operations/subgraph/fetch/types.rs | 23 - .../introspect/introspect_query.graphql | 6 - .../introspect/introspect_schema.graphql | 16 - .../src/operations/subgraph/introspect/mod.rs | 5 - .../operations/subgraph/introspect/types.rs | 22 - .../src/operations/subgraph/list/mod.rs | 5 - .../src/operations/subgraph/list/types.rs | 48 - .../src/operations/subgraph/publish/mod.rs | 5 - .../subgraph/publish/publish_mutation.graphql | 30 - .../src/operations/subgraph/publish/types.rs | 63 - .../src/operations/supergraph/fetch/mod.rs | 5 - .../src/operations/supergraph/fetch/types.rs | 18 - .../src/query/config/is_federated.graphql | 7 + .../config/is_federated.rs} | 20 +- .../src/{operations => query}/config/mod.rs | 2 +- .../config/whoami.graphql} | 2 +- .../runner.rs => query/config/whoami.rs} | 46 +- .../graph/check.graphql} | 16 +- crates/rover-client/src/query/graph/check.rs | 76 + .../src/query/graph/fetch.graphql | 11 + .../fetch/runner.rs => query/graph/fetch.rs} | 71 +- .../src/query/graph/introspect.rs | 129 + .../graph}/introspect_query.graphql | 2 +- .../graph}/introspect_schema.graphql | 22 +- .../src/{operations => query}/graph/mod.rs | 0 .../graph/publish.graphql} | 14 +- .../runner.rs => query/graph/publish.rs} | 162 +- .../src/{operations => query}/mod.rs | 0 .../subgraph/check.graphql} | 19 +- .../rover-client/src/query/subgraph/check.rs | 119 + .../src/query/subgraph/delete.graphql | 19 + .../rover-client/src/query/subgraph/delete.rs | 156 + .../subgraph/fetch.graphql} | 4 +- .../runner.rs => query/subgraph/fetch.rs} | 99 +- .../subgraph/introspect.rs} | 28 +- .../query/subgraph/introspect_query.graphql | 5 + .../query/subgraph/introspect_schema.graphql | 11 + .../subgraph/list.graphql} | 4 +- .../list/runner.rs => query/subgraph/list.rs} | 95 +- .../src/{operations => query}/subgraph/mod.rs | 0 .../src/query/subgraph/publish.graphql | 29 + .../runner.rs => query/subgraph/publish.rs} | 142 +- .../supergraph/fetch.graphql} | 7 +- .../runner.rs => query/supergraph/fetch.rs} | 153 +- .../{operations => query}/supergraph/mod.rs | 0 crates/rover-client/src/releases.rs | 38 +- .../rover-client/src/shared/build_errors.rs | 174 - .../rover-client/src/shared/check_response.rs | 186 - .../rover-client/src/shared/fetch_response.rs | 21 - crates/rover-client/src/shared/graph_ref.rs | 105 - crates/rover-client/src/shared/mod.rs | 13 - .../fixtures/interfaces.json | 0 .../introspect => tests}/fixtures/simple.json | 0 .../introspect => tests}/fixtures/swapi.json | 0 .../graph/introspect => tests}/schema.rs | 376 +- crates/sdl-encoder/src/schema_def.rs | 2 +- docs/source/errors.md | 14 - installers/binstall/src/error.rs | 7 +- installers/binstall/src/install.rs | 12 +- src/bin/rover.rs | 65 +- src/cli.rs | 172 +- src/command/config/auth.rs | 6 +- src/command/config/clear.rs | 6 +- src/command/config/delete.rs | 6 +- src/command/config/list.rs | 6 +- src/command/config/mod.rs | 4 +- src/command/config/whoami.rs | 16 +- src/command/docs/list.rs | 6 +- src/command/docs/mod.rs | 4 +- src/command/docs/open.rs | 6 +- src/command/docs/shortlinks.rs | 6 +- src/command/explain.rs | 6 +- src/command/graph/check.rs | 97 +- src/command/graph/fetch.rs | 20 +- src/command/graph/introspect.rs | 15 +- src/command/graph/mod.rs | 8 +- src/command/graph/publish.rs | 63 +- src/command/info.rs | 6 +- src/command/install/mod.rs | 6 +- src/command/mod.rs | 5 +- src/command/output.rs | 1029 +-- src/command/subgraph/check.rs | 129 +- src/command/subgraph/delete.rs | 135 +- src/command/subgraph/fetch.rs | 21 +- src/command/subgraph/introspect.rs | 13 +- src/command/subgraph/list.rs | 17 +- src/command/subgraph/mod.rs | 10 +- src/command/subgraph/publish.rs | 117 +- src/command/supergraph/compose/do_compose.rs | 79 +- src/command/supergraph/compose/no_compose.rs | 4 +- src/command/supergraph/fetch.rs | 19 +- src/command/supergraph/mod.rs | 4 +- src/command/update/check.rs | 6 +- src/command/update/mod.rs | 4 +- src/error/metadata/code.rs | 8 +- src/error/metadata/codes/E028.md | 2 +- src/error/metadata/codes/E029.md | 5 - src/error/metadata/codes/E030.md | 1 - src/error/metadata/mod.rs | 67 +- src/error/metadata/suggestion.rs | 39 +- src/error/mod.rs | 74 +- .../shared/git_context.rs => src/utils/git.rs | 213 +- src/utils/mod.rs | 1 + src/utils/parsers.rs | 132 +- src/utils/stringify.rs | 18 +- src/utils/table.rs | 11 +- src/utils/version.rs | 18 +- xtask/src/commands/lint.rs | 4 +- xtask/src/commands/prep/mod.rs | 1 - xtask/src/tools/npm.rs | 70 +- xtask/src/utils.rs | 2 +- 150 files changed, 2488 insertions(+), 10221 deletions(-) delete mode 100644 crates/rover-client/.eslintrc.json delete mode 100644 crates/rover-client/package-lock.json delete mode 100644 crates/rover-client/package.json create mode 100644 crates/rover-client/src/headers.rs create mode 100644 crates/rover-client/src/introspection/mod.rs create mode 100644 crates/rover-client/src/introspection/schema.rs delete mode 100644 crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql delete mode 100644 crates/rover-client/src/operations/config/is_federated/mod.rs delete mode 100644 crates/rover-client/src/operations/config/is_federated/types.rs delete mode 100644 crates/rover-client/src/operations/config/who_am_i/mod.rs delete mode 100644 crates/rover-client/src/operations/config/who_am_i/types.rs delete mode 100644 crates/rover-client/src/operations/graph/check/mod.rs delete mode 100644 crates/rover-client/src/operations/graph/check/runner.rs delete mode 100644 crates/rover-client/src/operations/graph/check/types.rs delete mode 100644 crates/rover-client/src/operations/graph/fetch/fetch_query.graphql delete mode 100644 crates/rover-client/src/operations/graph/fetch/mod.rs delete mode 100644 crates/rover-client/src/operations/graph/fetch/types.rs delete mode 100644 crates/rover-client/src/operations/graph/introspect/mod.rs delete mode 100644 crates/rover-client/src/operations/graph/introspect/runner.rs delete mode 100644 crates/rover-client/src/operations/graph/introspect/types.rs delete mode 100644 crates/rover-client/src/operations/graph/publish/mod.rs delete mode 100644 crates/rover-client/src/operations/graph/publish/types.rs delete mode 100644 crates/rover-client/src/operations/subgraph/check/mod.rs delete mode 100644 crates/rover-client/src/operations/subgraph/check/runner.rs delete mode 100644 crates/rover-client/src/operations/subgraph/check/types.rs delete mode 100644 crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql delete mode 100644 crates/rover-client/src/operations/subgraph/delete/mod.rs delete mode 100644 crates/rover-client/src/operations/subgraph/delete/runner.rs delete mode 100644 crates/rover-client/src/operations/subgraph/delete/types.rs delete mode 100644 crates/rover-client/src/operations/subgraph/fetch/mod.rs delete mode 100644 crates/rover-client/src/operations/subgraph/fetch/types.rs delete mode 100644 crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql delete mode 100644 crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql delete mode 100644 crates/rover-client/src/operations/subgraph/introspect/mod.rs delete mode 100644 crates/rover-client/src/operations/subgraph/introspect/types.rs delete mode 100644 crates/rover-client/src/operations/subgraph/list/mod.rs delete mode 100644 crates/rover-client/src/operations/subgraph/list/types.rs delete mode 100644 crates/rover-client/src/operations/subgraph/publish/mod.rs delete mode 100644 crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql delete mode 100644 crates/rover-client/src/operations/subgraph/publish/types.rs delete mode 100644 crates/rover-client/src/operations/supergraph/fetch/mod.rs delete mode 100644 crates/rover-client/src/operations/supergraph/fetch/types.rs create mode 100644 crates/rover-client/src/query/config/is_federated.graphql rename crates/rover-client/src/{operations/config/is_federated/runner.rs => query/config/is_federated.rs} (71%) rename crates/rover-client/src/{operations => query}/config/mod.rs (83%) rename crates/rover-client/src/{operations/config/who_am_i/who_am_i_query.graphql => query/config/whoami.graphql} (80%) rename crates/rover-client/src/{operations/config/who_am_i/runner.rs => query/config/whoami.rs} (76%) rename crates/rover-client/src/{operations/graph/check/check_mutation.graphql => query/graph/check.graphql} (61%) create mode 100644 crates/rover-client/src/query/graph/check.rs create mode 100644 crates/rover-client/src/query/graph/fetch.graphql rename crates/rover-client/src/{operations/graph/fetch/runner.rs => query/graph/fetch.rs} (58%) create mode 100644 crates/rover-client/src/query/graph/introspect.rs rename crates/rover-client/src/{operations/graph/introspect => query/graph}/introspect_query.graphql (97%) rename crates/rover-client/src/{operations/graph/introspect => query/graph}/introspect_schema.graphql (76%) rename crates/rover-client/src/{operations => query}/graph/mod.rs (100%) rename crates/rover-client/src/{operations/graph/publish/publish_mutation.graphql => query/graph/publish.graphql} (70%) rename crates/rover-client/src/{operations/graph/publish/runner.rs => query/graph/publish.rs} (58%) rename crates/rover-client/src/{operations => query}/mod.rs (100%) rename crates/rover-client/src/{operations/subgraph/check/check_mutation.graphql => query/subgraph/check.graphql} (63%) create mode 100644 crates/rover-client/src/query/subgraph/check.rs create mode 100644 crates/rover-client/src/query/subgraph/delete.graphql create mode 100644 crates/rover-client/src/query/subgraph/delete.rs rename crates/rover-client/src/{operations/subgraph/fetch/fetch_query.graphql => query/subgraph/fetch.graphql} (72%) rename crates/rover-client/src/{operations/subgraph/fetch/runner.rs => query/subgraph/fetch.rs} (68%) rename crates/rover-client/src/{operations/subgraph/introspect/runner.rs => query/subgraph/introspect.rs} (57%) create mode 100644 crates/rover-client/src/query/subgraph/introspect_query.graphql create mode 100644 crates/rover-client/src/query/subgraph/introspect_schema.graphql rename crates/rover-client/src/{operations/subgraph/list/list_query.graphql => query/subgraph/list.graphql} (72%) rename crates/rover-client/src/{operations/subgraph/list/runner.rs => query/subgraph/list.rs} (66%) rename crates/rover-client/src/{operations => query}/subgraph/mod.rs (100%) create mode 100644 crates/rover-client/src/query/subgraph/publish.graphql rename crates/rover-client/src/{operations/subgraph/publish/runner.rs => query/subgraph/publish.rs} (51%) rename crates/rover-client/src/{operations/supergraph/fetch/fetch_query.graphql => query/supergraph/fetch.graphql} (71%) rename crates/rover-client/src/{operations/supergraph/fetch/runner.rs => query/supergraph/fetch.rs} (65%) rename crates/rover-client/src/{operations => query}/supergraph/mod.rs (100%) delete mode 100644 crates/rover-client/src/shared/build_errors.rs delete mode 100644 crates/rover-client/src/shared/check_response.rs delete mode 100644 crates/rover-client/src/shared/fetch_response.rs delete mode 100644 crates/rover-client/src/shared/graph_ref.rs delete mode 100644 crates/rover-client/src/shared/mod.rs rename crates/rover-client/{src/operations/graph/introspect => tests}/fixtures/interfaces.json (100%) rename crates/rover-client/{src/operations/graph/introspect => tests}/fixtures/simple.json (100%) rename crates/rover-client/{src/operations/graph/introspect => tests}/fixtures/swapi.json (100%) rename crates/rover-client/{src/operations/graph/introspect => tests}/schema.rs (78%) delete mode 100644 src/error/metadata/codes/E029.md delete mode 100644 src/error/metadata/codes/E030.md rename crates/rover-client/src/shared/git_context.rs => src/utils/git.rs (55%) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1b117879a..2932cfe2b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -116,8 +116,8 @@ A minimal command in Rover would be laid out exactly like this: pub struct MyNewCommand { } impl MyNewCommand { - pub fn run(&self) -> Result { - Ok(RoverOutput::None) + pub fn run(&self) -> Result { + Ok(RoverStdout::None) } } ``` @@ -128,16 +128,16 @@ For our `graph hello` command, we'll add a new `hello.rs` file under `src/comman use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Hello { } impl Hello { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { eprintln!("Hello, world!"); - Ok(RoverOutput::None) + Ok(RoverStdout::None) } } ``` @@ -195,7 +195,7 @@ To add these to our new `graph hello` command, we can copy and paste the field f pub struct Hello { /// @ of graph in Apollo Studio to publish to. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF"))] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef @@ -206,6 +206,12 @@ pub struct Hello { } ``` +We'll have to also add some import statements at the top of our file to support parsing this new argument: + +```rust +use crate::utils::parsers::{parse_graph_ref, GraphRef}; +``` + Now if we run the command again, it will complain if we don't provide a graph ref: ```console @@ -222,13 +228,13 @@ For more information try --help ##### Setting up a command to work with `rover-client` -Most of Rover's commands make requests to Apollo Studio's API, or to another GraphQL API. Rather than handling the request logic in the repository's main package, Rover is structured so that this logic lives in `crates/rover-client`. This is helpful for separation of concerns and testing. +Most of Rover's commands make requests to Apollo Studio's API. Rather than handling the request logic in the repository's main package, Rover is structured so that this logic lives in `crates/rover-client`. This is helpful for separation of concerns and testing. To access functionality from `rover-client` in our `rover graph hello` command, we'll need to pass down a client from the entry to our command in `src/command/graph/mod.rs`. You can do this by changing the `Command::Hello(command) => command.run(),` line to `Command::Hello(command) => command.run(client_config),`. -Then you'll need to change `Hello::run` to accept a `client_config: StudioClientConfig` parameter in `src/command/graph/hello.rs`, and add a `use crate::utils::client::StudioClientConfig` import statement. Then, at the top of the run function, you can create a `StudioClient` by adding `let client = client_config.get_authenticated_client(&self.profile_name)?;`. You can see examples of this in the other commands. +Then you'll need to change `Hello::run` to accept a `client_config: StudioClientConfig` parameter in `src/command/graph/hello.rs`, and add a `use crate::utils::client::StudioClientConfig` import statement. Then, at the top of the run function, you can create a `StudioClient` by adding `let client = client_config.get_client(&self.profile_name)?;`. You can see examples of this in the other commands. ##### Auto-generated help command @@ -265,19 +271,19 @@ Whenever you create a new command, make sure to add `#[serde(skip_serializing)]` ##### Adding a query to Apollo Studio -The only piece of the `rover-client` crate that we need to be concerned with for now is the `src/operations` directory. This is where all the queries to Apollo Studio live. This directory is roughly organized by the command names as well, but there might be some queries in these directories that are used by multiple commands. +The only piece of the `rover-client` crate that we need to be concerned with for now is the `src/query` directory. This is where all the queries to Apollo Studio live. This directory is roughly organized by the command names as well, but there might be some queries in these directories that are used by multiple commands. -You can see in the `src/operations/graph` directory a number of `.rs` files paired with `.graphql` files. The `.graphql` files are the files where the GraphQL operations live, and the matching `.rs` files contain the logic needed to execute those operations. +You can see in the `src/query/graph` directory a number of `.rs` files paired with `.graphql` files. The `.graphql` files are the files where the GraphQL operations live, and the matching `.rs` files contain the logic needed to execute those operations. ##### Writing a GraphQL operation For our basic `graph hello` command, we're going to make a request to Apollo Studio that inquires about the existence of a particular graph, and nothing else. For this, we can use the `Query.service` field. -Create a `hello_query.graphql` file in `crates/rover-client/src/operations/graph` and paste the following into it: +Create a `hello.graphql` file in `crates/rover-client/src/query/graph` and paste the following into it: ```graphql -query GraphHello($graph_id: ID!) { - service(id: $graph_id) { +query GraphHello($graphId: ID!) { + service(id: $graphId) { deletedAt } } @@ -289,19 +295,17 @@ This basic GraphQL operation uses a graph's unique ID (which we get from the `Gr This project uses [graphql-client](https://docs.rs/graphql_client/latest/graphql_client/) to generate types for each raw `.graphql` query that we write. -First, create an empty directory at `crates/rover-client/src/operations/graph/hello`, and then in that directory, create a `mod.rs` file to initialize the module. +First, create an empty file at `crates/rover-client/src/query/graph/hello.rs`. -To start compiling this file, we need to export the module in `crates/rover-client/src/operations/graph/mod.rs`: +To start compiling this file, we need to export the module in `crates/rover-client/src/query/graph/mod.rs`: ```rust ... -/// "graph hello" command execution +/// "Graph hello" command execution pub mod hello; ``` -Back in our `hello` module, we'll create a `runner.rs`, and add `mod runner` to our `mod.rs` file. - -Then, in `runner.rs`, import the following types: +Back in `hello.rs`, we'll import the following types: ```rust use crate::blocking::StudioClient; @@ -309,14 +313,14 @@ use crate::RoverClientError; use graphql_client::*; ``` -Then, we'll create a new struct that will have auto-generated types for the `hello_query.graphql` file that we created earlier: +Then, we'll create a new struct that will have auto-generated types for the `hello.graphql` file that we created earlier: ```rust #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/graph/hello/hello_query.graphql", + query_path = "src/query/graph/hello.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" @@ -348,7 +352,7 @@ Before we go any further, lets make sure everything is set up properly. We're go It should look something like this (you should make sure you are following the style of other commands when creating new ones): ```rust -pub fn run(&self, client_config: StudioClientConfig) -> Result { +pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -362,10 +366,7 @@ pub fn run(&self, client_config: StudioClientConfig) -> Result { }, &client, )?; - println!("{:?}", deleted_at); - - // TODO: Add a new output type! - Ok(RoverOutput::None) + Ok(RoverStdout::PlainText(deleted_at)) } ``` @@ -394,40 +395,19 @@ fn build_response( } ``` -This should get you to the point where you can run `rover graph hello ` and see if and when the last graph was deleted. From here, you should be able to follow the examples of other commands to write out tests for the `build_response` function. - -##### Clean up the API +This should get you to the point where you can run `rover graph hello ` and see if and when the last graph was deleted. From here, you should be able to follow the examples of other commands to write out tests for the `build_response` function. This is left as an exercise for the reader. -Unfortunately this is not the cleanest API and doesn't match the pattern set by the rest of the commands. Each `rover-client` operation has an input type and an output type, along with a `run` function that takes in a `reqwest::blocking::Client`. +##### `RoverStdout` -You'll want to define all of the types scoped to this command in `types.rs`, and re-export them from the top level `hello` module, and nothing else. +Now that you can actually execute the `hello::run` query and return its result, you should create a new variant of `RoverStdout` in `src/command/output.rs` that is not `PlainText`. Your new variant should print the descriptor using the `print_descriptor` function, and print the raw content using `print_content`. -##### `RoverOutput` - -Now that you can actually execute the `hello::run` query and return its result, you should create a new variant of `RoverOutput` in `src/command/output.rs` that is not `None`. Your new variant should print the descriptor using the `print_descriptor` function, and print the raw content using `print_content`. - -To do so, change the line `Ok(RoverOutput::None)` to `Ok(RoverOutput::DeletedAt(deleted_at))`, add a new `DeletedAt(String)` variant to `RoverOutput`, and then match on it in `pub fn print(&self)` and `pub fn get_json(&self)`: +To do so, change the line `Ok(RoverStdout::PlainText(deleted_at))` to `Ok(RoverStdout::DeletedAt(deleted_at))`, add a new `DeletedAt(String)` variant to `RoverStdout`, and then match on it in `pub fn print(&self)`: ```rust -pub fn print(&self) { - match self { - ... - RoverOutput::DeletedAt(timestamp) => { - print_descriptor("Deleted At"); - print_content(×tamp); - } - ... - } -} - -pub fn get_json(&self) -> Value { - match self { - ... - RoverOutput::DeletedAt(timestamp) => { - json!({ "deleted_at": timestamp.to_string() }) - } - ... - } +... +RoverStdout::DeletedAt(timestamp) => { + print_descriptor("Deleted At"); + print_content(×tamp); } ``` diff --git a/Cargo.lock b/Cargo.lock index d527bd491..c28d9b8aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,16 +83,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" -[[package]] -name = "assert-json-diff" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "assert_cmd" version = "1.0.7" @@ -331,7 +321,6 @@ dependencies = [ "libc", "num-integer", "num-traits", - "serde", "time", "winapi", ] @@ -1118,9 +1107,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.10" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7728a72c4c7d72665fde02204bcbd93b247721025b222ef78606f14513e0fd03" +checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83" dependencies = [ "bytes", "futures-channel", @@ -1203,9 +1192,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ "cfg-if", ] @@ -1257,9 +1246,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.98" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" [[package]] name = "libgit2-sys" @@ -1926,7 +1915,6 @@ version = "0.2.0-beta.0" dependencies = [ "ansi_term 0.12.1", "anyhow", - "assert-json-diff", "assert_cmd", "assert_fs", "atty", @@ -1941,6 +1929,7 @@ dependencies = [ "harmonizer", "heck", "houston", + "humantime", "opener", "os_info", "predicates", @@ -1950,6 +1939,7 @@ dependencies = [ "robot-panic", "rover-client", "sdl-encoder", + "semver", "serde", "serde_json", "serde_yaml", @@ -1973,20 +1963,15 @@ version = "0.0.0" dependencies = [ "camino", "chrono", - "git-url-parse", - "git2", "graphql_client", "houston", "http", - "humantime", "indoc", "online", "pretty_assertions", - "prettytable-rs", "regex", "reqwest", "sdl-encoder", - "semver", "serde", "serde_json", "thiserror", @@ -2362,9 +2347,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", "quote", @@ -2513,9 +2498,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.8.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" +checksum = "570c2eb13b3ab38208130eccd41be92520388791207fde783bda7c1e8ace28d4" dependencies = [ "autocfg", "bytes", diff --git a/Cargo.toml b/Cargo.toml index cd8aa4033..1317c1d75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,10 +53,13 @@ git-url-parse = "0.3.1" git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] } harmonizer = { version = "0.27.0", optional = true } heck = "0.3.3" +humantime = "2.1.0" opener = "0.5.0" os_info = "3.0" prettytable-rs = "0.8.0" -reqwest = {version = "0.11.4", default-features = false, features = ["blocking"] } +reqwest = {version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "native-tls-vendored"]} +regex = "1" +semver = "1" serde = "1.0" serde_json = "1.0" serde_yaml = "0.8" @@ -72,7 +75,6 @@ url = { version = "2.2.2", features = ["serde"] } [dev-dependencies] assert_cmd = "1.0.7" assert_fs = "1.0.3" -assert-json-diff = "2.0.1" predicates = "2.0.0" reqwest = { version = "0.11.4", default-features = false, features = ["blocking", "native-tls-vendored"] } serial_test = "0.5.0" diff --git a/crates/rover-client/.eslintrc.json b/crates/rover-client/.eslintrc.json deleted file mode 100644 index fc09e7e9a..000000000 --- a/crates/rover-client/.eslintrc.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "root": true, - "overrides": [ - { - "files": ["*.graphql"], - "parser": "@graphql-eslint/eslint-plugin", - "plugins": ["@graphql-eslint"], - "parserOptions": { - "schema": "./.schema/schema.graphql", - "operations": ["./src/operations/**/*.graphql"] - }, - "rules": { - "@graphql-eslint/avoid-duplicate-fields": 2, - "@graphql-eslint/avoid-operation-name-prefix": 2, - "@graphql-eslint/avoid-typename-prefix": 2, - "@graphql-eslint/description-style": 2, - "@graphql-eslint/executable-definitions": 2, - "@graphql-eslint/fields-on-correct-type": 2, - "@graphql-eslint/fragments-on-composite-type": 2, - "@graphql-eslint/input-name": 2, - "@graphql-eslint/known-argument-names": 2, - "@graphql-eslint/known-directives": 2, - "@graphql-eslint/known-fragment-names": 2, - "@graphql-eslint/known-type-names": 2, - "@graphql-eslint/lone-anonymous-operation": 2, - "@graphql-eslint/no-anonymous-operations": 2, - "@graphql-eslint/no-case-insensitive-enum-values-duplicates": 2, - "@graphql-eslint/no-deprecated": 2, - "@graphql-eslint/no-fragment-cycles": 2, - "@graphql-eslint/no-hashtag-description": 2, - "@graphql-eslint/no-undefined-variables": 2, - "@graphql-eslint/no-unused-fields": 2, - "@graphql-eslint/no-unused-fragments": 2, - "@graphql-eslint/no-unused-variables": 2, - "@graphql-eslint/one-field-subscriptions": 2, - "@graphql-eslint/overlapping-fields-can-be-merged": 2, - "@graphql-eslint/possible-fragment-spread": 2, - "@graphql-eslint/possible-type-extension": 2, - "@graphql-eslint/provided-required-arguments": 2, - "@graphql-eslint/require-deprecation-reason": 2, - "@graphql-eslint/scalar-leafs": 2, - "@graphql-eslint/strict-id-in-types": 2, - "@graphql-eslint/unique-argument-names": 2, - "@graphql-eslint/unique-directive-names": 2, - "@graphql-eslint/unique-directive-names-per-location": 2, - "@graphql-eslint/unique-enum-value-names": 2, - "@graphql-eslint/unique-field-definition-names": 2, - "@graphql-eslint/unique-fragment-name": 2, - "@graphql-eslint/unique-input-field-names": 2, - "@graphql-eslint/unique-operation-name": 2, - "@graphql-eslint/unique-operation-types": 2, - "@graphql-eslint/unique-type-names": 2, - "@graphql-eslint/unique-variable-names": 2, - "@graphql-eslint/value-literals-of-correct-type": 2, - "@graphql-eslint/variables-are-input-types": 2, - "@graphql-eslint/variables-in-allowed-position": 2 - } - } - ] -} diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index e1c5315de..03de68888 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -12,17 +12,12 @@ houston = {path = "../houston"} # crates.io deps camino = "1" -chrono = { version = "0.4", features = ["serde"] } -git-url-parse = "0.3.1" -git2 = { version = "0.13.20", default-features = false, features = ["vendored-openssl"] } +chrono = "0.4" graphql_client = "0.9" http = "0.2" -humantime = "2.1.0" -prettytable-rs = "0.8.0" -reqwest = { version = "0.11", default-features = false, features = ["blocking", "brotli", "gzip", "json", "native-tls-vendored"] } -regex = "1" +regex = "1.5.4" +reqwest = {version = "0.11", default-features = false, features = ["blocking", "json"]} sdl-encoder = {path = "../sdl-encoder"} -semver = "1" serde = "1" serde_json = "1" thiserror = "1" diff --git a/crates/rover-client/package-lock.json b/crates/rover-client/package-lock.json deleted file mode 100644 index 2f685cc2c..000000000 --- a/crates/rover-client/package-lock.json +++ /dev/null @@ -1,5593 +0,0 @@ -{ - "name": "rover-client", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "version": "0.0.0", - "devDependencies": { - "@graphql-eslint/eslint-plugin": ">=1.1.4", - "eslint": ">=7.31.0", - "graphql": ">=15.5.1" - } - }, - "node_modules/@ardatan/aggregate-error": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz", - "integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==", - "dev": true, - "dependencies": { - "tslib": "~2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@ardatan/aggregate-error/node_modules/tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", - "dev": true - }, - "node_modules/@ardatan/fetch-event-source": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@ardatan/fetch-event-source/-/fetch-event-source-2.0.2.tgz", - "integrity": "sha512-mcpz/wJ7s50PJIVz4OQ1Yim3w/AAchtYtIg0QMWiMR2cZZoI9t23hRyqeumtD5EmyJu0fxtjmQ5WY8GI86V4rQ==", - "dev": true - }, - "node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.9.tgz", - "integrity": "sha512-4yoHbhDYzFa0GLfCzLp5GxH7vPPMAHdZjyE7M/OajM9037zhx0rf+iNsJwp4PT0MSFpwjG7BsHEbPkBQpZ6cYA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.9", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name/node_modules/@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity/node_modules/@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.12.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", - "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/parser": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.9.tgz", - "integrity": "sha512-RdUTOseXJ8POjjOeEBEvNMIZU/nm4yu2rufRkcibzkkg7DmQvXU8v3M4Xk9G7uuI86CDGkKcuDWgioqZm+mScQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", - "dev": true, - "dependencies": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "cosmiconfig": ">=6" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@graphql-eslint/eslint-plugin": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@graphql-eslint/eslint-plugin/-/eslint-plugin-1.1.4.tgz", - "integrity": "sha512-Gpl+dC8WAxrwYlkKoZ1jDnyvCawL4B405bdsohPd85Jf4zhV7BAqyEzgNaN1fUY7699tZ1Tse0jZitjLRhhcoQ==", - "dev": true, - "dependencies": { - "@graphql-tools/code-file-loader": "~6.3.0", - "@graphql-tools/graphql-file-loader": "~6.2.0", - "@graphql-tools/graphql-tag-pluck": "~6.5.0", - "@graphql-tools/import": "^6.3.1", - "@graphql-tools/json-file-loader": "~6.2.6", - "@graphql-tools/load": "~6.2.0", - "@graphql-tools/url-loader": "~6.10.0", - "@graphql-tools/utils": "~7.10.0", - "graphql-config": "^3.2.0", - "graphql-depth-limit": "1.1.0" - }, - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/batch-execute": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-7.1.2.tgz", - "integrity": "sha512-IuR2SB2MnC2ztA/XeTMTfWcA0Wy7ZH5u+nDkDNLAdX+AaSyDnsQS35sCmHqG0VOGTl7rzoyBWLCKGwSJplgtwg==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^7.7.0", - "dataloader": "2.0.0", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/batch-execute/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/batch-execute/node_modules/value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@graphql-tools/code-file-loader": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.3.1.tgz", - "integrity": "sha512-ZJimcm2ig+avgsEOWWVvAaxZrXXhiiSZyYYOJi0hk9wh5BxZcLUNKkTp6EFnZE/jmGUwuos3pIjUD3Hwi3Bwhg==", - "dev": true, - "dependencies": { - "@graphql-tools/graphql-tag-pluck": "^6.5.1", - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.1.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/delegate": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-7.1.5.tgz", - "integrity": "sha512-bQu+hDd37e+FZ0CQGEEczmRSfQRnnXeUxI/0miDV+NV/zCbEdIJj5tYFNrKT03W6wgdqx8U06d8L23LxvGri/g==", - "dev": true, - "dependencies": { - "@ardatan/aggregate-error": "0.0.6", - "@graphql-tools/batch-execute": "^7.1.2", - "@graphql-tools/schema": "^7.1.5", - "@graphql-tools/utils": "^7.7.1", - "dataloader": "2.0.0", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/delegate/node_modules/@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/delegate/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/delegate/node_modules/value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@graphql-tools/graphql-file-loader": { - "version": "6.2.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.7.tgz", - "integrity": "sha512-5k2SNz0W87tDcymhEMZMkd6/vs6QawDyjQXWtqkuLTBF3vxjxPD1I4dwHoxgWPIjjANhXybvulD7E+St/7s9TQ==", - "dev": true, - "dependencies": { - "@graphql-tools/import": "^6.2.6", - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.1.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.5.1.tgz", - "integrity": "sha512-7qkm82iFmcpb8M6/yRgzjShtW6Qu2OlCSZp8uatA3J0eMl87TxyJoUmL3M3UMMOSundAK8GmoyNVFUrueueV5Q==", - "dev": true, - "dependencies": { - "@babel/parser": "7.12.16", - "@babel/traverse": "7.12.13", - "@babel/types": "7.12.13", - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.1.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/import": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.3.1.tgz", - "integrity": "sha512-1szR19JI6WPibjYurMLdadHKZoG9C//8I/FZ0Dt4vJSbrMdVNp8WFxg4QnZrDeMG4MzZc90etsyF5ofKjcC+jw==", - "dev": true, - "dependencies": { - "resolve-from": "5.0.0", - "tslib": "~2.2.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/import/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/json-file-loader": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.2.6.tgz", - "integrity": "sha512-CnfwBSY5926zyb6fkDBHnlTblHnHI4hoBALFYXnrg0Ev4yWU8B04DZl/pBRUc459VNgO2x8/mxGIZj2hPJG1EA==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.0.1" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/json-file-loader/node_modules/tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", - "dev": true - }, - "node_modules/@graphql-tools/load": { - "version": "6.2.8", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.2.8.tgz", - "integrity": "sha512-JpbyXOXd8fJXdBh2ta0Q4w8ia6uK5FHzrTNmcvYBvflFuWly2LDTk2abbSl81zKkzswQMEd2UIYghXELRg8eTA==", - "dev": true, - "dependencies": { - "@graphql-tools/merge": "^6.2.12", - "@graphql-tools/utils": "^7.5.0", - "globby": "11.0.3", - "import-from": "3.0.0", - "is-glob": "4.0.1", - "p-limit": "3.1.0", - "tslib": "~2.2.0", - "unixify": "1.0.0", - "valid-url": "1.0.9" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/load/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/merge": { - "version": "6.2.16", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.16.tgz", - "integrity": "sha512-KjZ1pppzKcr2Uspgb53p8uw5yhWVuGIL+sEroar7vLsClSsuiGib8OKVICAGWjC9wrCxGaL9SjJGavfXpJMQIg==", - "dev": true, - "dependencies": { - "@graphql-tools/schema": "^8.0.1", - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg==", - "dev": true, - "dependencies": { - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/merge/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - }, - "node_modules/@graphql-tools/schema": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.0.1.tgz", - "integrity": "sha512-QG2HGLJjmsNc1wcj+rwZTEArgfMp7rsrb8iVq4P8ce1mDYAt6kRV6bLyPVb9q/j8Ik2zBc/B/Y1jPsnAVUHwdA==", - "dev": true, - "dependencies": { - "@graphql-tools/merge": "6.2.16", - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg==", - "dev": true, - "dependencies": { - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/schema/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - }, - "node_modules/@graphql-tools/url-loader": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz", - "integrity": "sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw==", - "dev": true, - "dependencies": { - "@graphql-tools/delegate": "^7.0.1", - "@graphql-tools/utils": "^7.9.0", - "@graphql-tools/wrap": "^7.0.4", - "@microsoft/fetch-event-source": "2.0.1", - "@types/websocket": "1.0.2", - "abort-controller": "3.0.0", - "cross-fetch": "3.1.4", - "extract-files": "9.0.0", - "form-data": "4.0.0", - "graphql-ws": "^4.4.1", - "is-promise": "4.0.0", - "isomorphic-ws": "4.0.1", - "lodash": "4.17.21", - "meros": "1.1.4", - "subscriptions-transport-ws": "^0.9.18", - "sync-fetch": "0.3.0", - "tslib": "~2.2.0", - "valid-url": "1.0.9", - "ws": "7.4.5" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/url-loader/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", - "dev": true, - "dependencies": { - "@ardatan/aggregate-error": "0.0.6", - "camel-case": "4.1.2", - "tslib": "~2.2.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/utils/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/wrap": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-7.0.8.tgz", - "integrity": "sha512-1NDUymworsOlb53Qfh7fonDi2STvqCtbeE68ntKY9K/Ju/be2ZNxrFSbrBHwnxWcN9PjISNnLcAyJ1L5tCUyhg==", - "dev": true, - "dependencies": { - "@graphql-tools/delegate": "^7.1.5", - "@graphql-tools/schema": "^7.1.5", - "@graphql-tools/utils": "^7.8.1", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/wrap/node_modules/@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/@graphql-tools/wrap/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/wrap/node_modules/value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true - }, - "node_modules/@microsoft/fetch-event-source": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", - "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==", - "dev": true - }, - "node_modules/@n1ru4l/graphql-live-query": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@n1ru4l/graphql-live-query/-/graphql-live-query-0.7.1.tgz", - "integrity": "sha512-5kJPe2FkPNsCGu9tocKIzUSNO986qAqdnbk8hIFqWlpVPBAmEAOYb1mr6PA18FYAlu7ojWm9Hm13k29aj2GGlQ==", - "dev": true, - "peerDependencies": { - "graphql": "^15.4.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/node": { - "version": "16.4.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.10.tgz", - "integrity": "sha512-TmVHsm43br64js9BqHWqiDZA+xMtbUpI1MBIA0EyiBmoV9pcEYFOSdj5fr6enZNfh4fChh+AGOLIzGwJnkshyQ==", - "dev": true - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "node_modules/@types/websocket": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz", - "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig-toml-loader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz", - "integrity": "sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==", - "dev": true, - "dependencies": { - "@iarna/toml": "^2.2.5" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dataloader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", - "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "dev": true - }, - "node_modules/extract-files": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", - "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", - "dev": true, - "engines": { - "node": "^10.17.0 || ^12.0.0 || >= 13.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/jaydenseric" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/graphql": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz", - "integrity": "sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==", - "dev": true, - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/graphql-config": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.4.0.tgz", - "integrity": "sha512-jnnN8wr9vkMMUHq1gOEcG9Qzd3NGgJ6k8nc3WuzsYGrQfZtQX7Mve+sHOUOfDUKsV4uhSRa/gHURA+tw5qK42g==", - "dev": true, - "dependencies": { - "@endemolshinegroup/cosmiconfig-typescript-loader": "3.0.2", - "@graphql-tools/graphql-file-loader": "^7.0.0", - "@graphql-tools/json-file-loader": "^7.0.0", - "@graphql-tools/load": "^7.0.0", - "@graphql-tools/merge": "^6.2.15", - "@graphql-tools/url-loader": "^7.0.2", - "@graphql-tools/utils": "^8.0.0", - "cosmiconfig": "7.0.0", - "cosmiconfig-toml-loader": "1.0.0", - "minimatch": "3.0.4", - "string-env-interpolation": "1.0.1" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/batch-execute": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-8.0.1.tgz", - "integrity": "sha512-39SpVo2BgcuFLp3ZNvnyPbyFBCCAQMsR/0BvSC8yQaq5AOMLU76fJCKY5RcmNY+9n6529eem8kzdN20qm2rq+g==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "8.0.1", - "dataloader": "2.0.0", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/delegate": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-8.0.3.tgz", - "integrity": "sha512-u6TTTqslVDna/0v9kJSqIYeqa+TdZDQMqDlwrsLi7VsATnzgv0OAYxj55XxSBJQWh0oSM+i4EoGqbwj+wU2tvg==", - "dev": true, - "dependencies": { - "@graphql-tools/batch-execute": "^8.0.1", - "@graphql-tools/schema": "^8.0.1", - "@graphql-tools/utils": "8.0.1", - "dataloader": "2.0.0", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/graphql-file-loader": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.0.1.tgz", - "integrity": "sha512-gSYh+W86GcR/TP8bLCPuNdAUeV1/y3+0czM32r6VxqZNiJjiSF6k68rb4F7M6jJ/1dA/SAEZpXLd94Dokc2s/g==", - "dev": true, - "dependencies": { - "@graphql-tools/import": "^6.2.6", - "@graphql-tools/utils": "8.0.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "tslib": "~2.3.0", - "unixify": "^1.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/json-file-loader": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-7.0.1.tgz", - "integrity": "sha512-UfZ3vA37d0OG28p8GijyIo5zRMXozz9f1TBcb++k0cQKmElILrnBHD4ZiNkWTFz5VBSKSjk4gpJD89D8BKKDyg==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/load": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-7.0.1.tgz", - "integrity": "sha512-TiHgGRWhHHRdhWyVlZrF9PT6NqGG0NRL0XLY6IYWzinNMuPAOyYcp6zwtPxgDbfeRQMO41/4QTIkR6mCLHrcRQ==", - "dev": true, - "dependencies": { - "@graphql-tools/merge": "^6.2.16", - "@graphql-tools/utils": "8.0.1", - "import-from": "4.0.0", - "p-limit": "3.1.0", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/url-loader": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-7.0.3.tgz", - "integrity": "sha512-9QhYaA6nCAleFSw5WvNgwy/ixkcJTrMfFAP3Ofsgk9Cau0iUesrgRYEYNJldf0NevP7wHzaVuWqRP/xHLqW2iw==", - "dev": true, - "dependencies": { - "@ardatan/fetch-event-source": "2.0.2", - "@graphql-tools/delegate": "8.0.3", - "@graphql-tools/utils": "8.0.1", - "@graphql-tools/wrap": "^8.0.3", - "@n1ru4l/graphql-live-query": "0.7.1", - "@types/websocket": "1.0.4", - "abort-controller": "3.0.0", - "cross-fetch": "3.1.4", - "extract-files": "11.0.0", - "form-data": "4.0.0", - "graphql-ws": "^5.0.0", - "is-promise": "4.0.0", - "isomorphic-ws": "4.0.1", - "lodash": "4.17.21", - "meros": "1.1.4", - "subscriptions-transport-ws": "^0.10.0", - "sync-fetch": "0.3.0", - "tslib": "~2.3.0", - "valid-url": "1.0.9", - "value-or-promise": "1.0.10", - "ws": "8.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/url-loader/node_modules/ws": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.0.0.tgz", - "integrity": "sha512-6AcSIXpBlS0QvCVKk+3cWnWElLsA6SzC0lkQ43ciEglgXJXiCWK3/CGFEJ+Ybgp006CMibamAsqOlxE9s4AvYA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg==", - "dev": true, - "dependencies": { - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@graphql-tools/wrap": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-8.0.3.tgz", - "integrity": "sha512-32tZiT5pEdyAzhU3jUW2ff/PS+z00jVGDvoy9m+LBG/NXMPb4JGFh3mDB91ZYnqrxvUHd2UNckxf+rg8Ej8tLg==", - "dev": true, - "dependencies": { - "@graphql-tools/delegate": "8.0.3", - "@graphql-tools/schema": "^8.0.1", - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-config/node_modules/@types/websocket": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.4.tgz", - "integrity": "sha512-qn1LkcFEKK8RPp459jkjzsfpbsx36BBt3oC3pITYtkoBw/aVX+EZFa5j3ThCRTNpLFvIMr5dSTD4RaMdilIOpA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/graphql-config/node_modules/extract-files": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz", - "integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==", - "dev": true, - "engines": { - "node": "^12.20 || >= 14.13" - }, - "funding": { - "url": "https://github.com/sponsors/jaydenseric" - } - }, - "node_modules/graphql-config/node_modules/graphql-ws": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.3.0.tgz", - "integrity": "sha512-53MbSTOmgx5i6hf3DHVD5PrXix1drDmt2ja8MW7NG+aTpKGzkXVLyNcyNpxme4SK8jVtIV6ZIHkiwirqN0efpw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": ">=0.11 <=15" - } - }, - "node_modules/graphql-config/node_modules/import-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", - "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", - "dev": true, - "engines": { - "node": ">=12.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphql-config/node_modules/subscriptions-transport-ws": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.10.0.tgz", - "integrity": "sha512-k28LhLn3abJ1mowFW+LP4QGggE0e3hrk55zXbMHyAeZkCUYtC0owepiwqMD3zX8DglQVaxnhE760pESrNSEzpg==", - "dev": true, - "dependencies": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependencies": { - "graphql": ">=0.10.0" - } - }, - "node_modules/graphql-config/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - }, - "node_modules/graphql-depth-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", - "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "graphql": "*" - } - }, - "node_modules/graphql-ws": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz", - "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": ">=0.11 <=15" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "dev": true, - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/meros": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", - "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@types/node": ">=12" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "dependencies": { - "mime-db": "1.49.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/string-env-interpolation": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", - "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", - "dev": true, - "dependencies": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependencies": { - "graphql": ">=0.10.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sync-fetch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.3.0.tgz", - "integrity": "sha512-dJp4qg+x4JwSEW1HibAuMi0IIrBI3wuQr2GimmqB7OXR50wmwzfdusG+p39R9w3R6aFtZ2mzvxvWKQ3Bd/vx3g==", - "dev": true, - "dependencies": { - "buffer": "^5.7.0", - "node-fetch": "^2.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unixify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", - "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", - "dev": true, - "dependencies": { - "normalize-path": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/valid-url": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=", - "dev": true - }, - "node_modules/value-or-promise": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.10.tgz", - "integrity": "sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ardatan/aggregate-error": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz", - "integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==", - "dev": true, - "requires": { - "tslib": "~2.0.1" - }, - "dependencies": { - "tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", - "dev": true - } - } - }, - "@ardatan/fetch-event-source": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@ardatan/fetch-event-source/-/fetch-event-source-2.0.2.tgz", - "integrity": "sha512-mcpz/wJ7s50PJIVz4OQ1Yim3w/AAchtYtIg0QMWiMR2cZZoI9t23hRyqeumtD5EmyJu0fxtjmQ5WY8GI86V4rQ==", - "dev": true - }, - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.9.tgz", - "integrity": "sha512-4yoHbhDYzFa0GLfCzLp5GxH7vPPMAHdZjyE7M/OajM9037zhx0rf+iNsJwp4PT0MSFpwjG7BsHEbPkBQpZ6cYA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.9", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", - "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/parser": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.9.tgz", - "integrity": "sha512-RdUTOseXJ8POjjOeEBEvNMIZU/nm4yu2rufRkcibzkkg7DmQvXU8v3M4Xk9G7uuI86CDGkKcuDWgioqZm+mScQ==", - "dev": true - }, - "@babel/types": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.9.tgz", - "integrity": "sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", - "dev": true, - "requires": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - } - } - }, - "@graphql-eslint/eslint-plugin": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@graphql-eslint/eslint-plugin/-/eslint-plugin-1.1.4.tgz", - "integrity": "sha512-Gpl+dC8WAxrwYlkKoZ1jDnyvCawL4B405bdsohPd85Jf4zhV7BAqyEzgNaN1fUY7699tZ1Tse0jZitjLRhhcoQ==", - "dev": true, - "requires": { - "@graphql-tools/code-file-loader": "~6.3.0", - "@graphql-tools/graphql-file-loader": "~6.2.0", - "@graphql-tools/graphql-tag-pluck": "~6.5.0", - "@graphql-tools/import": "^6.3.1", - "@graphql-tools/json-file-loader": "~6.2.6", - "@graphql-tools/load": "~6.2.0", - "@graphql-tools/url-loader": "~6.10.0", - "@graphql-tools/utils": "~7.10.0", - "graphql-config": "^3.2.0", - "graphql-depth-limit": "1.1.0" - } - }, - "@graphql-tools/batch-execute": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-7.1.2.tgz", - "integrity": "sha512-IuR2SB2MnC2ztA/XeTMTfWcA0Wy7ZH5u+nDkDNLAdX+AaSyDnsQS35sCmHqG0VOGTl7rzoyBWLCKGwSJplgtwg==", - "dev": true, - "requires": { - "@graphql-tools/utils": "^7.7.0", - "dataloader": "2.0.0", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true - } - } - }, - "@graphql-tools/code-file-loader": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.3.1.tgz", - "integrity": "sha512-ZJimcm2ig+avgsEOWWVvAaxZrXXhiiSZyYYOJi0hk9wh5BxZcLUNKkTp6EFnZE/jmGUwuos3pIjUD3Hwi3Bwhg==", - "dev": true, - "requires": { - "@graphql-tools/graphql-tag-pluck": "^6.5.1", - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.1.0" - } - }, - "@graphql-tools/delegate": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-7.1.5.tgz", - "integrity": "sha512-bQu+hDd37e+FZ0CQGEEczmRSfQRnnXeUxI/0miDV+NV/zCbEdIJj5tYFNrKT03W6wgdqx8U06d8L23LxvGri/g==", - "dev": true, - "requires": { - "@ardatan/aggregate-error": "0.0.6", - "@graphql-tools/batch-execute": "^7.1.2", - "@graphql-tools/schema": "^7.1.5", - "@graphql-tools/utils": "^7.7.1", - "dataloader": "2.0.0", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "dependencies": { - "@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", - "dev": true, - "requires": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - } - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true - } - } - }, - "@graphql-tools/graphql-file-loader": { - "version": "6.2.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.7.tgz", - "integrity": "sha512-5k2SNz0W87tDcymhEMZMkd6/vs6QawDyjQXWtqkuLTBF3vxjxPD1I4dwHoxgWPIjjANhXybvulD7E+St/7s9TQ==", - "dev": true, - "requires": { - "@graphql-tools/import": "^6.2.6", - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.1.0" - } - }, - "@graphql-tools/graphql-tag-pluck": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.5.1.tgz", - "integrity": "sha512-7qkm82iFmcpb8M6/yRgzjShtW6Qu2OlCSZp8uatA3J0eMl87TxyJoUmL3M3UMMOSundAK8GmoyNVFUrueueV5Q==", - "dev": true, - "requires": { - "@babel/parser": "7.12.16", - "@babel/traverse": "7.12.13", - "@babel/types": "7.12.13", - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.1.0" - } - }, - "@graphql-tools/import": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.3.1.tgz", - "integrity": "sha512-1szR19JI6WPibjYurMLdadHKZoG9C//8I/FZ0Dt4vJSbrMdVNp8WFxg4QnZrDeMG4MzZc90etsyF5ofKjcC+jw==", - "dev": true, - "requires": { - "resolve-from": "5.0.0", - "tslib": "~2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "@graphql-tools/json-file-loader": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.2.6.tgz", - "integrity": "sha512-CnfwBSY5926zyb6fkDBHnlTblHnHI4hoBALFYXnrg0Ev4yWU8B04DZl/pBRUc459VNgO2x8/mxGIZj2hPJG1EA==", - "dev": true, - "requires": { - "@graphql-tools/utils": "^7.0.0", - "tslib": "~2.0.1" - }, - "dependencies": { - "tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", - "dev": true - } - } - }, - "@graphql-tools/load": { - "version": "6.2.8", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.2.8.tgz", - "integrity": "sha512-JpbyXOXd8fJXdBh2ta0Q4w8ia6uK5FHzrTNmcvYBvflFuWly2LDTk2abbSl81zKkzswQMEd2UIYghXELRg8eTA==", - "dev": true, - "requires": { - "@graphql-tools/merge": "^6.2.12", - "@graphql-tools/utils": "^7.5.0", - "globby": "11.0.3", - "import-from": "3.0.0", - "is-glob": "4.0.1", - "p-limit": "3.1.0", - "tslib": "~2.2.0", - "unixify": "1.0.0", - "valid-url": "1.0.9" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "@graphql-tools/merge": { - "version": "6.2.16", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.16.tgz", - "integrity": "sha512-KjZ1pppzKcr2Uspgb53p8uw5yhWVuGIL+sEroar7vLsClSsuiGib8OKVICAGWjC9wrCxGaL9SjJGavfXpJMQIg==", - "dev": true, - "requires": { - "@graphql-tools/schema": "^8.0.1", - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0" - }, - "dependencies": { - "@graphql-tools/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg==", - "dev": true, - "requires": { - "tslib": "~2.3.0" - } - }, - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - } - } - }, - "@graphql-tools/schema": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.0.1.tgz", - "integrity": "sha512-QG2HGLJjmsNc1wcj+rwZTEArgfMp7rsrb8iVq4P8ce1mDYAt6kRV6bLyPVb9q/j8Ik2zBc/B/Y1jPsnAVUHwdA==", - "dev": true, - "requires": { - "@graphql-tools/merge": "6.2.16", - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "dependencies": { - "@graphql-tools/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg==", - "dev": true, - "requires": { - "tslib": "~2.3.0" - } - }, - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - } - } - }, - "@graphql-tools/url-loader": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz", - "integrity": "sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw==", - "dev": true, - "requires": { - "@graphql-tools/delegate": "^7.0.1", - "@graphql-tools/utils": "^7.9.0", - "@graphql-tools/wrap": "^7.0.4", - "@microsoft/fetch-event-source": "2.0.1", - "@types/websocket": "1.0.2", - "abort-controller": "3.0.0", - "cross-fetch": "3.1.4", - "extract-files": "9.0.0", - "form-data": "4.0.0", - "graphql-ws": "^4.4.1", - "is-promise": "4.0.0", - "isomorphic-ws": "4.0.1", - "lodash": "4.17.21", - "meros": "1.1.4", - "subscriptions-transport-ws": "^0.9.18", - "sync-fetch": "0.3.0", - "tslib": "~2.2.0", - "valid-url": "1.0.9", - "ws": "7.4.5" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "@graphql-tools/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", - "dev": true, - "requires": { - "@ardatan/aggregate-error": "0.0.6", - "camel-case": "4.1.2", - "tslib": "~2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "@graphql-tools/wrap": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-7.0.8.tgz", - "integrity": "sha512-1NDUymworsOlb53Qfh7fonDi2STvqCtbeE68ntKY9K/Ju/be2ZNxrFSbrBHwnxWcN9PjISNnLcAyJ1L5tCUyhg==", - "dev": true, - "requires": { - "@graphql-tools/delegate": "^7.1.5", - "@graphql-tools/schema": "^7.1.5", - "@graphql-tools/utils": "^7.8.1", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "dependencies": { - "@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", - "dev": true, - "requires": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - } - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true - }, - "@microsoft/fetch-event-source": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", - "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==", - "dev": true - }, - "@n1ru4l/graphql-live-query": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@n1ru4l/graphql-live-query/-/graphql-live-query-0.7.1.tgz", - "integrity": "sha512-5kJPe2FkPNsCGu9tocKIzUSNO986qAqdnbk8hIFqWlpVPBAmEAOYb1mr6PA18FYAlu7ojWm9Hm13k29aj2GGlQ==", - "dev": true, - "requires": {} - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@types/node": { - "version": "16.4.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.10.tgz", - "integrity": "sha512-TmVHsm43br64js9BqHWqiDZA+xMtbUpI1MBIA0EyiBmoV9pcEYFOSdj5fr6enZNfh4fChh+AGOLIzGwJnkshyQ==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "@types/websocket": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz", - "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "cosmiconfig-toml-loader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz", - "integrity": "sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==", - "dev": true, - "requires": { - "@iarna/toml": "^2.2.5" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", - "dev": true, - "requires": { - "node-fetch": "2.6.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "dataloader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", - "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==", - "dev": true - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "globals": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", - "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "dev": true - }, - "extract-files": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", - "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - } - } - }, - "graphql": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz", - "integrity": "sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==", - "dev": true - }, - "graphql-config": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.4.0.tgz", - "integrity": "sha512-jnnN8wr9vkMMUHq1gOEcG9Qzd3NGgJ6k8nc3WuzsYGrQfZtQX7Mve+sHOUOfDUKsV4uhSRa/gHURA+tw5qK42g==", - "dev": true, - "requires": { - "@endemolshinegroup/cosmiconfig-typescript-loader": "3.0.2", - "@graphql-tools/graphql-file-loader": "^7.0.0", - "@graphql-tools/json-file-loader": "^7.0.0", - "@graphql-tools/load": "^7.0.0", - "@graphql-tools/merge": "^6.2.15", - "@graphql-tools/url-loader": "^7.0.2", - "@graphql-tools/utils": "^8.0.0", - "cosmiconfig": "7.0.0", - "cosmiconfig-toml-loader": "1.0.0", - "minimatch": "3.0.4", - "string-env-interpolation": "1.0.1" - }, - "dependencies": { - "@graphql-tools/batch-execute": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-8.0.1.tgz", - "integrity": "sha512-39SpVo2BgcuFLp3ZNvnyPbyFBCCAQMsR/0BvSC8yQaq5AOMLU76fJCKY5RcmNY+9n6529eem8kzdN20qm2rq+g==", - "dev": true, - "requires": { - "@graphql-tools/utils": "8.0.1", - "dataloader": "2.0.0", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - } - }, - "@graphql-tools/delegate": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-8.0.3.tgz", - "integrity": "sha512-u6TTTqslVDna/0v9kJSqIYeqa+TdZDQMqDlwrsLi7VsATnzgv0OAYxj55XxSBJQWh0oSM+i4EoGqbwj+wU2tvg==", - "dev": true, - "requires": { - "@graphql-tools/batch-execute": "^8.0.1", - "@graphql-tools/schema": "^8.0.1", - "@graphql-tools/utils": "8.0.1", - "dataloader": "2.0.0", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - } - }, - "@graphql-tools/graphql-file-loader": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.0.1.tgz", - "integrity": "sha512-gSYh+W86GcR/TP8bLCPuNdAUeV1/y3+0czM32r6VxqZNiJjiSF6k68rb4F7M6jJ/1dA/SAEZpXLd94Dokc2s/g==", - "dev": true, - "requires": { - "@graphql-tools/import": "^6.2.6", - "@graphql-tools/utils": "8.0.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "tslib": "~2.3.0", - "unixify": "^1.0.0" - } - }, - "@graphql-tools/json-file-loader": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-7.0.1.tgz", - "integrity": "sha512-UfZ3vA37d0OG28p8GijyIo5zRMXozz9f1TBcb++k0cQKmElILrnBHD4ZiNkWTFz5VBSKSjk4gpJD89D8BKKDyg==", - "dev": true, - "requires": { - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0" - } - }, - "@graphql-tools/load": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-7.0.1.tgz", - "integrity": "sha512-TiHgGRWhHHRdhWyVlZrF9PT6NqGG0NRL0XLY6IYWzinNMuPAOyYcp6zwtPxgDbfeRQMO41/4QTIkR6mCLHrcRQ==", - "dev": true, - "requires": { - "@graphql-tools/merge": "^6.2.16", - "@graphql-tools/utils": "8.0.1", - "import-from": "4.0.0", - "p-limit": "3.1.0", - "tslib": "~2.3.0" - } - }, - "@graphql-tools/url-loader": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-7.0.3.tgz", - "integrity": "sha512-9QhYaA6nCAleFSw5WvNgwy/ixkcJTrMfFAP3Ofsgk9Cau0iUesrgRYEYNJldf0NevP7wHzaVuWqRP/xHLqW2iw==", - "dev": true, - "requires": { - "@ardatan/fetch-event-source": "2.0.2", - "@graphql-tools/delegate": "8.0.3", - "@graphql-tools/utils": "8.0.1", - "@graphql-tools/wrap": "^8.0.3", - "@n1ru4l/graphql-live-query": "0.7.1", - "@types/websocket": "1.0.4", - "abort-controller": "3.0.0", - "cross-fetch": "3.1.4", - "extract-files": "11.0.0", - "form-data": "4.0.0", - "graphql-ws": "^5.0.0", - "is-promise": "4.0.0", - "isomorphic-ws": "4.0.1", - "lodash": "4.17.21", - "meros": "1.1.4", - "subscriptions-transport-ws": "^0.10.0", - "sync-fetch": "0.3.0", - "tslib": "~2.3.0", - "valid-url": "1.0.9", - "value-or-promise": "1.0.10", - "ws": "8.0.0" - }, - "dependencies": { - "ws": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.0.0.tgz", - "integrity": "sha512-6AcSIXpBlS0QvCVKk+3cWnWElLsA6SzC0lkQ43ciEglgXJXiCWK3/CGFEJ+Ybgp006CMibamAsqOlxE9s4AvYA==", - "dev": true, - "requires": {} - } - } - }, - "@graphql-tools/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-gjQk6sht4b0/hcG+QEVxfMyO8bn5tuU1nIOVhQ4whgFaUmrnb3hx2mwzz1EJzfIOAuHKE8tY0lu6jt3bGTD4yg==", - "dev": true, - "requires": { - "tslib": "~2.3.0" - } - }, - "@graphql-tools/wrap": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-8.0.3.tgz", - "integrity": "sha512-32tZiT5pEdyAzhU3jUW2ff/PS+z00jVGDvoy9m+LBG/NXMPb4JGFh3mDB91ZYnqrxvUHd2UNckxf+rg8Ej8tLg==", - "dev": true, - "requires": { - "@graphql-tools/delegate": "8.0.3", - "@graphql-tools/schema": "^8.0.1", - "@graphql-tools/utils": "8.0.1", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - } - }, - "@types/websocket": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.4.tgz", - "integrity": "sha512-qn1LkcFEKK8RPp459jkjzsfpbsx36BBt3oC3pITYtkoBw/aVX+EZFa5j3ThCRTNpLFvIMr5dSTD4RaMdilIOpA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "extract-files": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz", - "integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==", - "dev": true - }, - "graphql-ws": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.3.0.tgz", - "integrity": "sha512-53MbSTOmgx5i6hf3DHVD5PrXix1drDmt2ja8MW7NG+aTpKGzkXVLyNcyNpxme4SK8jVtIV6ZIHkiwirqN0efpw==", - "dev": true, - "requires": {} - }, - "import-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", - "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", - "dev": true - }, - "subscriptions-transport-ws": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.10.0.tgz", - "integrity": "sha512-k28LhLn3abJ1mowFW+LP4QGggE0e3hrk55zXbMHyAeZkCUYtC0owepiwqMD3zX8DglQVaxnhE760pESrNSEzpg==", - "dev": true, - "requires": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - } - }, - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - } - } - }, - "graphql-depth-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", - "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", - "dev": true, - "requires": { - "arrify": "^1.0.1" - } - }, - "graphql-ws": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz", - "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==", - "dev": true, - "requires": {} - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "dev": true, - "requires": {} - }, - "iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "meros": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", - "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", - "dev": true, - "requires": {} - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true - }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "requires": { - "mime-db": "1.49.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "string-env-interpolation": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz", - "integrity": "sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", - "dev": true, - "requires": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, - "sync-fetch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.3.0.tgz", - "integrity": "sha512-dJp4qg+x4JwSEW1HibAuMi0IIrBI3wuQr2GimmqB7OXR50wmwzfdusG+p39R9w3R6aFtZ2mzvxvWKQ3Bd/vx3g==", - "dev": true, - "requires": { - "buffer": "^5.7.0", - "node-fetch": "^2.6.1" - } - }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - }, - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, - "peer": true - }, - "unixify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", - "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "valid-url": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=", - "dev": true - }, - "value-or-promise": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.10.tgz", - "integrity": "sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", - "dev": true, - "requires": {} - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/crates/rover-client/package.json b/crates/rover-client/package.json deleted file mode 100644 index 0665ee90d..000000000 --- a/crates/rover-client/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "rover-client", - "version": "0.0.0", - "description": "This package is solely used for linting GraphQL schemas", - "scripts": { - "lint": "eslint ." - }, - "author": "opensource@apollographql.com", - "devDependencies": { - "@graphql-eslint/eslint-plugin": ">=1.1.4", - "eslint": ">=7.31.0", - "graphql": ">=15.5.1" - } -} diff --git a/crates/rover-client/src/blocking/client.rs b/crates/rover-client/src/blocking/client.rs index 1718346f2..c734a63ad 100644 --- a/crates/rover-client/src/blocking/client.rs +++ b/crates/rover-client/src/blocking/client.rs @@ -1,16 +1,13 @@ -use crate::RoverClientError; +use crate::{headers, RoverClientError}; use graphql_client::{Error as GraphQLError, GraphQLQuery, Response as GraphQLResponse}; use reqwest::{ blocking::{Client as ReqwestClient, Response}, - header::{HeaderMap, HeaderName, HeaderValue}, + header::HeaderMap, Error as ReqwestError, StatusCode, }; use std::collections::HashMap; -pub(crate) const JSON_CONTENT_TYPE: &str = "application/json"; -pub(crate) const CLIENT_NAME: &str = "rover-client"; - /// Represents a generic GraphQL client for making http requests. pub struct GraphQLClient { graphql_endpoint: String, @@ -38,7 +35,7 @@ impl GraphQLClient { variables: Q::Variables, header_map: &HashMap, ) -> Result { - let header_map = build_headers(header_map)?; + let header_map = headers::build(header_map)?; let response = self.execute::(variables, header_map)?; GraphQLClient::handle_response::(response) } @@ -128,26 +125,6 @@ fn handle_graphql_body_errors(errors: Vec) -> Result<(), RoverClie } } -/// Function for building a [HeaderMap] for making http requests. Use for -/// Generic requests to any graphql endpoint. -/// -/// Takes a single argument, list of header key/value pairs -fn build_headers(header_map: &HashMap) -> Result { - let mut headers = HeaderMap::new(); - - // this should be consistent for any graphql requests - let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; - headers.append("Content-Type", content_type); - - for (key, value) in header_map { - let header_key = HeaderName::from_bytes(key.as_bytes())?; - let header_value = HeaderValue::from_str(value)?; - headers.append(header_key, header_value); - } - - Ok(headers) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rover-client/src/blocking/mod.rs b/crates/rover-client/src/blocking/mod.rs index d89a42510..e51e31143 100644 --- a/crates/rover-client/src/blocking/mod.rs +++ b/crates/rover-client/src/blocking/mod.rs @@ -3,5 +3,3 @@ mod studio_client; pub use client::GraphQLClient; pub use studio_client::StudioClient; - -pub(crate) use client::{CLIENT_NAME, JSON_CONTENT_TYPE}; diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index 405945f86..a7a4d2fbc 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -1,17 +1,12 @@ -use crate::{ - blocking::{GraphQLClient, CLIENT_NAME, JSON_CONTENT_TYPE}, - RoverClientError, -}; - -use houston::{Credential, CredentialOrigin}; +use crate::{blocking::GraphQLClient, headers, RoverClientError}; +use houston::Credential; use graphql_client::GraphQLQuery; -use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{blocking::Client as ReqwestClient, Error as ReqwestError}; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { - credential: Credential, + pub credential: Credential, client: GraphQLClient, version: String, } @@ -39,44 +34,8 @@ impl StudioClient { &self, variables: Q::Variables, ) -> Result { - let header_map = self.build_studio_headers()?; + let header_map = headers::build_studio_headers(&self.credential.api_key, &self.version)?; let response = self.client.execute::(variables, header_map)?; GraphQLClient::handle_response::(response) } - - /// Function for building a [HeaderMap] for making http requests. Use for making - /// requests to Apollo Studio. We're leaving this separate from `build` since we - /// need to be able to mark the api_key as sensitive (at the bottom) - /// - /// Takes an `api_key` and a `client_version`, and returns a [HeaderMap]. - pub fn build_studio_headers(&self) -> Result { - let mut headers = HeaderMap::new(); - - let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; - headers.insert("Content-Type", content_type); - - // The headers "apollographql-client-name" and "apollographql-client-version" - // are used for client identification in Apollo Studio. - - // This provides metrics in Studio that help keep track of what parts of the schema - // Rover uses, which ensures future changes to the API do not break Rover users. - // more info here: - // https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client - - let client_name = HeaderValue::from_str(CLIENT_NAME)?; - headers.insert("apollographql-client-name", client_name); - tracing::debug!(?self.version); - let client_version = HeaderValue::from_str(&self.version)?; - headers.insert("apollographql-client-version", client_version); - - let mut api_key = HeaderValue::from_str(&self.credential.api_key)?; - api_key.set_sensitive(true); - headers.insert("x-api-key", api_key); - - Ok(headers) - } - - pub fn get_credential_origin(&self) -> CredentialOrigin { - self.credential.origin.clone() - } } diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 47f2568c8..3f92d95bd 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,8 +1,6 @@ use reqwest::Url; use thiserror::Error; -use crate::shared::{BuildErrors, CheckResponse, GraphRef}; - /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] pub enum RoverClientError { @@ -60,11 +58,14 @@ pub enum RoverClientError { /// The Studio API could not find a variant for a graph #[error( - "The graph registry does not contain variant \"{}\" for graph \"{}\"", graph_ref.variant, graph_ref.name + "The graph registry does not contain variant \"{invalid_variant}\" for graph \"{graph}\"" )] NoSchemaForVariant { - /// The graph ref. - graph_ref: GraphRef, + /// The name of the graph. + graph: String, + + /// The non-existent variant. + invalid_variant: String, /// Valid variants. valid_variants: Vec, @@ -92,25 +93,15 @@ pub enum RoverClientError { /// when someone provides a bad graph/variant combination or isn't /// validated properly, we don't know which reason is at fault for data.service /// being empty, so this error tells them to check both. - #[error("Could not find graph with name \"{graph_ref}\"")] - GraphNotFound { graph_ref: GraphRef }, + #[error("Could not find graph with name \"{graph}\"")] + NoService { graph: String }, /// if someone attempts to get a core schema from a supergraph that has - /// no successful build in the API, we return this error. - #[error("No supergraph SDL exists for \"{graph_ref}\" because its subgraphs failed to build.")] - NoSupergraphBuilds { - graph_ref: GraphRef, - source: BuildErrors, - }, - - #[error("Encountered {} while trying to build a supergraph.", .source.length_string())] - BuildErrors { source: BuildErrors }, - - #[error("Encountered {} while trying to build subgraph \"{subgraph}\" into supergraph \"{graph_ref}\".", .source.length_string())] - SubgraphBuildErrors { - subgraph: String, - graph_ref: GraphRef, - source: BuildErrors, + /// no composition results we return this error. + #[error("No supergraph SDL exists for \"{graph}\" because its subgraphs failed to compose.")] + NoCompositionPublishes { + graph: String, + composition_errors: Vec, }, /// This error occurs when the Studio API returns no implementing services for a graph @@ -123,9 +114,9 @@ pub enum RoverClientError { /// `can_operation_convert` is only set to true when a non-federated graph /// was encountered during an operation that could potentially convert a non-federated graph /// to a federated graph. - #[error("The graph `{graph_ref}` is a non-federated graph. This operation is only possible for federated graphs.")] + #[error("The graph `{graph}` is a non-federated graph. This operation is only possible for federated graphs.")] ExpectedFederatedGraph { - graph_ref: GraphRef, + graph: String, can_operation_convert: bool, }, @@ -133,27 +124,6 @@ pub enum RoverClientError { #[error("Invalid ChangeSeverity.")] InvalidSeverity, - /// The user supplied an invalid validation period - #[error("You can only specify a duration as granular as seconds.")] - ValidationPeriodTooGranular, - - /// The user supplied an invalid validation period duration - #[error(transparent)] - InvalidValidationPeriodDuration(#[from] humantime::DurationError), - - /// While checking the proposed schema, we encountered changes that would break existing operations - // we nest the CheckResponse here because we want to print the entire response even - // if there were failures - #[error("{}", operation_check_error_msg(.check_response))] - OperationCheckFailure { - graph_ref: GraphRef, - check_response: CheckResponse, - }, - - /// This error occurs when a user has a malformed Graph Ref - #[error("Graph IDs must be in the format or @, where can only contain letters, numbers, or the characters `-` or `_`, and must be 64 characters or less. must be 64 characters or less.")] - InvalidGraphRef, - /// This error occurs when a user has a malformed API key #[error( "The API key you provided is malformed. An API key must have three parts separated by a colon." @@ -164,26 +134,10 @@ pub enum RoverClientError { #[error("The registry did not recognize the provided API key")] InvalidKey, - /// Could not parse the latest version - #[error("Could not parse the latest release version")] - UnparseableReleaseVersion { source: semver::Error }, - - /// Encountered an error while processing the request for the latest version - #[error("There's something wrong with the latest GitHub release URL")] - BadReleaseUrl, + /// could not parse the latest version + #[error("Could not get the latest release version")] + UnparseableReleaseVersion, #[error("This endpoint doesn't support subgraph introspection via the Query._service field")] SubgraphIntrospectionNotAvailable, } - -fn operation_check_error_msg(check_response: &CheckResponse) -> String { - let failure_count = check_response.get_failure_count(); - let plural = match failure_count { - 1 => "", - _ => "s", - }; - format!( - "This operation check has encountered {} schema change{} that would break operations from existing client traffic.", - failure_count, plural - ) -} diff --git a/crates/rover-client/src/headers.rs b/crates/rover-client/src/headers.rs new file mode 100644 index 000000000..999e8143b --- /dev/null +++ b/crates/rover-client/src/headers.rs @@ -0,0 +1,61 @@ +use crate::RoverClientError; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use std::collections::HashMap; + +const JSON_CONTENT_TYPE: &str = "application/json"; +const CLIENT_NAME: &str = "rover-client"; + +/// Function for building a [HeaderMap] for making http requests. Use for +/// Generic requests to any graphql endpoint. +/// +/// Takes a single argument, list of header key/value pairs +pub fn build(header_map: &HashMap) -> Result { + let mut headers = HeaderMap::new(); + + // this should be consistent for any graphql requests + let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; + headers.append("Content-Type", content_type); + + for (key, value) in header_map { + let header_key = HeaderName::from_bytes(key.as_bytes())?; + let header_value = HeaderValue::from_str(value)?; + headers.append(header_key, header_value); + } + + Ok(headers) +} + +/// Function for building a [HeaderMap] for making http requests. Use for making +/// requests to Apollo Studio. We're leaving this separate from `build` since we +/// need to be able to mark the api_key as sensitive (at the bottom) +/// +/// Takes an `api_key` and a `client_version`, and returns a [HeaderMap]. +pub fn build_studio_headers( + api_key: &str, + client_version: &str, +) -> Result { + let mut headers = HeaderMap::new(); + + let content_type = HeaderValue::from_str(JSON_CONTENT_TYPE)?; + headers.insert("Content-Type", content_type); + + // The headers "apollographql-client-name" and "apollographql-client-version" + // are used for client identification in Apollo Studio. + + // This provides metrics in Studio that help keep track of what parts of the schema + // Rover uses, which ensures future changes to the API do not break Rover users. + // more info here: + // https://www.apollographql.com/docs/studio/client-awareness/#using-apollo-server-and-apollo-client + + let client_name = HeaderValue::from_str(CLIENT_NAME)?; + headers.insert("apollographql-client-name", client_name); + tracing::debug!(?client_version); + let client_version = HeaderValue::from_str(client_version)?; + headers.insert("apollographql-client-version", client_version); + + let mut api_key = HeaderValue::from_str(api_key)?; + api_key.set_sensitive(true); + headers.insert("x-api-key", api_key); + + Ok(headers) +} diff --git a/crates/rover-client/src/introspection/mod.rs b/crates/rover-client/src/introspection/mod.rs new file mode 100644 index 000000000..dd9d7b111 --- /dev/null +++ b/crates/rover-client/src/introspection/mod.rs @@ -0,0 +1,2 @@ +mod schema; +pub use schema::Schema; diff --git a/crates/rover-client/src/introspection/schema.rs b/crates/rover-client/src/introspection/schema.rs new file mode 100644 index 000000000..c5744f894 --- /dev/null +++ b/crates/rover-client/src/introspection/schema.rs @@ -0,0 +1,271 @@ +//! Schema encoding module used to work with Introspection result. +//! +//! More information on Schema Definition language(SDL) can be found in [this +//! documentation](https://www.apollographql.com/docs/apollo-server/schema/schema/). +//! +use crate::query::graph::introspect; +use sdl_encoder::{ + Directive, EnumDef, EnumValue, Field, InputField, InputObjectDef, InputValue, InterfaceDef, + ObjectDef, ScalarDef, Schema as SDL, SchemaDef, Type_, UnionDef, +}; +use serde::Deserialize; +use std::convert::TryFrom; + +pub type FullTypeField = introspect::introspection_query::FullTypeFields; +pub type FullTypeInputField = introspect::introspection_query::FullTypeInputFields; +pub type FullTypeFieldArg = introspect::introspection_query::FullTypeFieldsArgs; +pub type IntrospectionResult = introspect::introspection_query::ResponseData; +pub type SchemaMutationType = introspect::introspection_query::IntrospectionQuerySchemaMutationType; +pub type SchemaQueryType = introspect::introspection_query::IntrospectionQuerySchemaQueryType; +pub type SchemaType = introspect::introspection_query::IntrospectionQuerySchemaTypes; +pub type SchemaDirective = introspect::introspection_query::IntrospectionQuerySchemaDirectives; +pub type SchemaSubscriptionType = + introspect::introspection_query::IntrospectionQuerySchemaSubscriptionType; +pub type __TypeKind = introspect::introspection_query::__TypeKind; + +// Represents GraphQL types we will not be encoding to SDL. +const GRAPHQL_NAMED_TYPES: [&str; 12] = [ + "__Schema", + "__Type", + "__TypeKind", + "__Field", + "__InputValue", + "__EnumValue", + "__DirectiveLocation", + "__Directive", + "Boolean", + "String", + "Int", + "ID", +]; + +// Represents GraphQL directives we will not be encoding to SDL. +const SPECIFIED_DIRECTIVES: [&str; 3] = ["skip", "include", "deprecated"]; + +/// A representation of a GraphQL Schema. +/// +/// Contains Schema Types and Directives. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Schema { + types: Vec, + directives: Vec, + mutation_type: Option, + query_type: SchemaQueryType, + subscription_type: Option, +} + +impl Schema { + /// Encode Schema into an SDL. + pub fn encode(self) -> String { + let mut sdl = SDL::new(); + + // When we have a defined mutation and subscription, we record + // everything to Schema Definition. + // https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/ + if self.mutation_type.is_some() | self.subscription_type.is_some() { + let mut schema_def = SchemaDef::new(); + if let Some(mutation_type) = self.mutation_type { + schema_def.mutation(mutation_type.name.unwrap()); + } + if let Some(subscription_type) = self.subscription_type { + schema_def.subscription(subscription_type.name.unwrap()); + } + if let Some(name) = self.query_type.name { + schema_def.query(name); + } + sdl.schema(schema_def); + } else if let Some(name) = self.query_type.name { + // If we don't have a mutation or a subscription, but do have a + // query type, only create a Schema Definition when it's something + // other than `Query`. + if name != "Query" { + let mut schema_def = SchemaDef::new(); + schema_def.query(name); + sdl.schema(schema_def); + } + } + + // Exclude GraphQL directives like 'skip' and 'include' before encoding directives. + self.directives + .into_iter() + .filter(|directive| !SPECIFIED_DIRECTIVES.contains(&directive.name.as_str())) + .for_each(|directive| Self::encode_directives(directive, &mut sdl)); + + // Exclude GraphQL named types like __Schema before encoding full type. + self.types + .into_iter() + .filter(|type_| match type_.full_type.name.as_deref() { + Some(name) => !GRAPHQL_NAMED_TYPES.contains(&name), + None => false, + }) + .for_each(|type_| Self::encode_full_type(type_, &mut sdl)); + + sdl.finish() + } + + fn encode_directives(directive: SchemaDirective, sdl: &mut SDL) { + let mut directive_ = Directive::new(directive.name); + directive_.description(directive.description); + for location in directive.locations { + // Location is of a __DirectiveLocation enum that doesn't implement + // Display (meaning we can't just do .to_string). This next line + // just forces it into a String with format! debug. + directive_.location(format!("{:?}", location)); + } + + sdl.directive(directive_) + } + + fn encode_full_type(type_: SchemaType, sdl: &mut SDL) { + let ty = type_.full_type; + + match ty.kind { + __TypeKind::OBJECT => { + let mut object_def = ObjectDef::new(ty.name.unwrap_or_else(String::new)); + object_def.description(ty.description); + if let Some(interfaces) = ty.interfaces { + for interface in interfaces { + object_def.interface(interface.type_ref.name.unwrap_or_else(String::new)); + } + } + if let Some(field) = ty.fields { + for f in field { + let field_def = Self::encode_field(f); + object_def.field(field_def); + } + sdl.object(object_def); + } + } + __TypeKind::INPUT_OBJECT => { + let mut input_def = InputObjectDef::new(ty.name.unwrap_or_else(String::new)); + input_def.description(ty.description); + if let Some(field) = ty.input_fields { + for f in field { + let input_field_def = Self::encode_input_field(f); + input_def.field(input_field_def); + } + sdl.input(input_def); + } + } + __TypeKind::INTERFACE => { + let mut interface_def = InterfaceDef::new(ty.name.unwrap_or_else(String::new)); + interface_def.description(ty.description); + if let Some(interfaces) = ty.interfaces { + for interface in interfaces { + interface_def + .interface(interface.type_ref.name.unwrap_or_else(String::new)); + } + } + if let Some(field) = ty.fields { + for f in field { + let field_def = Self::encode_field(f); + interface_def.field(field_def); + } + sdl.interface(interface_def); + } + } + __TypeKind::SCALAR => { + let mut scalar_def = ScalarDef::new(ty.name.unwrap_or_else(String::new)); + scalar_def.description(ty.description); + sdl.scalar(scalar_def); + } + __TypeKind::UNION => { + let mut union_def = UnionDef::new(ty.name.unwrap_or_else(String::new)); + union_def.description(ty.description); + if let Some(possible_types) = ty.possible_types { + for possible_type in possible_types { + union_def.member(possible_type.type_ref.name.unwrap_or_else(String::new)); + } + } + sdl.union(union_def); + } + __TypeKind::ENUM => { + let mut enum_def = EnumDef::new(ty.name.unwrap_or_else(String::new)); + if let Some(enums) = ty.enum_values { + for enum_ in enums { + let mut enum_value = EnumValue::new(enum_.name); + enum_value.description(enum_.description); + + if enum_.is_deprecated { + enum_value.deprecated(enum_.deprecation_reason); + } + + enum_def.value(enum_value); + } + } + sdl.enum_(enum_def); + } + _ => (), + } + } + + fn encode_field(field: FullTypeField) -> Field { + let ty = Self::encode_type(field.type_.type_ref); + let mut field_def = Field::new(field.name, ty); + + for value in field.args { + let field_value = Self::encode_arg(value); + field_def.arg(field_value); + } + + if field.is_deprecated { + field_def.deprecated(field.deprecation_reason); + } + field_def.description(field.description); + field_def + } + + fn encode_input_field(field: FullTypeInputField) -> InputField { + let ty = Self::encode_type(field.input_value.type_.type_ref); + let mut field_def = InputField::new(field.input_value.name, ty); + + field_def.default(field.input_value.default_value); + field_def.description(field.input_value.description); + field_def + } + + fn encode_arg(value: FullTypeFieldArg) -> InputValue { + let ty = Self::encode_type(value.input_value.type_.type_ref); + let mut value_def = InputValue::new(value.input_value.name, ty); + + value_def.default(value.input_value.default_value); + value_def.description(value.input_value.description); + value_def + } + + fn encode_type(ty: impl introspect::OfType) -> Type_ { + use introspect::introspection_query::__TypeKind::*; + match ty.kind() { + SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT => Type_::NamedType { + name: ty.name().unwrap().to_string(), + }, + NON_NULL => { + let ty = Self::encode_type(ty.of_type().unwrap()); + Type_::NonNull { ty: Box::new(ty) } + } + LIST => { + let ty = Self::encode_type(ty.of_type().unwrap()); + Type_::List { ty: Box::new(ty) } + } + Other(ty) => panic!("Unknown type: {}", ty), + } + } +} + +impl TryFrom for Schema { + type Error = &'static str; + + fn try_from(src: IntrospectionResult) -> Result { + match src.schema { + Some(s) => Ok(Self { + types: s.types, + directives: s.directives, + mutation_type: s.mutation_type, + query_type: s.query_type, + subscription_type: s.subscription_type, + }), + None => Err("Schema not found in Introspection Result."), + } + } +} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index 5c8f3e490..0b59cf52f 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -2,20 +2,22 @@ //! HTTP client for making GraphQL requests for the Rover CLI tool. -mod error; - /// Module related to blocking http client. pub mod blocking; +mod error; + +/// Module related to constructing request headers. +pub mod headers; + +/// Module related to building an SDL from an introspection response. +pub mod introspection; /// Module for client related errors. pub use error::RoverClientError; #[allow(clippy::upper_case_acronyms)] /// Module for actually querying studio -pub mod operations; +pub mod query; /// Module for getting release info pub mod releases; - -/// Module for shared functionality -pub mod shared; diff --git a/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql b/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql deleted file mode 100644 index 4007e1af7..000000000 --- a/crates/rover-client/src/operations/config/is_federated/is_federated_query.graphql +++ /dev/null @@ -1,7 +0,0 @@ -query IsFederatedGraph($graph_id: ID!, $variant: String!) { - service(id: $graph_id) { - implementingServices(graphVariant: $variant) { - __typename - } - } -} \ No newline at end of file diff --git a/crates/rover-client/src/operations/config/is_federated/mod.rs b/crates/rover-client/src/operations/config/is_federated/mod.rs deleted file mode 100644 index 36375e300..000000000 --- a/crates/rover-client/src/operations/config/is_federated/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub(crate) use runner::run; -pub(crate) use types::IsFederatedInput; diff --git a/crates/rover-client/src/operations/config/is_federated/types.rs b/crates/rover-client/src/operations/config/is_federated/types.rs deleted file mode 100644 index 3f6f22e52..000000000 --- a/crates/rover-client/src/operations/config/is_federated/types.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::operations::config::is_federated::runner::is_federated_graph; -use crate::shared::GraphRef; - -type QueryVariables = is_federated_graph::Variables; - -#[derive(Debug, Clone, PartialEq)] -pub struct IsFederatedInput { - pub graph_ref: GraphRef, -} - -impl From for QueryVariables { - fn from(input: IsFederatedInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - } - } -} diff --git a/crates/rover-client/src/operations/config/who_am_i/mod.rs b/crates/rover-client/src/operations/config/who_am_i/mod.rs deleted file mode 100644 index ccb47268d..000000000 --- a/crates/rover-client/src/operations/config/who_am_i/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::{Actor, ConfigWhoAmIInput, RegistryIdentity}; diff --git a/crates/rover-client/src/operations/config/who_am_i/types.rs b/crates/rover-client/src/operations/config/who_am_i/types.rs deleted file mode 100644 index d2d3c4e93..000000000 --- a/crates/rover-client/src/operations/config/who_am_i/types.rs +++ /dev/null @@ -1,31 +0,0 @@ -use super::runner::config_who_am_i_query; - -use houston::CredentialOrigin; - -pub(crate) type QueryResponseData = config_who_am_i_query::ResponseData; -pub(crate) type QueryVariables = config_who_am_i_query::Variables; -pub(crate) type QueryActorType = config_who_am_i_query::ActorType; - -#[derive(Debug, PartialEq)] -pub struct RegistryIdentity { - pub id: String, - pub graph_title: Option, - pub key_actor_type: Actor, - pub credential_origin: CredentialOrigin, -} - -#[derive(Debug, PartialEq)] -pub enum Actor { - GRAPH, - USER, - OTHER, -} - -#[derive(Debug, PartialEq)] -pub struct ConfigWhoAmIInput {} - -impl From for QueryVariables { - fn from(_input: ConfigWhoAmIInput) -> Self { - Self {} - } -} diff --git a/crates/rover-client/src/operations/graph/check/mod.rs b/crates/rover-client/src/operations/graph/check/mod.rs deleted file mode 100644 index 23c23b7e8..000000000 --- a/crates/rover-client/src/operations/graph/check/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::GraphCheckInput; diff --git a/crates/rover-client/src/operations/graph/check/runner.rs b/crates/rover-client/src/operations/graph/check/runner.rs deleted file mode 100644 index 152ab808e..000000000 --- a/crates/rover-client/src/operations/graph/check/runner.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::blocking::StudioClient; -use crate::operations::graph::check::types::{GraphCheckInput, MutationResponseData}; -use crate::shared::{CheckResponse, GraphRef}; -use crate::RoverClientError; - -use graphql_client::*; - -type Timestamp = String; -#[derive(GraphQLQuery)] -// The paths are relative to the directory where your `Cargo.toml` is located. -// Both json and the GraphQL schema language are supported as sources for the schema -#[graphql( - query_path = "src/operations/graph/check/check_mutation.graphql", - schema_path = ".schema/schema.graphql", - response_derives = "PartialEq, Debug, Serialize, Deserialize", - deprecated = "warn" -)] -/// This struct is used to generate the module containing `Variables` and -/// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. graph_check_mutation -pub(crate) struct GraphCheckMutation; - -/// The main function to be used from this module. -/// This function takes a proposed schema and validates it against a published -/// schema. -pub fn run( - input: GraphCheckInput, - client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; - get_check_response_from_data(data, graph_ref) -} - -fn get_check_response_from_data( - data: MutationResponseData, - graph_ref: GraphRef, -) -> Result { - let service = data.service.ok_or(RoverClientError::GraphNotFound { - graph_ref: graph_ref.clone(), - })?; - let target_url = service.check_schema.target_url; - - let diff_to_previous = service.check_schema.diff_to_previous; - - let operation_check_count = diff_to_previous.number_of_checked_operations.unwrap_or(0) as u64; - - let result = diff_to_previous.severity.into(); - let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); - for change in diff_to_previous.changes { - changes.push(change.into()); - } - - CheckResponse::try_new( - target_url, - operation_check_count, - changes, - result, - graph_ref, - ) -} diff --git a/crates/rover-client/src/operations/graph/check/types.rs b/crates/rover-client/src/operations/graph/check/types.rs deleted file mode 100644 index 9991eed14..000000000 --- a/crates/rover-client/src/operations/graph/check/types.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::operations::graph::check::runner::graph_check_mutation; -use crate::shared::{ChangeSeverity, CheckConfig, GitContext, GraphRef, SchemaChange}; - -#[derive(Debug, Clone, PartialEq)] -pub struct GraphCheckInput { - pub graph_ref: GraphRef, - pub proposed_schema: String, - pub git_context: GitContext, - pub config: CheckConfig, -} - -impl From for MutationVariables { - fn from(input: GraphCheckInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: Some(input.graph_ref.variant), - proposed_schema: Some(input.proposed_schema), - config: input.config.into(), - git_context: input.git_context.into(), - } - } -} - -type MutationConfig = graph_check_mutation::HistoricQueryParameters; -impl From for MutationConfig { - fn from(input: CheckConfig) -> Self { - Self { - query_count_threshold: input.query_count_threshold, - query_count_threshold_percentage: input.query_count_threshold_percentage, - from: Some( - input - .validation_period - .clone() - .unwrap_or_default() - .from - .to_string(), - ), - to: Some(input.validation_period.unwrap_or_default().to.to_string()), - // we don't support configuring these, but we can't leave them out - excluded_clients: None, - ignored_operations: None, - included_variants: None, - } - } -} - -type MutationVariables = graph_check_mutation::Variables; -pub(crate) type MutationResponseData = graph_check_mutation::ResponseData; - -pub(crate) type MutationChangeSeverity = graph_check_mutation::ChangeSeverity; -impl From for ChangeSeverity { - fn from(severity: MutationChangeSeverity) -> Self { - match severity { - MutationChangeSeverity::NOTICE => ChangeSeverity::PASS, - MutationChangeSeverity::FAILURE => ChangeSeverity::FAIL, - _ => ChangeSeverity::unreachable(), - } - } -} - -impl From for MutationChangeSeverity { - fn from(severity: ChangeSeverity) -> Self { - match severity { - ChangeSeverity::PASS => MutationChangeSeverity::NOTICE, - ChangeSeverity::FAIL => MutationChangeSeverity::FAILURE, - } - } -} - -type MutationSchemaChange = - graph_check_mutation::GraphCheckMutationServiceCheckSchemaDiffToPreviousChanges; -impl From for MutationSchemaChange { - fn from(schema_change: SchemaChange) -> MutationSchemaChange { - MutationSchemaChange { - severity: schema_change.severity.into(), - code: schema_change.code, - description: schema_change.description, - } - } -} - -impl From for SchemaChange { - fn from(schema_change: MutationSchemaChange) -> SchemaChange { - SchemaChange { - severity: schema_change.severity.into(), - code: schema_change.code, - description: schema_change.description, - } - } -} - -type MutationGitContextInput = graph_check_mutation::GitContextInput; -impl From for MutationGitContextInput { - fn from(git_context: GitContext) -> MutationGitContextInput { - MutationGitContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} diff --git a/crates/rover-client/src/operations/graph/fetch/fetch_query.graphql b/crates/rover-client/src/operations/graph/fetch/fetch_query.graphql deleted file mode 100644 index 8a4cc851e..000000000 --- a/crates/rover-client/src/operations/graph/fetch/fetch_query.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query GraphFetchQuery($graph_id: ID!, $variant: String) { - frontendUrlRoot, - service(id: $graph_id) { - schema(tag: $variant) { - document - } - variants { - name - } - } -} diff --git a/crates/rover-client/src/operations/graph/fetch/mod.rs b/crates/rover-client/src/operations/graph/fetch/mod.rs deleted file mode 100644 index 7713aca96..000000000 --- a/crates/rover-client/src/operations/graph/fetch/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::GraphFetchInput; diff --git a/crates/rover-client/src/operations/graph/fetch/types.rs b/crates/rover-client/src/operations/graph/fetch/types.rs deleted file mode 100644 index bbfe7fc7b..000000000 --- a/crates/rover-client/src/operations/graph/fetch/types.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::operations::graph::fetch::runner::graph_fetch_query; -use crate::shared::GraphRef; - -type QueryVariables = graph_fetch_query::Variables; - -#[derive(Debug, Clone, PartialEq)] -pub struct GraphFetchInput { - pub graph_ref: GraphRef, -} - -impl From for QueryVariables { - fn from(input: GraphFetchInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: Some(input.graph_ref.variant), - } - } -} diff --git a/crates/rover-client/src/operations/graph/introspect/mod.rs b/crates/rover-client/src/operations/graph/introspect/mod.rs deleted file mode 100644 index 541b12ff2..000000000 --- a/crates/rover-client/src/operations/graph/introspect/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod runner; -mod schema; -mod types; - -pub use runner::run; -pub use schema::Schema; -pub use types::{GraphIntrospectInput, GraphIntrospectResponse}; diff --git a/crates/rover-client/src/operations/graph/introspect/runner.rs b/crates/rover-client/src/operations/graph/introspect/runner.rs deleted file mode 100644 index 2734beff5..000000000 --- a/crates/rover-client/src/operations/graph/introspect/runner.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::blocking::GraphQLClient; -use crate::operations::graph::introspect::{types::*, Schema}; -use crate::RoverClientError; -use graphql_client::*; - -use std::convert::TryFrom; - -#[derive(GraphQLQuery)] -#[graphql( - query_path = "src/operations/graph/introspect/introspect_query.graphql", - schema_path = "src/operations/graph/introspect/introspect_schema.graphql", - response_derives = "PartialEq, Debug, Serialize, Deserialize", - deprecated = "warn" -)] - -/// This struct is used to generate the module containing `Variables` and -/// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. graph_introspect_query -pub(crate) struct GraphIntrospectQuery; - -/// The main function to be used from this module. This function fetches a -/// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( - input: GraphIntrospectInput, - client: &GraphQLClient, -) -> Result { - let variables = input.clone().into(); - let response_data = client.post::(variables, &input.headers)?; - build_response(response_data) -} - -fn build_response( - response: QueryResponseData, -) -> Result { - match Schema::try_from(response) { - Ok(schema) => Ok(GraphIntrospectResponse { - schema_sdl: schema.encode(), - }), - Err(msg) => Err(RoverClientError::IntrospectionError { msg: msg.into() }), - } -} diff --git a/crates/rover-client/src/operations/graph/introspect/types.rs b/crates/rover-client/src/operations/graph/introspect/types.rs deleted file mode 100644 index 73fd29965..000000000 --- a/crates/rover-client/src/operations/graph/introspect/types.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::collections::HashMap; - -use crate::operations::graph::introspect::runner::graph_introspect_query; - -pub(crate) type QueryResponseData = graph_introspect_query::ResponseData; -pub(crate) type QueryVariables = graph_introspect_query::Variables; - -#[derive(Debug, Clone, PartialEq)] -pub struct GraphIntrospectInput { - pub headers: HashMap, -} - -impl From for QueryVariables { - fn from(_input: GraphIntrospectInput) -> Self { - Self {} - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct GraphIntrospectResponse { - pub schema_sdl: String, -} diff --git a/crates/rover-client/src/operations/graph/publish/mod.rs b/crates/rover-client/src/operations/graph/publish/mod.rs deleted file mode 100644 index e4fe3abb2..000000000 --- a/crates/rover-client/src/operations/graph/publish/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::{ - ChangeSummary, FieldChanges, GraphPublishInput, GraphPublishResponse, TypeChanges, -}; diff --git a/crates/rover-client/src/operations/graph/publish/types.rs b/crates/rover-client/src/operations/graph/publish/types.rs deleted file mode 100644 index b51b0808b..000000000 --- a/crates/rover-client/src/operations/graph/publish/types.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::operations::graph::publish::runner::graph_publish_mutation; -use crate::shared::{GitContext, GraphRef}; - -use serde::Serialize; - -use std::fmt; - -#[derive(Clone, Debug, PartialEq)] -pub struct GraphPublishInput { - pub graph_ref: GraphRef, - pub proposed_schema: String, - pub git_context: GitContext, -} - -type MutationVariables = graph_publish_mutation::Variables; -impl From for MutationVariables { - fn from(input: GraphPublishInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - proposed_schema: input.proposed_schema, - git_context: input.git_context.into(), - } - } -} - -type GraphPublishContextInput = graph_publish_mutation::GitContextInput; -impl From for GraphPublishContextInput { - fn from(git_context: GitContext) -> GraphPublishContextInput { - GraphPublishContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} - -#[derive(Clone, Serialize, Debug, PartialEq)] -pub struct GraphPublishResponse { - pub api_schema_hash: String, - #[serde(flatten)] - pub change_summary: ChangeSummary, -} - -#[derive(Clone, Serialize, Debug, PartialEq)] -pub struct ChangeSummary { - pub field_changes: FieldChanges, - pub type_changes: TypeChanges, -} - -impl ChangeSummary { - pub(crate) fn none() -> ChangeSummary { - ChangeSummary { - field_changes: FieldChanges::none(), - type_changes: TypeChanges::none(), - } - } - - pub(crate) fn is_none(&self) -> bool { - self.field_changes.is_none() && self.type_changes.is_none() - } -} - -impl fmt::Display for ChangeSummary { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.is_none() { - write!(f, "[No Changes]") - } else { - write!(f, "[{}, {}]", &self.field_changes, &self.type_changes) - } - } -} - -#[derive(Clone, Serialize, Debug, PartialEq)] -pub struct FieldChanges { - pub additions: u64, - pub removals: u64, - pub edits: u64, -} - -impl FieldChanges { - pub(crate) fn none() -> FieldChanges { - FieldChanges { - additions: 0, - removals: 0, - edits: 0, - } - } - - pub(crate) fn with_diff(additions: u64, removals: u64, edits: u64) -> FieldChanges { - FieldChanges { - additions, - removals, - edits, - } - } - - pub(crate) fn is_none(&self) -> bool { - self.additions == 0 && self.removals == 0 && self.edits == 0 - } -} - -impl fmt::Display for FieldChanges { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Fields: +{} -{} â–³ {}", - &self.additions, &self.removals, &self.edits - ) - } -} - -#[derive(Clone, Serialize, Debug, PartialEq)] -pub struct TypeChanges { - pub additions: u64, - pub removals: u64, - pub edits: u64, -} - -impl TypeChanges { - pub(crate) fn none() -> TypeChanges { - TypeChanges { - additions: 0, - removals: 0, - edits: 0, - } - } - - pub(crate) fn with_diff(additions: u64, removals: u64, edits: u64) -> TypeChanges { - TypeChanges { - additions, - removals, - edits, - } - } - - pub(crate) fn is_none(&self) -> bool { - self.additions == 0 && self.removals == 0 && self.edits == 0 - } -} - -impl fmt::Display for TypeChanges { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Types: +{} -{} â–³ {}", - &self.additions, &self.removals, &self.edits - ) - } -} diff --git a/crates/rover-client/src/operations/subgraph/check/mod.rs b/crates/rover-client/src/operations/subgraph/check/mod.rs deleted file mode 100644 index ebbc0d6e6..000000000 --- a/crates/rover-client/src/operations/subgraph/check/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::SubgraphCheckInput; diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs deleted file mode 100644 index b446ca7e9..000000000 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ /dev/null @@ -1,117 +0,0 @@ -use super::types::*; -use crate::blocking::StudioClient; -use crate::operations::{ - config::is_federated::{self, IsFederatedInput}, - subgraph::check::types::MutationResponseData, -}; -use crate::shared::{BuildError, CheckResponse, GraphRef, SchemaChange}; -use crate::RoverClientError; - -use graphql_client::*; - -type Timestamp = String; -#[derive(GraphQLQuery)] -// The paths are relative to the directory where your `Cargo.toml` is located. -// Both json and the GraphQL schema language are supported as sources for the schema -#[graphql( - query_path = "src/operations/subgraph/check/check_mutation.graphql", - schema_path = ".schema/schema.graphql", - response_derives = "PartialEq, Debug, Serialize, Deserialize", - deprecated = "warn" -)] -/// This struct is used to generate the module containing `Variables` and -/// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. subgraph_check_mutation -pub(crate) struct SubgraphCheckMutation; - -/// The main function to be used from this module. -/// This function takes a proposed schema and validates it against a published -/// schema. -pub fn run( - input: SubgraphCheckInput, - client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let subgraph = input.subgraph.clone(); - // This response is used to check whether or not the current graph is federated. - let is_federated = is_federated::run( - IsFederatedInput { - graph_ref: graph_ref.clone(), - }, - client, - )?; - if !is_federated { - return Err(RoverClientError::ExpectedFederatedGraph { - graph_ref, - can_operation_convert: false, - }); - } - let variables = input.into(); - let data = client.post::(variables)?; - get_check_response_from_data(data, graph_ref, subgraph) -} - -fn get_check_response_from_data( - data: MutationResponseData, - graph_ref: GraphRef, - subgraph: String, -) -> Result { - let service = data.service.ok_or(RoverClientError::GraphNotFound { - graph_ref: graph_ref.clone(), - })?; - - // for some reason this is a `Vec>` - // we convert this to just `Vec` because the `None` - // errors would be useless. - let query_composition_errors: Vec = service - .check_partial_schema - .composition_validation_result - .errors; - - if query_composition_errors.is_empty() { - let check_schema_result = service.check_partial_schema.check_schema_result.ok_or( - RoverClientError::MalformedResponse { - null_field: "service.check_partial_schema.check_schema_result".to_string(), - }, - )?; - - let diff_to_previous = check_schema_result.diff_to_previous; - - let operation_check_count = - diff_to_previous.number_of_checked_operations.unwrap_or(0) as u64; - - let result = diff_to_previous.severity.into(); - - let mut changes = Vec::with_capacity(diff_to_previous.changes.len()); - for change in diff_to_previous.changes { - changes.push(SchemaChange { - code: change.code, - severity: change.severity.into(), - description: change.description, - }); - } - - CheckResponse::try_new( - check_schema_result.target_url, - operation_check_count, - changes, - result, - graph_ref, - ) - } else { - let num_failures = query_composition_errors.len(); - - let mut build_errors = Vec::with_capacity(num_failures); - for query_composition_error in query_composition_errors { - build_errors.push(BuildError::composition_error( - query_composition_error.message, - query_composition_error.code, - )); - } - Err(RoverClientError::SubgraphBuildErrors { - subgraph, - graph_ref, - source: build_errors.into(), - }) - } -} diff --git a/crates/rover-client/src/operations/subgraph/check/types.rs b/crates/rover-client/src/operations/subgraph/check/types.rs deleted file mode 100644 index c8aa519ba..000000000 --- a/crates/rover-client/src/operations/subgraph/check/types.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::operations::subgraph::check::runner::subgraph_check_mutation; -use crate::shared::{ChangeSeverity, CheckConfig, GitContext, GraphRef}; - -type MutationVariables = subgraph_check_mutation::Variables; - -pub(crate) type MutationResponseData = subgraph_check_mutation::ResponseData; -pub(crate) type MutationCompositionErrors = - subgraph_check_mutation::SubgraphCheckMutationServiceCheckPartialSchemaCompositionValidationResultErrors; - -type MutationSchema = subgraph_check_mutation::PartialSchemaInput; -type MutationConfig = subgraph_check_mutation::HistoricQueryParameters; - -pub(crate) type MutationChangeSeverity = subgraph_check_mutation::ChangeSeverity; -impl From for ChangeSeverity { - fn from(severity: MutationChangeSeverity) -> Self { - match severity { - MutationChangeSeverity::NOTICE => ChangeSeverity::PASS, - MutationChangeSeverity::FAILURE => ChangeSeverity::FAIL, - _ => ChangeSeverity::unreachable(), - } - } -} - -type MutationGitContextInput = subgraph_check_mutation::GitContextInput; -impl From for MutationGitContextInput { - fn from(git_context: GitContext) -> MutationGitContextInput { - MutationGitContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphCheckInput { - pub graph_ref: GraphRef, - pub subgraph: String, - pub proposed_schema: String, - pub git_context: GitContext, - pub config: CheckConfig, -} - -impl From for MutationVariables { - fn from(input: SubgraphCheckInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - subgraph: input.subgraph, - proposed_schema: MutationSchema { - sdl: Some(input.proposed_schema), - hash: None, - }, - config: MutationConfig { - query_count_threshold: input.config.query_count_threshold, - query_count_threshold_percentage: input.config.query_count_threshold_percentage, - from: Some( - input - .config - .validation_period - .clone() - .unwrap_or_default() - .from - .to_string(), - ), - to: Some( - input - .config - .validation_period - .unwrap_or_default() - .to - .to_string(), - ), - // we don't support configuring these, but we can't leave them out - excluded_clients: None, - ignored_operations: None, - included_variants: None, - }, - git_context: input.git_context.into(), - } - } -} diff --git a/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql b/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql deleted file mode 100644 index c5a15a429..000000000 --- a/crates/rover-client/src/operations/subgraph/delete/delete_mutation.graphql +++ /dev/null @@ -1,20 +0,0 @@ -mutation SubgraphDeleteMutation( - $graph_id: ID! - $variant: String! - $subgraph: String! - $dry_run: Boolean! -) { - service(id: $graph_id) { - removeImplementingServiceAndTriggerComposition( - graphVariant: $variant - name: $subgraph - dryRun: $dry_run - ) { - errors { - message - code - } - updatedGateway - } - } -} diff --git a/crates/rover-client/src/operations/subgraph/delete/mod.rs b/crates/rover-client/src/operations/subgraph/delete/mod.rs deleted file mode 100644 index db8cb0622..000000000 --- a/crates/rover-client/src/operations/subgraph/delete/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::{SubgraphDeleteInput, SubgraphDeleteResponse}; diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs deleted file mode 100644 index 9acfd9ed4..000000000 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::blocking::StudioClient; -use crate::operations::subgraph::delete::types::*; -use crate::shared::{BuildError, BuildErrors, GraphRef}; -use crate::RoverClientError; - -use graphql_client::*; - -#[derive(GraphQLQuery)] -// The paths are relative to the directory where your `Cargo.toml` is located. -// Both json and the GraphQL schema language are supported as sources for the schema -#[graphql( - query_path = "src/operations/subgraph/delete/delete_mutation.graphql", - schema_path = ".schema/schema.graphql", - response_derives = "PartialEq, Debug, Serialize, Deserialize", - deprecated = "warn" -)] -/// This struct is used to generate the module containing `Variables` and -/// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. subgraph_delete_mutation -pub(crate) struct SubgraphDeleteMutation; - -/// The main function to be used from this module. This function fetches a -/// schema from apollo studio and returns it in either sdl (default) or json format -pub fn run( - input: SubgraphDeleteInput, - client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; - let data = get_delete_data_from_response(response_data, graph_ref)?; - Ok(build_response(data)) -} - -fn get_delete_data_from_response( - response_data: subgraph_delete_mutation::ResponseData, - graph_ref: GraphRef, -) -> Result { - let service_data = response_data - .service - .ok_or(RoverClientError::GraphNotFound { graph_ref })?; - - Ok(service_data.remove_implementing_service_and_trigger_composition) -} - -fn build_response(response: MutationComposition) -> SubgraphDeleteResponse { - let build_errors: BuildErrors = response - .errors - .iter() - .filter_map(|error| { - error - .as_ref() - .map(|e| BuildError::composition_error(e.message.clone(), e.code.clone())) - }) - .collect(); - - SubgraphDeleteResponse { - supergraph_was_updated: response.updated_gateway, - build_errors, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::json; - - #[test] - fn get_delete_data_from_response_works() { - let json_response = json!({ - "service": { - "removeImplementingServiceAndTriggerComposition": { - "errors": [ - { - "message": "wow", - "code": null - }, - null, - { - "message": "boo", - "code": "BOO" - } - ], - "updatedGateway": false, - } - } - }); - let data: subgraph_delete_mutation::ResponseData = - serde_json::from_value(json_response).unwrap(); - let output = get_delete_data_from_response(data, mock_graph_ref()); - - assert!(output.is_ok()); - - let expected_response = MutationComposition { - errors: vec![ - Some(MutationCompositionErrors { - message: "wow".to_string(), - code: None, - }), - None, - Some(MutationCompositionErrors { - message: "boo".to_string(), - code: Some("BOO".to_string()), - }), - ], - updated_gateway: false, - }; - assert_eq!(output.unwrap(), expected_response); - } - - #[test] - fn build_response_works_with_successful_responses() { - let response = MutationComposition { - errors: vec![ - Some(MutationCompositionErrors { - message: "wow".to_string(), - code: None, - }), - None, - Some(MutationCompositionErrors { - message: "boo".to_string(), - code: Some("BOO".to_string()), - }), - ], - updated_gateway: false, - }; - - let parsed = build_response(response); - assert_eq!( - parsed, - SubgraphDeleteResponse { - build_errors: vec![ - BuildError::composition_error("wow".to_string(), None), - BuildError::composition_error("boo".to_string(), Some("BOO".to_string())) - ] - .into(), - supergraph_was_updated: false, - } - ); - } - - #[test] - fn build_response_works_with_failure_responses() { - let response = MutationComposition { - errors: vec![], - updated_gateway: true, - }; - - let parsed = build_response(response); - assert_eq!( - parsed, - SubgraphDeleteResponse { - build_errors: BuildErrors::new(), - supergraph_was_updated: true, - } - ); - } - - fn mock_graph_ref() -> GraphRef { - GraphRef { - name: "mygraph".to_string(), - variant: "current".to_string(), - } - } -} diff --git a/crates/rover-client/src/operations/subgraph/delete/types.rs b/crates/rover-client/src/operations/subgraph/delete/types.rs deleted file mode 100644 index 86052896e..000000000 --- a/crates/rover-client/src/operations/subgraph/delete/types.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{ - operations::subgraph::delete::runner::subgraph_delete_mutation, - shared::{BuildErrors, GraphRef}, -}; - -pub(crate) type MutationComposition = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerComposition; -pub(crate) type MutationVariables = subgraph_delete_mutation::Variables; - -use serde::Serialize; - -#[cfg(test)] -pub(crate) type MutationCompositionErrors = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerCompositionErrors; - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphDeleteInput { - pub graph_ref: GraphRef, - pub subgraph: String, - pub dry_run: bool, -} - -/// this struct contains all the info needed to print the result of the delete. -/// `updated_gateway` is true when composition succeeds and the gateway config -/// is updated for the gateway to consume. `composition_errors` is just a list -/// of strings for when there are build errors as a result of the delete. -#[derive(Debug, Clone, Serialize, PartialEq)] -pub struct SubgraphDeleteResponse { - pub supergraph_was_updated: bool, - - #[serde(skip_serializing)] - pub build_errors: BuildErrors, -} - -impl From for MutationVariables { - fn from(input: SubgraphDeleteInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - subgraph: input.subgraph, - dry_run: input.dry_run, - } - } -} diff --git a/crates/rover-client/src/operations/subgraph/fetch/mod.rs b/crates/rover-client/src/operations/subgraph/fetch/mod.rs deleted file mode 100644 index 65ea4d534..000000000 --- a/crates/rover-client/src/operations/subgraph/fetch/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::SubgraphFetchInput; diff --git a/crates/rover-client/src/operations/subgraph/fetch/types.rs b/crates/rover-client/src/operations/subgraph/fetch/types.rs deleted file mode 100644 index 68f0f1a75..000000000 --- a/crates/rover-client/src/operations/subgraph/fetch/types.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::shared::GraphRef; - -use super::runner::subgraph_fetch_query; - -pub(crate) type ServiceList = Vec; -pub(crate) type SubgraphFetchResponseData = subgraph_fetch_query::ResponseData; -pub(crate) type Services = subgraph_fetch_query::SubgraphFetchQueryServiceImplementingServices; -pub(crate) type QueryVariables = subgraph_fetch_query::Variables; - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphFetchInput { - pub graph_ref: GraphRef, - pub subgraph: String, -} - -impl From for QueryVariables { - fn from(input: SubgraphFetchInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - } - } -} diff --git a/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql b/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql deleted file mode 100644 index a5023b264..000000000 --- a/crates/rover-client/src/operations/subgraph/introspect/introspect_query.graphql +++ /dev/null @@ -1,6 +0,0 @@ -query SubgraphIntrospectQuery { - # eslint-disable-next-line - _service { - sdl - } -} \ No newline at end of file diff --git a/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql b/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql deleted file mode 100644 index 9b16951ea..000000000 --- a/crates/rover-client/src/operations/subgraph/introspect/introspect_schema.graphql +++ /dev/null @@ -1,16 +0,0 @@ -# eslint-disable-next-line -schema { - query: Query -} - -# eslint-disable-next-line -type Query { - # eslint-disable-next-line - _service: _Service -} - -# eslint-disable-next-line -type _Service { - # eslint-disable-next-line - sdl: String! -} diff --git a/crates/rover-client/src/operations/subgraph/introspect/mod.rs b/crates/rover-client/src/operations/subgraph/introspect/mod.rs deleted file mode 100644 index 047cf54f2..000000000 --- a/crates/rover-client/src/operations/subgraph/introspect/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(crate) mod runner; -pub(crate) mod types; - -pub use runner::run; -pub use types::{SubgraphIntrospectInput, SubgraphIntrospectResponse}; diff --git a/crates/rover-client/src/operations/subgraph/introspect/types.rs b/crates/rover-client/src/operations/subgraph/introspect/types.rs deleted file mode 100644 index 0b1a933c7..000000000 --- a/crates/rover-client/src/operations/subgraph/introspect/types.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::operations::subgraph::introspect::runner::subgraph_introspect_query; - -pub(crate) type QueryVariables = subgraph_introspect_query::Variables; -pub(crate) type QueryResponseData = subgraph_introspect_query::ResponseData; - -use std::collections::HashMap; - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphIntrospectInput { - pub headers: HashMap, -} - -impl From for QueryVariables { - fn from(_input: SubgraphIntrospectInput) -> Self { - Self {} - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphIntrospectResponse { - pub result: String, -} diff --git a/crates/rover-client/src/operations/subgraph/list/mod.rs b/crates/rover-client/src/operations/subgraph/list/mod.rs deleted file mode 100644 index dd493dd4c..000000000 --- a/crates/rover-client/src/operations/subgraph/list/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::{SubgraphInfo, SubgraphListInput, SubgraphListResponse, SubgraphUpdatedAt}; diff --git a/crates/rover-client/src/operations/subgraph/list/types.rs b/crates/rover-client/src/operations/subgraph/list/types.rs deleted file mode 100644 index 8e18dd9f2..000000000 --- a/crates/rover-client/src/operations/subgraph/list/types.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{operations::subgraph::list::runner::subgraph_list_query, shared::GraphRef}; - -pub(crate) type QuerySubgraphInfo = subgraph_list_query::SubgraphListQueryServiceImplementingServicesOnFederatedImplementingServicesServices; -pub(crate) type QueryResponseData = subgraph_list_query::ResponseData; -pub(crate) type QueryGraphType = subgraph_list_query::SubgraphListQueryServiceImplementingServices; - -type QueryVariables = subgraph_list_query::Variables; - -use chrono::{DateTime, Local, Utc}; -use serde::Serialize; - -#[derive(Clone, PartialEq, Debug)] -pub struct SubgraphListInput { - pub graph_ref: GraphRef, -} - -impl From for QueryVariables { - fn from(input: SubgraphListInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - } - } -} - -#[derive(Clone, Serialize, PartialEq, Debug)] -pub struct SubgraphListResponse { - pub subgraphs: Vec, - - #[serde(skip_serializing)] - pub root_url: String, - - #[serde(skip_serializing)] - pub graph_ref: GraphRef, -} - -#[derive(Clone, Serialize, PartialEq, Debug)] -pub struct SubgraphInfo { - pub name: String, - pub url: Option, // optional, and may not be a real url - pub updated_at: SubgraphUpdatedAt, -} - -#[derive(Clone, Serialize, PartialEq, Debug)] -pub struct SubgraphUpdatedAt { - pub local: Option>, - pub utc: Option>, -} diff --git a/crates/rover-client/src/operations/subgraph/publish/mod.rs b/crates/rover-client/src/operations/subgraph/publish/mod.rs deleted file mode 100644 index 0cf673b54..000000000 --- a/crates/rover-client/src/operations/subgraph/publish/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::{SubgraphPublishInput, SubgraphPublishResponse}; diff --git a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql b/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql deleted file mode 100644 index 0777dadd5..000000000 --- a/crates/rover-client/src/operations/subgraph/publish/publish_mutation.graphql +++ /dev/null @@ -1,30 +0,0 @@ -mutation SubgraphPublishMutation( - $graph_id: ID! - $variant: String! - $subgraph: String! - $url: String - $revision: String! - $schema: PartialSchemaInput! - $git_context: GitContextInput! -) { - service(id: $graph_id) { - upsertImplementingServiceAndTriggerComposition( - name: $subgraph - url: $url - revision: $revision - activePartialSchema: $schema - graphVariant: $variant - gitContext: $git_context - ) { - compositionConfig { - schemaHash - } - errors { - message - code - } - didUpdateGateway: updatedGateway - serviceWasCreated: wasCreated - } - } -} diff --git a/crates/rover-client/src/operations/subgraph/publish/types.rs b/crates/rover-client/src/operations/subgraph/publish/types.rs deleted file mode 100644 index 4dd7eb182..000000000 --- a/crates/rover-client/src/operations/subgraph/publish/types.rs +++ /dev/null @@ -1,63 +0,0 @@ -use super::runner::subgraph_publish_mutation; - -use crate::shared::{BuildErrors, GitContext, GraphRef}; - -pub(crate) type ResponseData = subgraph_publish_mutation::ResponseData; -pub(crate) type MutationVariables = subgraph_publish_mutation::Variables; -pub(crate) type UpdateResponse = subgraph_publish_mutation::SubgraphPublishMutationServiceUpsertImplementingServiceAndTriggerComposition; - -type SchemaInput = subgraph_publish_mutation::PartialSchemaInput; -type GitContextInput = subgraph_publish_mutation::GitContextInput; - -use serde::Serialize; - -#[derive(Debug, Clone, PartialEq)] -pub struct SubgraphPublishInput { - pub graph_ref: GraphRef, - pub subgraph: String, - pub url: Option, - pub schema: String, - pub git_context: GitContext, - pub convert_to_federated_graph: bool, -} - -#[derive(Debug, Clone, Serialize, PartialEq)] -pub struct SubgraphPublishResponse { - pub api_schema_hash: Option, - - pub supergraph_was_updated: bool, - - pub subgraph_was_created: bool, - - #[serde(skip_serializing)] - pub build_errors: BuildErrors, -} - -impl From for MutationVariables { - fn from(publish_input: SubgraphPublishInput) -> Self { - Self { - graph_id: publish_input.graph_ref.name, - variant: publish_input.graph_ref.variant, - subgraph: publish_input.subgraph, - url: publish_input.url, - schema: SchemaInput { - sdl: Some(publish_input.schema), - hash: None, - }, - git_context: publish_input.git_context.into(), - revision: "".to_string(), - } - } -} - -impl From for GitContextInput { - fn from(git_context: GitContext) -> GitContextInput { - GitContextInput { - branch: git_context.branch, - commit: git_context.commit, - committer: git_context.author, - remote_url: git_context.remote_url, - message: None, - } - } -} diff --git a/crates/rover-client/src/operations/supergraph/fetch/mod.rs b/crates/rover-client/src/operations/supergraph/fetch/mod.rs deleted file mode 100644 index d97562469..000000000 --- a/crates/rover-client/src/operations/supergraph/fetch/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod types; - -pub use runner::run; -pub use types::SupergraphFetchInput; diff --git a/crates/rover-client/src/operations/supergraph/fetch/types.rs b/crates/rover-client/src/operations/supergraph/fetch/types.rs deleted file mode 100644 index 1bcfa8193..000000000 --- a/crates/rover-client/src/operations/supergraph/fetch/types.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::operations::supergraph::fetch::runner::supergraph_fetch_query; -use crate::shared::GraphRef; - -type QueryVariables = supergraph_fetch_query::Variables; - -#[derive(Debug, Clone, PartialEq)] -pub struct SupergraphFetchInput { - pub graph_ref: GraphRef, -} - -impl From for QueryVariables { - fn from(input: SupergraphFetchInput) -> Self { - Self { - graph_id: input.graph_ref.name, - variant: input.graph_ref.variant, - } - } -} diff --git a/crates/rover-client/src/query/config/is_federated.graphql b/crates/rover-client/src/query/config/is_federated.graphql new file mode 100644 index 000000000..77ede9055 --- /dev/null +++ b/crates/rover-client/src/query/config/is_federated.graphql @@ -0,0 +1,7 @@ +query IsFederatedGraph($graphId: ID!, $graphVariant: String!) { + service(id: $graphId) { + implementingServices(graphVariant: $graphVariant) { + __typename + } + } +} \ No newline at end of file diff --git a/crates/rover-client/src/operations/config/is_federated/runner.rs b/crates/rover-client/src/query/config/is_federated.rs similarity index 71% rename from crates/rover-client/src/operations/config/is_federated/runner.rs rename to crates/rover-client/src/query/config/is_federated.rs index 35084ce68..b8fb68fa6 100644 --- a/crates/rover-client/src/operations/config/is_federated/runner.rs +++ b/crates/rover-client/src/query/config/is_federated.rs @@ -1,15 +1,13 @@ +// PublishPartialSchemaMutation use crate::blocking::StudioClient; -use crate::operations::config::is_federated::IsFederatedInput; -use crate::shared::GraphRef; use crate::RoverClientError; - use graphql_client::*; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/config/is_federated/is_federated_query.graphql", + query_path = "src/query/config/is_federated.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" @@ -20,23 +18,21 @@ use graphql_client::*; pub(crate) struct IsFederatedGraph; pub(crate) fn run( - input: IsFederatedInput, + variables: is_federated_graph::Variables, client: &StudioClient, ) -> Result { - let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; - build_response(data, graph_ref) + let graph = variables.graph_id.clone(); + let data = client.post::(variables)?; + build_response(data, graph) } type ImplementingServices = is_federated_graph::IsFederatedGraphServiceImplementingServices; fn build_response( data: is_federated_graph::ResponseData, - graph_ref: GraphRef, + graph: String, ) -> Result { - let service = data - .service - .ok_or(RoverClientError::GraphNotFound { graph_ref })?; + let service = data.service.ok_or(RoverClientError::NoService { graph })?; Ok(match service.implementing_services { Some(typename) => match typename { ImplementingServices::FederatedImplementingServices => true, diff --git a/crates/rover-client/src/operations/config/mod.rs b/crates/rover-client/src/query/config/mod.rs similarity index 83% rename from crates/rover-client/src/operations/config/mod.rs rename to crates/rover-client/src/query/config/mod.rs index e5e9f78ca..ab814a569 100644 --- a/crates/rover-client/src/operations/config/mod.rs +++ b/crates/rover-client/src/query/config/mod.rs @@ -1,5 +1,5 @@ /// runner for rover config whoami -pub mod who_am_i; +pub mod whoami; /// runner is_federated check pub mod is_federated; diff --git a/crates/rover-client/src/operations/config/who_am_i/who_am_i_query.graphql b/crates/rover-client/src/query/config/whoami.graphql similarity index 80% rename from crates/rover-client/src/operations/config/who_am_i/who_am_i_query.graphql rename to crates/rover-client/src/query/config/whoami.graphql index 4f131f2fa..9bf0e18f6 100644 --- a/crates/rover-client/src/operations/config/who_am_i/who_am_i_query.graphql +++ b/crates/rover-client/src/query/config/whoami.graphql @@ -1,4 +1,4 @@ -query ConfigWhoAmIQuery { +query WhoAmIQuery { me { __typename ... on Service { diff --git a/crates/rover-client/src/operations/config/who_am_i/runner.rs b/crates/rover-client/src/query/config/whoami.rs similarity index 76% rename from crates/rover-client/src/operations/config/who_am_i/runner.rs rename to crates/rover-client/src/query/config/whoami.rs index 0974cd49d..238440eb1 100644 --- a/crates/rover-client/src/operations/config/who_am_i/runner.rs +++ b/crates/rover-client/src/query/config/whoami.rs @@ -1,10 +1,5 @@ use crate::blocking::StudioClient; -use crate::operations::config::who_am_i::{ - types::{QueryActorType, QueryResponseData, RegistryIdentity}, - Actor, ConfigWhoAmIInput, -}; use crate::RoverClientError; - use houston::CredentialOrigin; use graphql_client::*; @@ -13,28 +8,43 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/config/who_am_i/who_am_i_query.graphql", + query_path = "src/query/config/whoami.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. config_who_am_i_query -pub(crate) struct ConfigWhoAmIQuery; +/// Snake case of this name is the mod name. i.e. who_am_i_query +pub struct WhoAmIQuery; + +#[derive(Debug, PartialEq)] +pub struct RegistryIdentity { + pub id: String, + pub graph_title: Option, + pub key_actor_type: Actor, + pub credential_origin: CredentialOrigin, +} + +#[derive(Debug, PartialEq)] +pub enum Actor { + GRAPH, + USER, + OTHER, +} /// Get info from the registry about an API key, i.e. the name/id of the /// user/graph and what kind of key it is (GRAPH/USER/Other) pub fn run( - input: ConfigWhoAmIInput, + variables: who_am_i_query::Variables, client: &StudioClient, ) -> Result { - let response_data = client.post::(input.into())?; - get_identity_from_response_data(response_data, client.get_credential_origin()) + let response_data = client.post::(variables)?; + get_identity_from_response_data(response_data, client.credential.origin.clone()) } fn get_identity_from_response_data( - response_data: QueryResponseData, + response_data: who_am_i_query::ResponseData, credential_origin: CredentialOrigin, ) -> Result { if let Some(me) = response_data.me { @@ -44,13 +54,13 @@ fn get_identity_from_response_data( // more here: https://studio-staging.apollographql.com/graph/engine/schema/reference/enums/ActorType?variant=prod let key_actor_type = match me.as_actor.type_ { - QueryActorType::GRAPH => Actor::GRAPH, - QueryActorType::USER => Actor::USER, + who_am_i_query::ActorType::GRAPH => Actor::GRAPH, + who_am_i_query::ActorType::USER => Actor::USER, _ => Actor::OTHER, }; let graph_title = match me.on { - config_who_am_i_query::ConfigWhoAmIQueryMeOn::Service(s) => Some(s.title), + who_am_i_query::WhoAmIQueryMeOn::Service(s) => Some(s.title), _ => None, }; @@ -81,8 +91,7 @@ mod tests { }, } }); - let data: config_who_am_i_query::ResponseData = - serde_json::from_value(json_response).unwrap(); + let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { @@ -107,8 +116,7 @@ mod tests { }, } }); - let data: config_who_am_i_query::ResponseData = - serde_json::from_value(json_response).unwrap(); + let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { diff --git a/crates/rover-client/src/operations/graph/check/check_mutation.graphql b/crates/rover-client/src/query/graph/check.graphql similarity index 61% rename from crates/rover-client/src/operations/graph/check/check_mutation.graphql rename to crates/rover-client/src/query/graph/check.graphql index 513688e1b..a891ba7eb 100644 --- a/crates/rover-client/src/operations/graph/check/check_mutation.graphql +++ b/crates/rover-client/src/query/graph/check.graphql @@ -1,15 +1,15 @@ -mutation GraphCheckMutation( - $graph_id: ID! +mutation CheckSchemaQuery( + $graphId: ID! $variant: String - $proposed_schema: String - $git_context: GitContextInput! + $schema: String + $gitContext: GitContextInput! $config: HistoricQueryParameters! ) { - service(id: $graph_id) { + service(id: $graphId) { checkSchema( - proposedSchemaDocument: $proposed_schema + proposedSchemaDocument: $schema baseSchemaTag: $variant - gitContext: $git_context + gitContext: $gitContext historicParameters: $config ) { targetUrl @@ -24,4 +24,4 @@ mutation GraphCheckMutation( } } } -} +} \ No newline at end of file diff --git a/crates/rover-client/src/query/graph/check.rs b/crates/rover-client/src/query/graph/check.rs new file mode 100644 index 000000000..c57dad4ca --- /dev/null +++ b/crates/rover-client/src/query/graph/check.rs @@ -0,0 +1,76 @@ +use crate::blocking::StudioClient; +use crate::RoverClientError; +use graphql_client::*; + +use reqwest::Url; + +type Timestamp = String; +#[derive(GraphQLQuery)] +// The paths are relative to the directory where your `Cargo.toml` is located. +// Both json and the GraphQL schema language are supported as sources for the schema +#[graphql( + query_path = "src/query/graph/check.graphql", + schema_path = ".schema/schema.graphql", + response_derives = "PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. check_schema_query +pub struct CheckSchemaQuery; + +/// The main function to be used from this module. +/// This function takes a proposed schema and validates it against a published +/// schema. +pub fn run( + variables: check_schema_query::Variables, + client: &StudioClient, +) -> Result { + let graph = variables.graph_id.clone(); + let data = client.post::(variables)?; + get_check_response_from_data(data, graph) +} + +#[derive(Debug)] +pub struct CheckResponse { + pub target_url: Option, + pub number_of_checked_operations: i64, + pub change_severity: check_schema_query::ChangeSeverity, + pub changes: Vec, +} + +fn get_check_response_from_data( + data: check_schema_query::ResponseData, + graph: String, +) -> Result { + let service = data.service.ok_or(RoverClientError::NoService { graph })?; + let target_url = get_url(service.check_schema.target_url); + + let diff_to_previous = service.check_schema.diff_to_previous; + + let number_of_checked_operations = diff_to_previous.number_of_checked_operations.unwrap_or(0); + + let change_severity = diff_to_previous.severity; + let changes = diff_to_previous.changes; + + Ok(CheckResponse { + target_url, + number_of_checked_operations, + change_severity, + changes, + }) +} + +fn get_url(url: Option) -> Option { + match url { + Some(url) => { + let url = Url::parse(&url); + match url { + Ok(url) => Some(url), + // if the API returns an invalid URL, don't put it in the response + Err(_) => None, + } + } + None => None, + } +} diff --git a/crates/rover-client/src/query/graph/fetch.graphql b/crates/rover-client/src/query/graph/fetch.graphql new file mode 100644 index 000000000..697a17b23 --- /dev/null +++ b/crates/rover-client/src/query/graph/fetch.graphql @@ -0,0 +1,11 @@ +query FetchSchemaQuery($variant: String, $graphId: ID!, $hash: ID) { + frontendUrlRoot, + service(id: $graphId) { + schema(tag: $variant, hash: $hash) { + document + } + variants { + name + } + } +} diff --git a/crates/rover-client/src/operations/graph/fetch/runner.rs b/crates/rover-client/src/query/graph/fetch.rs similarity index 58% rename from crates/rover-client/src/operations/graph/fetch/runner.rs rename to crates/rover-client/src/query/graph/fetch.rs index fc72a3b53..2890334e0 100644 --- a/crates/rover-client/src/operations/graph/fetch/runner.rs +++ b/crates/rover-client/src/query/graph/fetch.rs @@ -1,8 +1,5 @@ use crate::blocking::StudioClient; -use crate::operations::graph::fetch::GraphFetchInput; -use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; - use graphql_client::*; // I'm not sure where this should live long-term @@ -13,42 +10,40 @@ type GraphQLDocument = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/graph/fetch/fetch_query.graphql", + query_path = "src/query/graph/fetch.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. graph_fetch_query -pub(crate) struct GraphFetchQuery; +/// Snake case of this name is the mod name. i.e. fetch_schema_query +pub struct FetchSchemaQuery; /// The main function to be used from this module. This function fetches a /// schema from apollo studio and returns it in either sdl (default) or json format pub fn run( - input: GraphFetchInput, + variables: fetch_schema_query::Variables, client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; - let sdl_contents = get_schema_from_response_data(response_data, graph_ref)?; - Ok(FetchResponse { - sdl: Sdl { - contents: sdl_contents, - r#type: SdlType::Graph, - }, - }) +) -> Result { + let graph = variables.graph_id.clone(); + let invalid_variant = variables + .variant + .clone() + .unwrap_or_else(|| "current".to_string()); + let response_data = client.post::(variables)?; + get_schema_from_response_data(response_data, graph, invalid_variant) + // if we want json, we can parse & serialize it here } fn get_schema_from_response_data( - response_data: graph_fetch_query::ResponseData, - graph_ref: GraphRef, + response_data: fetch_schema_query::ResponseData, + graph: String, + invalid_variant: String, ) -> Result { - let service_data = response_data - .service - .ok_or(RoverClientError::GraphNotFound { - graph_ref: graph_ref.clone(), - })?; + let service_data = response_data.service.ok_or(RoverClientError::NoService { + graph: graph.clone(), + })?; let mut valid_variants = Vec::new(); @@ -60,7 +55,8 @@ fn get_schema_from_response_data( Ok(schema.document) } else { Err(RoverClientError::NoSchemaForVariant { - graph_ref, + graph, + invalid_variant, valid_variants, frontend_url_root: response_data.frontend_url_root, }) @@ -82,9 +78,9 @@ mod tests { "variants": [] } }); - let data: graph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_schema_from_response_data(data, graph_ref); + let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let (graph, invalid_variant) = mock_vars(); + let output = get_schema_from_response_data(data, graph, invalid_variant); assert!(output.is_ok()); assert_eq!(output.unwrap(), "type Query { hello: String }".to_string()); @@ -94,9 +90,9 @@ mod tests { fn get_schema_from_response_data_errs_on_no_service() { let json_response = json!({ "service": null, "frontendUrlRoot": "https://studio.apollographql.com" }); - let data: graph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_schema_from_response_data(data, graph_ref); + let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let (graph, invalid_variant) = mock_vars(); + let output = get_schema_from_response_data(data, graph, invalid_variant); assert!(output.is_err()); } @@ -110,17 +106,14 @@ mod tests { "variants": [], }, }); - let data: graph_fetch_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_schema_from_response_data(data, graph_ref); + let data: fetch_schema_query::ResponseData = serde_json::from_value(json_response).unwrap(); + let (graph, invalid_variant) = mock_vars(); + let output = get_schema_from_response_data(data, graph, invalid_variant); assert!(output.is_err()); } - fn mock_graph_ref() -> GraphRef { - GraphRef { - name: "mygraph".to_string(), - variant: "current".to_string(), - } + fn mock_vars() -> (String, String) { + ("mygraph".to_string(), "current".to_string()) } } diff --git a/crates/rover-client/src/query/graph/introspect.rs b/crates/rover-client/src/query/graph/introspect.rs new file mode 100644 index 000000000..1e88db228 --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect.rs @@ -0,0 +1,129 @@ +use std::{collections::HashMap, convert::TryFrom}; + +use crate::blocking::GraphQLClient; +use crate::introspection::Schema; +use crate::RoverClientError; +use graphql_client::*; + +#[derive(GraphQLQuery)] +#[graphql( + query_path = "src/query/graph/introspect_query.graphql", + schema_path = "src/query/graph/introspect_schema.graphql", + response_derives = "PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] + +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. introspection_query +pub struct IntrospectionQuery; + +#[derive(Debug, PartialEq)] +pub struct IntrospectionResponse { + pub result: String, +} + +/// The main function to be used from this module. This function fetches a +/// schema from apollo studio and returns it in either sdl (default) or json format +pub fn run( + client: &GraphQLClient, + headers: &HashMap, +) -> Result { + let variables = introspection_query::Variables {}; + let response_data = client.post::(variables, headers)?; + build_response(response_data) +} + +fn build_response( + response: introspection_query::ResponseData, +) -> Result { + match Schema::try_from(response) { + Ok(schema) => Ok(IntrospectionResponse { + result: schema.encode(), + }), + Err(msg) => Err(RoverClientError::IntrospectionError { msg: msg.into() }), + } +} + +/// This trait is used to be able to iterate over ofType fields in +/// IntrospectionResponse. +pub trait OfType { + type TypeRef: OfType; + + fn kind(&self) -> &introspection_query::__TypeKind; + fn name(&self) -> Option<&str>; + fn of_type(self) -> Option; +} + +macro_rules! impl_of_type { + ($target:ty, $assoc:ty) => { + impl OfType for $target { + type TypeRef = $assoc; + + fn kind(&self) -> &introspection_query::__TypeKind { + &self.kind + } + + fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + fn of_type(self) -> Option { + self.of_type + } + } + }; +} + +impl_of_type!( + introspection_query::TypeRef, + introspection_query::TypeRefOfType +); + +impl_of_type!( + introspection_query::TypeRefOfType, + introspection_query::TypeRefOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType +); + +// NOTE(lrlna): This is a **hack**. This makes sure that the last possible +// generated ofType by graphql_client can return a None for of_type method. +impl OfType for introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType { + type TypeRef = introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType; + + fn kind(&self) -> &introspection_query::__TypeKind { + &self.kind + } + + fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + fn of_type(self) -> Option { + None + } +} diff --git a/crates/rover-client/src/operations/graph/introspect/introspect_query.graphql b/crates/rover-client/src/query/graph/introspect_query.graphql similarity index 97% rename from crates/rover-client/src/operations/graph/introspect/introspect_query.graphql rename to crates/rover-client/src/query/graph/introspect_query.graphql index c1c64031a..996742faf 100644 --- a/crates/rover-client/src/operations/graph/introspect/introspect_query.graphql +++ b/crates/rover-client/src/query/graph/introspect_query.graphql @@ -1,4 +1,4 @@ -query GraphIntrospectQuery { +query IntrospectionQuery { __schema { queryType { name diff --git a/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql b/crates/rover-client/src/query/graph/introspect_schema.graphql similarity index 76% rename from crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql rename to crates/rover-client/src/query/graph/introspect_schema.graphql index c9030af48..805ad4486 100644 --- a/crates/rover-client/src/operations/graph/introspect/introspect_schema.graphql +++ b/crates/rover-client/src/query/graph/introspect_schema.graphql @@ -1,14 +1,11 @@ -# eslint-disable-next-line schema { query: Query } -# eslint-disable-next-line type Query { __schema: __Schema } -# eslint-disable-next-line type __Schema { types: [__Type!]! queryType: __Type! @@ -17,32 +14,30 @@ type __Schema { directives: [__Directive!]! } -# eslint-disable-next-line type __Type { kind: __TypeKind! name: String description: String - "OBJECT and INTERFACE only" + # OBJECT and INTERFACE only fields(includeDeprecated: Boolean = false): [__Field!] - "OBJECT only" + # OBJECT only interfaces: [__Type!] - "INTERFACE and UNION only" + # INTERFACE and UNION only possibleTypes: [__Type!] - "ENUM only" + # ENUM only enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - "INPUT_OBJECT only" + # INPUT_OBJECT only inputFields: [__InputValue!] - "NON_NULL and LIST only" + # NON_NULL and LIST only ofType: __Type } -# eslint-disable-next-line type __Field { name: String! description: String @@ -52,7 +47,6 @@ type __Field { deprecationReason: String } -# eslint-disable-next-line type __InputValue { name: String! description: String @@ -60,7 +54,6 @@ type __InputValue { defaultValue: String } -# eslint-disable-next-line type __EnumValue { name: String! description: String @@ -68,7 +61,6 @@ type __EnumValue { deprecationReason: String } -# eslint-disable-next-line enum __TypeKind { SCALAR OBJECT @@ -80,7 +72,6 @@ enum __TypeKind { NON_NULL } -# eslint-disable-next-line type __Directive { name: String! description: String @@ -88,7 +79,6 @@ type __Directive { args: [__InputValue!]! } -# eslint-disable-next-line enum __DirectiveLocation { QUERY MUTATION diff --git a/crates/rover-client/src/operations/graph/mod.rs b/crates/rover-client/src/query/graph/mod.rs similarity index 100% rename from crates/rover-client/src/operations/graph/mod.rs rename to crates/rover-client/src/query/graph/mod.rs diff --git a/crates/rover-client/src/operations/graph/publish/publish_mutation.graphql b/crates/rover-client/src/query/graph/publish.graphql similarity index 70% rename from crates/rover-client/src/operations/graph/publish/publish_mutation.graphql rename to crates/rover-client/src/query/graph/publish.graphql index 8535b5689..b260df1b0 100644 --- a/crates/rover-client/src/operations/graph/publish/publish_mutation.graphql +++ b/crates/rover-client/src/query/graph/publish.graphql @@ -1,14 +1,14 @@ -mutation GraphPublishMutation( - $graph_id: ID! +mutation PublishSchemaMutation( $variant: String! - $proposed_schema: String! - $git_context: GitContextInput! + $graphId: ID! + $schemaDocument: String + $gitContext: GitContextInput! ) { - service(id: $graph_id) { + service(id: $graphId) { uploadSchema( tag: $variant - schemaDocument: $proposed_schema - gitContext: $git_context + schemaDocument: $schemaDocument + gitContext: $gitContext ) { code message diff --git a/crates/rover-client/src/operations/graph/publish/runner.rs b/crates/rover-client/src/query/graph/publish.rs similarity index 58% rename from crates/rover-client/src/operations/graph/publish/runner.rs rename to crates/rover-client/src/query/graph/publish.rs index 4ff0fbaec..3c5955600 100644 --- a/crates/rover-client/src/operations/graph/publish/runner.rs +++ b/crates/rover-client/src/query/graph/publish.rs @@ -1,7 +1,4 @@ use crate::blocking::StudioClient; -use crate::operations::graph::publish::types::{ChangeSummary, FieldChanges, TypeChanges}; -use crate::operations::graph::publish::{GraphPublishInput, GraphPublishResponse}; -use crate::shared::GraphRef; use crate::RoverClientError; use graphql_client::*; @@ -9,36 +6,40 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/graph/publish/publish_mutation.graphql", + query_path = "src/query/graph/publish.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. graph_publish_mutation -pub(crate) struct GraphPublishMutation; +/// Snake case of this name is the mod name. i.e. stash_schema_query +pub struct PublishSchemaMutation; + +#[derive(Debug, PartialEq)] +pub struct PublishResponse { + pub schema_hash: String, + pub change_summary: String, +} /// Returns a message from apollo studio about the status of the update, and /// a sha256 hash of the schema to be used with `schema publish` pub fn run( - input: GraphPublishInput, + variables: publish_schema_mutation::Variables, client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let data = client.post::(input.into())?; - let publish_response = get_publish_response_from_data(data, graph_ref)?; +) -> Result { + let graph = variables.graph_id.clone(); + let data = client.post::(variables)?; + let publish_response = get_publish_response_from_data(data, graph)?; build_response(publish_response) } fn get_publish_response_from_data( - data: graph_publish_mutation::ResponseData, - graph_ref: GraphRef, -) -> Result { + data: publish_schema_mutation::ResponseData, + graph: String, +) -> Result { // then, from the response data, get .service?.upload_schema? - let service_data = data - .service - .ok_or(RoverClientError::GraphNotFound { graph_ref })?; + let service_data = data.service.ok_or(RoverClientError::NoService { graph })?; service_data .upload_schema @@ -48,8 +49,8 @@ fn get_publish_response_from_data( } fn build_response( - publish_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema, -) -> Result { + publish_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema, +) -> Result { if !publish_response.success { let msg = format!( "Schema upload failed with error: {}", @@ -76,66 +77,40 @@ fn build_response( // which very well may have changes. For this, we'll just look at the code // first and handle the response as if there was `None` for the diff let change_summary = if publish_response.code == "NO_CHANGES" { - ChangeSummary::none() + build_change_summary(None) } else { - let diff = publish_response - .tag - .ok_or_else(|| RoverClientError::MalformedResponse { - null_field: "service.upload_schema.tag".to_string(), - })? - .diff_to_previous; - - if let Some(diff) = diff { - diff.into() - } else { - ChangeSummary::none() - } + build_change_summary(publish_response.tag.unwrap().diff_to_previous) }; - Ok(GraphPublishResponse { - api_schema_hash: hash, + Ok(PublishResponse { + schema_hash: hash, change_summary, }) } -type QueryChangeDiff = - graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPrevious; +type ChangeDiff = + publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTagDiffToPrevious; -impl From for ChangeSummary { - fn from(input: QueryChangeDiff) -> Self { - Self { - field_changes: input.change_summary.field.into(), - type_changes: input.change_summary.type_.into(), +/// builds a string-representation of the diff between two schemas +/// e.g. ` [Fields: +2 -1 â–³0, Types: +4 -0 â–³7]` or `[No Changes]` +fn build_change_summary(diff: Option) -> String { + match diff { + None => "[No Changes]".to_string(), + Some(diff) => { + let changes = diff.change_summary; + let fields = format!( + "Fields: +{} -{} â–³ {}", + changes.field.additions, changes.field.removals, changes.field.edits + ); + let types = format!( + "Types: +{} -{} â–³ {}", + changes.type_.additions, changes.type_.removals, changes.type_.edits + ); + format!("[{}, {}]", fields, types) } } } -type QueryFieldChanges = - graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPreviousChangeSummaryField; - -impl From for FieldChanges { - fn from(input: QueryFieldChanges) -> Self { - Self::with_diff( - input.additions as u64, - input.removals as u64, - input.edits as u64, - ) - } -} - -type QueryTypeChanges = - graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagDiffToPreviousChangeSummaryType; - -impl From for TypeChanges { - fn from(input: QueryTypeChanges) -> Self { - Self::with_diff( - input.additions as u64, - input.removals as u64, - input.edits as u64, - ) - } -} - #[cfg(test)] mod tests { use super::*; @@ -156,25 +131,25 @@ mod tests { } } }); - let data: graph_publish_mutation::ResponseData = + let data: publish_schema_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_publish_response_from_data(data, mock_graph_ref()); + let output = get_publish_response_from_data(data, "mygraph".to_string()); assert!(output.is_ok()); assert_eq!( output.unwrap(), - graph_publish_mutation::GraphPublishMutationServiceUploadSchema { + publish_schema_mutation::PublishSchemaMutationServiceUploadSchema { code: "IT_WERK".to_string(), message: "it really do be published".to_string(), success: true, tag: Some( - graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTag { + publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTag { variant: - graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagVariant { + publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTagVariant { name: "current".to_string() }, schema: - graph_publish_mutation::GraphPublishMutationServiceUploadSchemaTagSchema { + publish_schema_mutation::PublishSchemaMutationServiceUploadSchemaTagSchema { hash: "123456".to_string() }, diff_to_previous: None, @@ -187,9 +162,9 @@ mod tests { #[test] fn get_publish_response_from_data_errs_with_no_service() { let json_response = json!({ "service": null }); - let data: graph_publish_mutation::ResponseData = + let data: publish_schema_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_publish_response_from_data(data, mock_graph_ref()); + let output = get_publish_response_from_data(data, "mygraph".to_string()); assert!(output.is_err()); } @@ -201,9 +176,9 @@ mod tests { "uploadSchema": null } }); - let data: graph_publish_mutation::ResponseData = + let data: publish_schema_mutation::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_publish_response_from_data(data, mock_graph_ref()); + let output = get_publish_response_from_data(data, "mygraph".to_string()); assert!(output.is_err()); } @@ -219,16 +194,16 @@ mod tests { "schema": { "hash": "123456" } } }); - let update_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema = + let update_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema = serde_json::from_value(json_response).unwrap(); let output = build_response(update_response); assert!(output.is_ok()); assert_eq!( output.unwrap(), - GraphPublishResponse { - api_schema_hash: "123456".to_string(), - change_summary: ChangeSummary::none(), + PublishResponse { + schema_hash: "123456".to_string(), + change_summary: "[No Changes]".to_string(), } ); } @@ -241,7 +216,7 @@ mod tests { "success": false, "tag": null }); - let update_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema = + let update_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema = serde_json::from_value(json_response).unwrap(); let output = build_response(update_response); @@ -256,7 +231,7 @@ mod tests { "success": true, "tag": null }); - let update_response: graph_publish_mutation::GraphPublishMutationServiceUploadSchema = + let update_response: publish_schema_mutation::PublishSchemaMutationServiceUploadSchema = serde_json::from_value(json_response).unwrap(); let output = build_response(update_response); @@ -279,26 +254,13 @@ mod tests { } } }); - let diff_to_previous: QueryChangeDiff = serde_json::from_value(json_diff).unwrap(); - let output: ChangeSummary = diff_to_previous.into(); - assert_eq!( - output.to_string(), - "[Fields: +3 -1 â–³ 0, Types: +4 -0 â–³ 2]".to_string() - ) + let diff_to_previous: ChangeDiff = serde_json::from_value(json_diff).unwrap(); + let output = build_change_summary(Some(diff_to_previous)); + assert_eq!(output, "[Fields: +3 -1 â–³ 0, Types: +4 -0 â–³ 2]".to_string()) } #[test] fn build_change_summary_works_with_no_changes() { - assert_eq!( - ChangeSummary::none().to_string(), - "[No Changes]".to_string() - ) - } - - fn mock_graph_ref() -> GraphRef { - GraphRef { - name: "mygraph".to_string(), - variant: "current".to_string(), - } + assert_eq!(build_change_summary(None), "[No Changes]".to_string()) } } diff --git a/crates/rover-client/src/operations/mod.rs b/crates/rover-client/src/query/mod.rs similarity index 100% rename from crates/rover-client/src/operations/mod.rs rename to crates/rover-client/src/query/mod.rs diff --git a/crates/rover-client/src/operations/subgraph/check/check_mutation.graphql b/crates/rover-client/src/query/subgraph/check.graphql similarity index 63% rename from crates/rover-client/src/operations/subgraph/check/check_mutation.graphql rename to crates/rover-client/src/query/subgraph/check.graphql index 906dfd831..5037bd561 100644 --- a/crates/rover-client/src/operations/subgraph/check/check_mutation.graphql +++ b/crates/rover-client/src/query/subgraph/check.graphql @@ -1,27 +1,22 @@ - mutation SubgraphCheckMutation( + mutation CheckPartialSchemaQuery( $graph_id: ID! $variant: String! - $subgraph: String! - $proposed_schema: PartialSchemaInput! - $git_context: GitContextInput! + $implementingServiceName: String! + $partialSchema: PartialSchemaInput! + $gitContext: GitContextInput! $config: HistoricQueryParameters! ) { service(id: $graph_id) { checkPartialSchema( graphVariant: $variant - implementingServiceName: $subgraph - partialSchema: $proposed_schema - gitContext: $git_context + implementingServiceName: $implementingServiceName + partialSchema: $partialSchema + gitContext: $gitContext historicParameters: $config ) { compositionValidationResult { errors { message - code - locations { - line - column - } } } checkSchemaResult { diff --git a/crates/rover-client/src/query/subgraph/check.rs b/crates/rover-client/src/query/subgraph/check.rs new file mode 100644 index 000000000..bcff8cb11 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/check.rs @@ -0,0 +1,119 @@ +use crate::blocking::StudioClient; +use crate::query::config::is_federated; +use crate::RoverClientError; + +use graphql_client::*; + +use reqwest::Url; + +type Timestamp = String; +#[derive(GraphQLQuery)] +// The paths are relative to the directory where your `Cargo.toml` is located. +// Both json and the GraphQL schema language are supported as sources for the schema +#[graphql( + query_path = "src/query/subgraph/check.graphql", + schema_path = ".schema/schema.graphql", + response_derives = "PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. check_partial_schema_query +pub struct CheckPartialSchemaQuery; + +/// The main function to be used from this module. +/// This function takes a proposed schema and validates it against a published +/// schema. +pub fn run( + variables: check_partial_schema_query::Variables, + client: &StudioClient, +) -> Result { + let graph = variables.graph_id.clone(); + // This response is used to check whether or not the current graph is federated. + let is_federated = is_federated::run( + is_federated::is_federated_graph::Variables { + graph_id: variables.graph_id.clone(), + graph_variant: variables.variant.clone(), + }, + client, + )?; + if !is_federated { + return Err(RoverClientError::ExpectedFederatedGraph { + graph, + can_operation_convert: false, + }); + } + let data = client.post::(variables)?; + get_check_response_from_data(data, graph) +} + +pub enum CheckResponse { + CompositionErrors(Vec), + CheckResult(CheckResult) +} + +#[derive(Debug)] +pub struct CheckResult { + pub target_url: Option, + pub number_of_checked_operations: i64, + pub change_severity: check_partial_schema_query::ChangeSeverity, + pub changes: Vec, +} + +fn get_check_response_from_data( + data: check_partial_schema_query::ResponseData, + graph: String, +) -> Result { + let service = data.service.ok_or(RoverClientError::NoService { graph })?; + + // for some reason this is a `Vec>` + // we convert this to just `Vec` because the `None` + // errors would be useless. + let composition_errors: Vec = service + .check_partial_schema + .composition_validation_result + .errors; + + if composition_errors.is_empty() { + let check_schema_result = service.check_partial_schema.check_schema_result.ok_or( + RoverClientError::MalformedResponse { + null_field: "service.check_partial_schema.check_schema_result".to_string(), + }, + )?; + + let target_url = get_url(check_schema_result.target_url); + + let diff_to_previous = check_schema_result.diff_to_previous; + + let number_of_checked_operations = + diff_to_previous.number_of_checked_operations.unwrap_or(0); + + let change_severity = diff_to_previous.severity; + let changes = diff_to_previous.changes; + + let check_result = CheckResult { + target_url, + number_of_checked_operations, + change_severity, + changes, + }; + + Ok(CheckResponse::CheckResult(check_result)) + } else { + Ok(CheckResponse::CompositionErrors(composition_errors)) + } +} + +fn get_url(url: Option) -> Option { + match url { + Some(url) => { + let url = Url::parse(&url); + match url { + Ok(url) => Some(url), + // if the API returns an invalid URL, don't put it in the response + Err(_) => None, + } + } + None => None, + } +} diff --git a/crates/rover-client/src/query/subgraph/delete.graphql b/crates/rover-client/src/query/subgraph/delete.graphql new file mode 100644 index 000000000..0b6cf25a5 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/delete.graphql @@ -0,0 +1,19 @@ +mutation DeleteServiceMutation( + $graphId: ID! + $graphVariant: String! + $name: String! + $dryRun: Boolean! +) { + service(id: $graphId) { + removeImplementingServiceAndTriggerComposition( + graphVariant: $graphVariant + name: $name + dryRun: $dryRun + ) { + errors { + message + } + updatedGateway + } + } +} diff --git a/crates/rover-client/src/query/subgraph/delete.rs b/crates/rover-client/src/query/subgraph/delete.rs new file mode 100644 index 000000000..7e735b2ea --- /dev/null +++ b/crates/rover-client/src/query/subgraph/delete.rs @@ -0,0 +1,156 @@ +use crate::blocking::StudioClient; +use crate::RoverClientError; +use graphql_client::*; + +#[derive(GraphQLQuery)] +// The paths are relative to the directory where your `Cargo.toml` is located. +// Both json and the GraphQL schema language are supported as sources for the schema +#[graphql( + query_path = "src/query/subgraph/delete.graphql", + schema_path = ".schema/schema.graphql", + response_derives = "PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. delete_service_mutation +pub struct DeleteServiceMutation; +type RawMutationResponse = delete_service_mutation::DeleteServiceMutationServiceRemoveImplementingServiceAndTriggerComposition; + +/// this struct contains all the info needed to print the result of the delete. +/// `updated_gateway` is true when composition succeeds and the gateway config +/// is updated for the gateway to consume. `composition_errors` is just a list +/// of strings for when there are composition errors as a result of the delete. +#[derive(Debug, PartialEq)] +pub struct DeleteServiceResponse { + pub updated_gateway: bool, + pub composition_errors: Option>, +} + +/// The main function to be used from this module. This function fetches a +/// schema from apollo studio and returns it in either sdl (default) or json format +pub fn run( + variables: delete_service_mutation::Variables, + client: &StudioClient, +) -> Result { + let graph = variables.graph_id.clone(); + let response_data = client.post::(variables)?; + let data = get_delete_data_from_response(response_data, graph)?; + Ok(build_response(data)) +} + +fn get_delete_data_from_response( + response_data: delete_service_mutation::ResponseData, + graph: String, +) -> Result { + let service_data = response_data + .service + .ok_or(RoverClientError::NoService { graph })?; + + Ok(service_data.remove_implementing_service_and_trigger_composition) +} + +fn build_response(response: RawMutationResponse) -> DeleteServiceResponse { + let composition_errors: Vec = response + .errors + .iter() + .filter_map(|error| error.as_ref().map(|e| e.message.clone())) + .collect(); + + // if there are no errors, just return None + let composition_errors = if !composition_errors.is_empty() { + Some(composition_errors) + } else { + None + }; + + DeleteServiceResponse { + updated_gateway: response.updated_gateway, + composition_errors, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + type RawCompositionErrors = delete_service_mutation::DeleteServiceMutationServiceRemoveImplementingServiceAndTriggerCompositionErrors; + + #[test] + fn get_delete_data_from_response_works() { + let json_response = json!({ + "service": { + "removeImplementingServiceAndTriggerComposition": { + "errors": [ + { "message": "wow" }, + null, + { "message": "boo" } + ], + "updatedGateway": false, + } + } + }); + let data: delete_service_mutation::ResponseData = + serde_json::from_value(json_response).unwrap(); + let output = get_delete_data_from_response(data, "mygraph".to_string()); + + assert!(output.is_ok()); + + let expected_response = RawMutationResponse { + errors: vec![ + Some(RawCompositionErrors { + message: "wow".to_string(), + }), + None, + Some(RawCompositionErrors { + message: "boo".to_string(), + }), + ], + updated_gateway: false, + }; + assert_eq!(output.unwrap(), expected_response); + } + + #[test] + fn build_response_works_with_successful_responses() { + let response = RawMutationResponse { + errors: vec![ + Some(RawCompositionErrors { + message: "wow".to_string(), + }), + None, + Some(RawCompositionErrors { + message: "boo".to_string(), + }), + ], + updated_gateway: false, + }; + + let parsed = build_response(response); + assert_eq!( + parsed, + DeleteServiceResponse { + composition_errors: Some(vec!["wow".to_string(), "boo".to_string()]), + updated_gateway: false, + } + ); + } + + #[test] + fn build_response_works_with_failure_responses() { + let response = RawMutationResponse { + errors: vec![], + updated_gateway: true, + }; + + let parsed = build_response(response); + assert_eq!( + parsed, + DeleteServiceResponse { + composition_errors: None, + updated_gateway: true, + } + ); + } +} diff --git a/crates/rover-client/src/operations/subgraph/fetch/fetch_query.graphql b/crates/rover-client/src/query/subgraph/fetch.graphql similarity index 72% rename from crates/rover-client/src/operations/subgraph/fetch/fetch_query.graphql rename to crates/rover-client/src/query/subgraph/fetch.graphql index 631177641..8b4e600c0 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/fetch_query.graphql +++ b/crates/rover-client/src/query/subgraph/fetch.graphql @@ -1,5 +1,5 @@ -query SubgraphFetchQuery($graph_id: ID!, $variant: String!) { - service(id: $graph_id) { +query FetchSubgraphQuery($variant: String!, $graphID: ID!) { + service(id: $graphID) { implementingServices(graphVariant: $variant) { __typename ... on FederatedImplementingServices { diff --git a/crates/rover-client/src/operations/subgraph/fetch/runner.rs b/crates/rover-client/src/query/subgraph/fetch.rs similarity index 68% rename from crates/rover-client/src/operations/subgraph/fetch/runner.rs rename to crates/rover-client/src/query/subgraph/fetch.rs index dd0a1e2ac..9509d3951 100644 --- a/crates/rover-client/src/operations/subgraph/fetch/runner.rs +++ b/crates/rover-client/src/query/subgraph/fetch.rs @@ -1,85 +1,71 @@ -use super::types::*; use crate::blocking::StudioClient; -use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; - use graphql_client::*; #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/subgraph/fetch/fetch_query.graphql", + query_path = "src/query/subgraph/fetch.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. subgraph_fetch_query -pub(crate) struct SubgraphFetchQuery; +/// Snake case of this name is the mod name. i.e. fetch_subgraph_query +pub struct FetchSubgraphQuery; /// Fetches a schema from apollo studio and returns its SDL (String) pub fn run( - input: SubgraphFetchInput, + variables: fetch_subgraph_query::Variables, client: &StudioClient, -) -> Result { - let input_clone = input.clone(); - let response_data = client.post::(input.into())?; - get_sdl_from_response_data(input_clone, response_data) -} - -fn get_sdl_from_response_data( - input: SubgraphFetchInput, - response_data: SubgraphFetchResponseData, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let service_list = get_services_from_response_data(graph_ref, response_data)?; - let sdl_contents = get_sdl_for_service(&input.subgraph, service_list)?; - Ok(FetchResponse { - sdl: Sdl { - contents: sdl_contents, - r#type: SdlType::Subgraph, - }, - }) + // we can't specify this as a variable in the op, so we have to filter the + // operation response by this name + subgraph: &str, +) -> Result { + let graph = variables.graph_id.clone(); + let response_data = client.post::(variables)?; + let services = get_services_from_response_data(response_data, graph)?; + get_sdl_for_service(services, subgraph) + // if we want json, we can parse & serialize it here } +type ServiceList = Vec; fn get_services_from_response_data( - graph_ref: GraphRef, - response_data: SubgraphFetchResponseData, + response_data: fetch_subgraph_query::ResponseData, + graph: String, ) -> Result { - let service_data = response_data - .service - .ok_or(RoverClientError::GraphNotFound { - graph_ref: graph_ref.clone(), - })?; + let service_data = response_data.service.ok_or(RoverClientError::NoService { + graph: graph.clone(), + })?; // get list of services let services = match service_data.implementing_services { Some(services) => Ok(services), + // this case may be removable in the near future as unreachable, since + // you should still get an `implementingServices` response in the case + // of a non-federated graph. Fow now, this case still exists, but + // wont' for long. Check on this later (Jake) :) None => Err(RoverClientError::ExpectedFederatedGraph { - graph_ref: graph_ref.clone(), + graph: graph.clone(), can_operation_convert: false, }), }?; match services { - Services::FederatedImplementingServices(services) => Ok(services.services), - Services::NonFederatedImplementingService => { - Err(RoverClientError::ExpectedFederatedGraph { - graph_ref, - can_operation_convert: false, - }) + fetch_subgraph_query::FetchSubgraphQueryServiceImplementingServices::FederatedImplementingServices (services) => { + Ok(services.services) + }, + fetch_subgraph_query::FetchSubgraphQueryServiceImplementingServices::NonFederatedImplementingService => { + Err(RoverClientError::ExpectedFederatedGraph { graph, can_operation_convert: false }) } } } -fn get_sdl_for_service( - subgraph_name: &str, - services: ServiceList, -) -> Result { +fn get_sdl_for_service(services: ServiceList, subgraph: &str) -> Result { // find the right service by name - let service = services.iter().find(|svc| svc.name == subgraph_name); + let service = services.iter().find(|svc| svc.name == subgraph); // if there is a service, get it's active sdl, otherwise, error and list // available services to fetch @@ -89,7 +75,7 @@ fn get_sdl_for_service( let valid_subgraphs: Vec = services.iter().map(|svc| svc.name.clone()).collect(); Err(RoverClientError::NoSubgraphInGraph { - invalid_subgraph: subgraph_name.to_string(), + invalid_subgraph: subgraph.to_string(), valid_subgraphs, }) } @@ -123,8 +109,9 @@ mod tests { } } }); - let data: SubgraphFetchResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_services_from_response_data(mock_graph_ref(), data); + let data: fetch_subgraph_query::ResponseData = + serde_json::from_value(json_response).unwrap(); + let output = get_services_from_response_data(data, "mygraph".to_string()); let expected_json = json!([ { @@ -153,8 +140,9 @@ mod tests { "implementingServices": null } }); - let data: SubgraphFetchResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_services_from_response_data(mock_graph_ref(), data); + let data: fetch_subgraph_query::ResponseData = + serde_json::from_value(json_response).unwrap(); + let output = get_services_from_response_data(data, "mygraph".to_string()); assert!(output.is_err()); } @@ -175,7 +163,7 @@ mod tests { } ]); let service_list: ServiceList = serde_json::from_value(json_service_list).unwrap(); - let output = get_sdl_for_service("accounts2", service_list); + let output = get_sdl_for_service(service_list, "accounts2"); assert_eq!( output.unwrap(), "extend type User @key(fields: \"id\") {\n id: ID! @external\n age: Int\n}\n" @@ -200,14 +188,7 @@ mod tests { } ]); let service_list: ServiceList = serde_json::from_value(json_service_list).unwrap(); - let output = get_sdl_for_service("harambe-was-an-inside-job", service_list); + let output = get_sdl_for_service(service_list, "harambe-was-an-inside-job"); assert!(output.is_err()); } - - fn mock_graph_ref() -> GraphRef { - GraphRef { - name: "mygraph".to_string(), - variant: "current".to_string(), - } - } } diff --git a/crates/rover-client/src/operations/subgraph/introspect/runner.rs b/crates/rover-client/src/query/subgraph/introspect.rs similarity index 57% rename from crates/rover-client/src/operations/subgraph/introspect/runner.rs rename to crates/rover-client/src/query/subgraph/introspect.rs index 9009c2942..8a2a0b206 100644 --- a/crates/rover-client/src/operations/subgraph/introspect/runner.rs +++ b/crates/rover-client/src/query/subgraph/introspect.rs @@ -1,25 +1,29 @@ use crate::blocking::GraphQLClient; -use crate::operations::subgraph::introspect::types::*; use crate::RoverClientError; - use graphql_client::*; +use std::collections::HashMap; #[derive(GraphQLQuery)] #[graphql( - query_path = "src/operations/subgraph/introspect/introspect_query.graphql", - schema_path = "src/operations/subgraph/introspect/introspect_schema.graphql", + query_path = "src/query/subgraph/introspect_query.graphql", + schema_path = "src/query/subgraph/introspect_schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] -pub(crate) struct SubgraphIntrospectQuery; +pub struct IntrospectionQuery; + +#[derive(Debug, PartialEq)] +pub struct IntrospectionResponse { + pub result: String, +} pub fn run( - input: SubgraphIntrospectInput, client: &GraphQLClient, -) -> Result { - let response_data = - client.post::(input.clone().into(), &input.headers); + headers: &HashMap, +) -> Result { + let variables = introspection_query::Variables {}; + let response_data = client.post::(variables, headers); match response_data { Ok(data) => build_response(data), @@ -35,12 +39,14 @@ pub fn run( } } -fn build_response(data: QueryResponseData) -> Result { +fn build_response( + data: introspection_query::ResponseData, +) -> Result { let service_data = data.service.ok_or(RoverClientError::IntrospectionError { msg: "No introspection response available.".to_string(), })?; - Ok(SubgraphIntrospectResponse { + Ok(IntrospectionResponse { result: service_data.sdl, }) } diff --git a/crates/rover-client/src/query/subgraph/introspect_query.graphql b/crates/rover-client/src/query/subgraph/introspect_query.graphql new file mode 100644 index 000000000..b29c43696 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/introspect_query.graphql @@ -0,0 +1,5 @@ +query IntrospectionQuery{ + _service { + sdl + } +} \ No newline at end of file diff --git a/crates/rover-client/src/query/subgraph/introspect_schema.graphql b/crates/rover-client/src/query/subgraph/introspect_schema.graphql new file mode 100644 index 000000000..52fd06ab0 --- /dev/null +++ b/crates/rover-client/src/query/subgraph/introspect_schema.graphql @@ -0,0 +1,11 @@ +schema { + query: Query +} + +type Query { + _service: _Service +} + +type _Service { + sdl: String! +} diff --git a/crates/rover-client/src/operations/subgraph/list/list_query.graphql b/crates/rover-client/src/query/subgraph/list.graphql similarity index 72% rename from crates/rover-client/src/operations/subgraph/list/list_query.graphql rename to crates/rover-client/src/query/subgraph/list.graphql index 06a50be9a..d733b65aa 100644 --- a/crates/rover-client/src/operations/subgraph/list/list_query.graphql +++ b/crates/rover-client/src/query/subgraph/list.graphql @@ -1,6 +1,6 @@ -query SubgraphListQuery($graph_id: ID!, $variant: String!) { +query ListSubgraphsQuery($graphId: ID!, $variant: String!) { frontendUrlRoot - service(id: $graph_id) { + service(id: $graphId) { implementingServices(graphVariant: $variant) { __typename ... on FederatedImplementingServices { diff --git a/crates/rover-client/src/operations/subgraph/list/runner.rs b/crates/rover-client/src/query/subgraph/list.rs similarity index 66% rename from crates/rover-client/src/operations/subgraph/list/runner.rs rename to crates/rover-client/src/query/subgraph/list.rs index 2b9912783..a7752d5f7 100644 --- a/crates/rover-client/src/operations/subgraph/list/runner.rs +++ b/crates/rover-client/src/query/subgraph/list.rs @@ -1,50 +1,62 @@ use crate::blocking::StudioClient; -use crate::operations::subgraph::list::types::*; -use crate::shared::GraphRef; use crate::RoverClientError; - +use chrono::prelude::*; use graphql_client::*; type Timestamp = String; + #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/subgraph/list/list_query.graphql", + query_path = "src/query/subgraph/list.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. subgraph_list_query -pub(crate) struct SubgraphListQuery; +/// Snake case of this name is the mod name. i.e. list_subgraphs_query +pub struct ListSubgraphsQuery; + +#[derive(Clone, PartialEq, Debug)] +pub struct SubgraphInfo { + pub name: String, + pub url: Option, // optional, and may not be a real url + pub updated_at: Option>, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct ListDetails { + pub subgraphs: Vec, + pub root_url: String, + pub graph_name: String, +} /// Fetches list of subgraphs for a given graph, returns name & url of each pub fn run( - input: SubgraphListInput, + variables: list_subgraphs_query::Variables, client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; +) -> Result { + let graph = variables.graph_id.clone(); + let response_data = client.post::(variables)?; let root_url = response_data.frontend_url_root.clone(); - let subgraphs = get_subgraphs_from_response_data(response_data, graph_ref.clone())?; - Ok(SubgraphListResponse { + let subgraphs = get_subgraphs_from_response_data(response_data, graph.clone())?; + Ok(ListDetails { subgraphs: format_subgraphs(&subgraphs), root_url, - graph_ref, + graph_name: graph, }) } +type RawSubgraphInfo = list_subgraphs_query::ListSubgraphsQueryServiceImplementingServicesOnFederatedImplementingServicesServices; fn get_subgraphs_from_response_data( - response_data: QueryResponseData, - graph_ref: GraphRef, -) -> Result, RoverClientError> { - let service_data = response_data - .service - .ok_or(RoverClientError::GraphNotFound { - graph_ref: graph_ref.clone(), - })?; + response_data: list_subgraphs_query::ResponseData, + graph: String, +) -> Result, RoverClientError> { + let service_data = response_data.service.ok_or(RoverClientError::NoService { + graph: graph.clone(), + })?; // get list of services let services = match service_data.implementing_services { @@ -60,35 +72,31 @@ fn get_subgraphs_from_response_data( // implementing_services.services match services { - QueryGraphType::FederatedImplementingServices(services) => Ok(services.services), - QueryGraphType::NonFederatedImplementingService => { - Err(RoverClientError::ExpectedFederatedGraph { - graph_ref, - can_operation_convert: false, - }) + list_subgraphs_query::ListSubgraphsQueryServiceImplementingServices::FederatedImplementingServices (services) => { + Ok(services.services) + }, + list_subgraphs_query::ListSubgraphsQueryServiceImplementingServices::NonFederatedImplementingService => { + Err(RoverClientError::ExpectedFederatedGraph { graph, can_operation_convert: false }) } } } /// puts the subgraphs into a vec of SubgraphInfo, sorted by updated_at /// timestamp. Newer updated services will show at top of list -fn format_subgraphs(subgraphs: &[QuerySubgraphInfo]) -> Vec { +fn format_subgraphs(subgraphs: &[RawSubgraphInfo]) -> Vec { let mut subgraphs: Vec = subgraphs .iter() .map(|subgraph| SubgraphInfo { name: subgraph.name.clone(), url: subgraph.url.clone(), - updated_at: SubgraphUpdatedAt { - local: subgraph.updated_at.clone().parse().ok(), - utc: subgraph.updated_at.clone().parse().ok(), - }, + updated_at: subgraph.updated_at.clone().parse::>().ok(), }) .collect(); // sort and reverse, so newer items come first. We use _unstable here, since // we don't care which order equal items come in the list (it's unlikely that // we'll even have equal items after all) - subgraphs.sort_unstable_by(|a, b| a.updated_at.utc.cmp(&b.updated_at.utc).reverse()); + subgraphs.sort_unstable_by(|a, b| a.updated_at.cmp(&b.updated_at).reverse()); subgraphs } @@ -120,8 +128,9 @@ mod tests { } } }); - let data: QueryResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_subgraphs_from_response_data(data, mock_graph_ref()); + let data: list_subgraphs_query::ResponseData = + serde_json::from_value(json_response).unwrap(); + let output = get_subgraphs_from_response_data(data, "mygraph".to_string()); let expected_json = json!([ { @@ -135,7 +144,7 @@ mod tests { "updatedAt": "2020-09-16T19:22:06.420Z" } ]); - let expected_service_list: Vec = + let expected_service_list: Vec = serde_json::from_value(expected_json).unwrap(); assert!(output.is_ok()); @@ -150,8 +159,9 @@ mod tests { "implementingServices": null } }); - let data: QueryResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_subgraphs_from_response_data(data, mock_graph_ref()); + let data: list_subgraphs_query::ResponseData = + serde_json::from_value(json_response).unwrap(); + let output = get_subgraphs_from_response_data(data, "mygraph".to_string()); assert!(output.is_err()); } @@ -174,17 +184,10 @@ mod tests { "updatedAt": "2020-09-16T19:22:06.420Z" } ]); - let raw_subgraph_list: Vec = + let raw_subgraph_list: Vec = serde_json::from_value(raw_info_json).unwrap(); let formatted = format_subgraphs(&raw_subgraph_list); assert_eq!(formatted[0].name, "accounts".to_string()); assert_eq!(formatted[2].name, "shipping".to_string()); } - - fn mock_graph_ref() -> GraphRef { - GraphRef { - name: "mygraph".to_string(), - variant: "current".to_string(), - } - } } diff --git a/crates/rover-client/src/operations/subgraph/mod.rs b/crates/rover-client/src/query/subgraph/mod.rs similarity index 100% rename from crates/rover-client/src/operations/subgraph/mod.rs rename to crates/rover-client/src/query/subgraph/mod.rs diff --git a/crates/rover-client/src/query/subgraph/publish.graphql b/crates/rover-client/src/query/subgraph/publish.graphql new file mode 100644 index 000000000..6acd42bcf --- /dev/null +++ b/crates/rover-client/src/query/subgraph/publish.graphql @@ -0,0 +1,29 @@ +mutation PublishPartialSchemaMutation( + $graphId: ID! + $graphVariant: String! + $name: String! + $url: String + $revision: String! + $activePartialSchema: PartialSchemaInput! + $gitContext: GitContextInput! +) { + service(id: $graphId) { + upsertImplementingServiceAndTriggerComposition( + name: $name + url: $url + revision: $revision + activePartialSchema: $activePartialSchema + graphVariant: $graphVariant + gitContext: $gitContext + ) { + compositionConfig { + schemaHash + } + errors { + message + } + didUpdateGateway: updatedGateway + serviceWasCreated: wasCreated + } + } +} diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/query/subgraph/publish.rs similarity index 51% rename from crates/rover-client/src/operations/subgraph/publish/runner.rs rename to crates/rover-client/src/query/subgraph/publish.rs index 1394d4664..c3b828bd4 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/query/subgraph/publish.rs @@ -1,7 +1,6 @@ -use super::types::*; +// PublishPartialSchemaMutation use crate::blocking::StudioClient; -use crate::operations::config::is_federated::{self, IsFederatedInput}; -use crate::shared::{BuildError, BuildErrors, GraphRef}; +use crate::query::config::is_federated; use crate::RoverClientError; use graphql_client::*; @@ -9,52 +8,62 @@ use graphql_client::*; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/subgraph/publish/publish_mutation.graphql", + query_path = "src/query/subgraph/publish.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. subgraph_publish_mutation -pub(crate) struct SubgraphPublishMutation; +/// Snake case of this name is the mod name. i.e. publish_partial_schema_mutation +pub struct PublishPartialSchemaMutation; + +#[derive(Debug, PartialEq)] +pub struct PublishPartialSchemaResponse { + pub schema_hash: Option, + pub did_update_gateway: bool, + pub service_was_created: bool, + pub composition_errors: Option>, +} pub fn run( - input: SubgraphPublishInput, + variables: publish_partial_schema_mutation::Variables, client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let variables: MutationVariables = input.clone().into(); + convert_to_federated_graph: bool, +) -> Result { + let graph = variables.graph_id.clone(); // We don't want to implicitly convert non-federated graph to supergraphs. // Error here if no --convert flag is passed _and_ the current context // is non-federated. Add a suggestion to require a --convert flag. - if !input.convert_to_federated_graph { + if !convert_to_federated_graph { let is_federated = is_federated::run( - IsFederatedInput { - graph_ref: graph_ref.clone(), + is_federated::is_federated_graph::Variables { + graph_id: variables.graph_id.clone(), + graph_variant: variables.graph_variant.clone(), }, client, )?; if !is_federated { return Err(RoverClientError::ExpectedFederatedGraph { - graph_ref, + graph, can_operation_convert: true, }); } } - let data = client.post::(variables)?; - let publish_response = get_publish_response_from_data(data, graph_ref)?; + let data = client.post::(variables)?; + let publish_response = get_publish_response_from_data(data, graph)?; Ok(build_response(publish_response)) } +// alias this return type since it's disgusting +type UpdateResponse = publish_partial_schema_mutation::PublishPartialSchemaMutationServiceUpsertImplementingServiceAndTriggerComposition; + fn get_publish_response_from_data( - data: ResponseData, - graph_ref: GraphRef, + data: publish_partial_schema_mutation::ResponseData, + graph: String, ) -> Result { - let service_data = data - .service - .ok_or(RoverClientError::GraphNotFound { graph_ref })?; + let service_data = data.service.ok_or(RoverClientError::NoService { graph })?; service_data .upsert_implementing_service_and_trigger_composition @@ -63,25 +72,28 @@ fn get_publish_response_from_data( }) } -fn build_response(publish_response: UpdateResponse) -> SubgraphPublishResponse { - let build_errors: BuildErrors = publish_response +fn build_response(publish_response: UpdateResponse) -> PublishPartialSchemaResponse { + let composition_errors: Vec = publish_response .errors .iter() - .filter_map(|error| { - error - .as_ref() - .map(|e| BuildError::composition_error(e.message.clone(), e.code.clone())) - }) + .filter_map(|error| error.as_ref().map(|e| e.message.clone())) .collect(); - SubgraphPublishResponse { - api_schema_hash: match publish_response.composition_config { + // if there are no errors, just return None + let composition_errors = if !composition_errors.is_empty() { + Some(composition_errors) + } else { + None + }; + + PublishPartialSchemaResponse { + schema_hash: match publish_response.composition_config { Some(config) => Some(config.schema_hash), None => None, }, - supergraph_was_updated: publish_response.did_update_gateway, - subgraph_was_created: publish_response.service_was_created, - build_errors, + did_update_gateway: publish_response.did_update_gateway, + service_was_created: publish_response.service_was_created, + composition_errors, } } @@ -94,15 +106,9 @@ mod tests { let json_response = json!({ "compositionConfig": { "schemaHash": "5gf564" }, "errors": [ - { - "message": "[Accounts] User -> build error", - "code": null - }, + {"message": "[Accounts] User -> composition error"}, null, // this is technically allowed in the types - { - "message": "[Products] Product -> another one", - "code": "ERROR" - } + {"message": "[Products] Product -> another one"} ], "didUpdateGateway": false, "serviceWasCreated": true @@ -112,21 +118,14 @@ mod tests { assert_eq!( output, - SubgraphPublishResponse { - api_schema_hash: Some("5gf564".to_string()), - build_errors: vec![ - BuildError::composition_error( - "[Accounts] User -> build error".to_string(), - None - ), - BuildError::composition_error( - "[Products] Product -> another one".to_string(), - Some("ERROR".to_string()) - ) - ] - .into(), - supergraph_was_updated: false, - subgraph_was_created: true, + PublishPartialSchemaResponse { + schema_hash: Some("5gf564".to_string()), + composition_errors: Some(vec![ + "[Accounts] User -> composition error".to_string(), + "[Products] Product -> another one".to_string() + ]), + did_update_gateway: false, + service_was_created: true, } ); } @@ -144,11 +143,11 @@ mod tests { assert_eq!( output, - SubgraphPublishResponse { - api_schema_hash: Some("5gf564".to_string()), - build_errors: BuildErrors::new(), - supergraph_was_updated: true, - subgraph_was_created: true, + PublishPartialSchemaResponse { + schema_hash: Some("5gf564".to_string()), + composition_errors: None, + did_update_gateway: true, + service_was_created: true, } ); } @@ -159,10 +158,7 @@ mod tests { fn build_response_works_with_failure_and_no_hash() { let json_response = json!({ "compositionConfig": null, - "errors": [{ - "message": "[Accounts] -> Things went really wrong", - "code": null - }], + "errors": [{ "message": "[Accounts] -> Things went really wrong" }], "didUpdateGateway": false, "serviceWasCreated": false }); @@ -171,15 +167,13 @@ mod tests { assert_eq!( output, - SubgraphPublishResponse { - api_schema_hash: None, - build_errors: vec![BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), - None - )] - .into(), - supergraph_was_updated: false, - subgraph_was_created: false, + PublishPartialSchemaResponse { + schema_hash: None, + composition_errors: Some( + vec!["[Accounts] -> Things went really wrong".to_string()] + ), + did_update_gateway: false, + service_was_created: false, } ); } diff --git a/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql b/crates/rover-client/src/query/supergraph/fetch.graphql similarity index 71% rename from crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql rename to crates/rover-client/src/query/supergraph/fetch.graphql index 0e1aebc68..5436b091a 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/fetch_query.graphql +++ b/crates/rover-client/src/query/supergraph/fetch.graphql @@ -1,6 +1,6 @@ -query SupergraphFetchQuery($graph_id: ID!, $variant: String!) { +query FetchSupergraphQuery($graphId: ID!, $variant: String!) { frontendUrlRoot - service(id: $graph_id) { + service(id: $graphId) { variants { name } @@ -13,8 +13,7 @@ query SupergraphFetchQuery($graph_id: ID!, $variant: String!) { mostRecentCompositionPublish(graphVariant: $variant) { errors { message - code } } } -} +} \ No newline at end of file diff --git a/crates/rover-client/src/operations/supergraph/fetch/runner.rs b/crates/rover-client/src/query/supergraph/fetch.rs similarity index 65% rename from crates/rover-client/src/operations/supergraph/fetch/runner.rs rename to crates/rover-client/src/query/supergraph/fetch.rs index 8374c6d51..dc1b7b235 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/runner.rs +++ b/crates/rover-client/src/query/supergraph/fetch.rs @@ -1,8 +1,5 @@ use crate::blocking::StudioClient; -use crate::operations::supergraph::fetch::SupergraphFetchInput; -use crate::shared::{BuildError, BuildErrors, FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; - use graphql_client::*; // I'm not sure where this should live long-term @@ -13,46 +10,41 @@ type GraphQLDocument = String; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema #[graphql( - query_path = "src/operations/supergraph/fetch/fetch_query.graphql", + query_path = "src/query/supergraph/fetch.graphql", schema_path = ".schema/schema.graphql", response_derives = "PartialEq, Debug, Serialize, Deserialize", deprecated = "warn" )] /// This struct is used to generate the module containing `Variables` and /// `ResponseData` structs. -/// Snake case of this name is the mod name. i.e. supergraph_fetch_query -pub(crate) struct SupergraphFetchQuery; +/// Snake case of this name is the mod name. i.e. fetch_supergraph_query +pub struct FetchSupergraphQuery; /// The main function to be used from this module. This function fetches a /// core schema from apollo studio pub fn run( - input: SupergraphFetchInput, + variables: fetch_supergraph_query::Variables, client: &StudioClient, -) -> Result { - let graph_ref = input.graph_ref.clone(); - let response_data = client.post::(input.into())?; - get_supergraph_sdl_from_response_data(response_data, graph_ref) +) -> Result { + let graph = variables.graph_id.clone(); + let variant = variables.variant.clone(); + let response_data = client.post::(variables)?; + get_supergraph_sdl_from_response_data(response_data, graph, variant) } fn get_supergraph_sdl_from_response_data( - response_data: supergraph_fetch_query::ResponseData, - graph_ref: GraphRef, -) -> Result { - let service_data = response_data - .service - .ok_or(RoverClientError::GraphNotFound { - graph_ref: graph_ref.clone(), - })?; + response_data: fetch_supergraph_query::ResponseData, + graph: String, + variant: String, +) -> Result { + let service_data = response_data.service.ok_or(RoverClientError::NoService { + graph: graph.clone(), + })?; if let Some(schema_tag) = service_data.schema_tag { if let Some(composition_result) = schema_tag.composition_result { - if let Some(sdl_contents) = composition_result.supergraph_sdl { - Ok(FetchResponse { - sdl: Sdl { - contents: sdl_contents, - r#type: SdlType::Supergraph, - }, - }) + if let Some(supergraph_sdl) = composition_result.supergraph_sdl { + Ok(supergraph_sdl) } else { Err(RoverClientError::MalformedResponse { null_field: "supergraphSdl".to_string(), @@ -60,21 +52,21 @@ fn get_supergraph_sdl_from_response_data( } } else { Err(RoverClientError::ExpectedFederatedGraph { - graph_ref, + graph, can_operation_convert: false, }) } } else if let Some(most_recent_composition_publish) = service_data.most_recent_composition_publish { - let build_errors: BuildErrors = most_recent_composition_publish + let composition_errors = most_recent_composition_publish .errors .into_iter() - .map(|error| BuildError::composition_error(error.message, error.code)) + .map(|error| error.message) .collect(); - Err(RoverClientError::NoSupergraphBuilds { - graph_ref, - source: build_errors, + Err(RoverClientError::NoCompositionPublishes { + graph, + composition_errors, }) } else { let mut valid_variants = Vec::new(); @@ -83,15 +75,16 @@ fn get_supergraph_sdl_from_response_data( valid_variants.push(variant.name) } - if !valid_variants.contains(&graph_ref.variant) { + if !valid_variants.contains(&variant) { Err(RoverClientError::NoSchemaForVariant { - graph_ref, + graph, + invalid_variant: variant, valid_variants, frontend_url_root: response_data.frontend_url_root, }) } else { Err(RoverClientError::ExpectedFederatedGraph { - graph_ref, + graph, can_operation_convert: false, }) } @@ -119,75 +112,59 @@ mod tests { } }, }); - let data: supergraph_fetch_query::ResponseData = + let data: fetch_supergraph_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_supergraph_sdl_from_response_data(data, graph_ref); + let (graph, invalid_variant) = mock_vars(); + let output = get_supergraph_sdl_from_response_data(data, graph, invalid_variant); assert!(output.is_ok()); - assert_eq!( - output.unwrap(), - FetchResponse { - sdl: Sdl { - contents: "type Query { hello: String }".to_string(), - r#type: SdlType::Supergraph - } - } - ); + assert_eq!(output.unwrap(), "type Query { hello: String }".to_string()); } #[test] fn get_schema_from_response_data_errs_on_no_service() { let json_response = json!({ "service": null, "frontendUrlRoot": "https://studio.apollographql.com" }); - let data: supergraph_fetch_query::ResponseData = + let data: fetch_supergraph_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); - let expected_error = RoverClientError::GraphNotFound { graph_ref }.to_string(); + let (graph, invalid_variant) = mock_vars(); + let output = get_supergraph_sdl_from_response_data(data, graph.clone(), invalid_variant); + let expected_error = RoverClientError::NoService { graph }.to_string(); let actual_error = output.unwrap_err().to_string(); assert_eq!(actual_error, expected_error); } #[test] fn get_schema_from_response_data_errs_on_no_schema_tag() { - let build_errors = vec![ - BuildError::composition_error( - "Unknown type \"Unicorn\".".to_string(), - Some("UNKNOWN_TYPE".to_string()), - ), - BuildError::composition_error( - "Type Query must define one or more fields.".to_string(), - None, - ), + let (graph, variant) = mock_vars(); + let composition_errors = vec![ + "Unknown type \"Unicorn\".".to_string(), + "Type Query must define one or more fields.".to_string(), ]; - let build_errors_json = json!([ + let composition_errors_json = json!([ { - "message": build_errors[0].get_message(), - "code": build_errors[0].get_code() + "message": composition_errors[0] }, { - "message": build_errors[1].get_message(), - "code": build_errors[1].get_code() + "message": composition_errors[1] } ]); - let graph_ref = mock_graph_ref(); let json_response = json!({ "frontendUrlRoot": "https://studio.apollographql.com/", "service": { "schemaTag": null, - "variants": [{"name": &graph_ref.variant}], + "variants": [{"name": variant}], "mostRecentCompositionPublish": { - "errors": build_errors_json, + "errors": composition_errors_json } }, }); - let data: supergraph_fetch_query::ResponseData = + let data: fetch_supergraph_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); - let expected_error = RoverClientError::NoSupergraphBuilds { - graph_ref, - source: build_errors.into(), + let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant); + let expected_error = RoverClientError::NoCompositionPublishes { + graph, + composition_errors, } .to_string(); let actual_error = output.unwrap_err().to_string(); @@ -196,6 +173,7 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_invalid_variant() { + let (graph, variant) = mock_vars(); let valid_variant = "cccuuurrreeennnttt".to_string(); let frontend_url_root = "https://studio.apollographql.com".to_string(); let json_response = json!({ @@ -206,12 +184,12 @@ mod tests { "mostRecentCompositionPublish": null }, }); - let data: supergraph_fetch_query::ResponseData = + let data: fetch_supergraph_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); + let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant.clone()); let expected_error = RoverClientError::NoSchemaForVariant { - graph_ref, + graph, + invalid_variant: variant, valid_variants: vec![valid_variant], frontend_url_root, } @@ -222,6 +200,7 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_no_composition_result() { + let (graph, variant) = mock_vars(); let valid_variant = "current".to_string(); let frontend_url_root = "https://studio.apollographql.com".to_string(); let json_response = json!({ @@ -234,12 +213,11 @@ mod tests { "mostRecentCompositionResult": null }, }); - let data: supergraph_fetch_query::ResponseData = + let data: fetch_supergraph_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); + let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant.clone()); let expected_error = RoverClientError::ExpectedFederatedGraph { - graph_ref, + graph, can_operation_convert: false, } .to_string(); @@ -249,6 +227,7 @@ mod tests { #[test] fn get_schema_from_response_data_errs_on_no_supergraph_sdl() { + let (graph, variant) = mock_vars(); let valid_variant = "current".to_string(); let frontend_url_root = "https://studio.apollographql.com".to_string(); let json_response = json!({ @@ -264,10 +243,9 @@ mod tests { "mostRecentCompositionPublish": null }, }); - let data: supergraph_fetch_query::ResponseData = + let data: fetch_supergraph_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let graph_ref = mock_graph_ref(); - let output = get_supergraph_sdl_from_response_data(data, graph_ref.clone()); + let output = get_supergraph_sdl_from_response_data(data, graph.clone(), variant.clone()); let expected_error = RoverClientError::MalformedResponse { null_field: "supergraphSdl".to_string(), } @@ -276,10 +254,7 @@ mod tests { assert_eq!(actual_error, expected_error); } - fn mock_graph_ref() -> GraphRef { - GraphRef { - name: "mygraph".to_string(), - variant: "current".to_string(), - } + fn mock_vars() -> (String, String) { + ("mygraph".to_string(), "current".to_string()) } } diff --git a/crates/rover-client/src/operations/supergraph/mod.rs b/crates/rover-client/src/query/supergraph/mod.rs similarity index 100% rename from crates/rover-client/src/operations/supergraph/mod.rs rename to crates/rover-client/src/query/supergraph/mod.rs diff --git a/crates/rover-client/src/releases.rs b/crates/rover-client/src/releases.rs index 8317c4a11..544636899 100644 --- a/crates/rover-client/src/releases.rs +++ b/crates/rover-client/src/releases.rs @@ -1,28 +1,26 @@ use crate::RoverClientError; - +use regex::Regex; use reqwest::blocking::Client; -pub use semver::Version; const LATEST_RELEASE_URL: &str = "https://github.com/apollographql/rover/releases/latest"; -/// Looks up and parses the latest release version -pub fn get_latest_release(client: Client) -> Result { - // send a request to the latest GitHub release - let response = client.head(LATEST_RELEASE_URL).send()?; - - // this will return a response with a redirect to the latest tagged release - let url_path_segments = response - .url() - .path_segments() - .ok_or(RoverClientError::BadReleaseUrl)?; +/// Looks up the latest release version, and returns it as a string +pub fn get_latest_release(client: Client) -> Result { + let res = client.head(LATEST_RELEASE_URL).send()?; - // the last section of the URL will have the latest version in `v0.1.1` format - let version_string = url_path_segments - .last() - .ok_or(RoverClientError::BadReleaseUrl)? - .to_string(); + let release_url = res.url().to_string(); + let release_url_parts: Vec<&str> = release_url.split('/').collect(); - // strip the `v` prefix from the last section of the URL before parsing - Version::parse(&version_string[1..]) - .map_err(|source| RoverClientError::UnparseableReleaseVersion { source }) + match release_url_parts.last() { + Some(version) => { + // Parse out the semver version (ex. v1.0.0 -> 1.0.0) + let re = Regex::new(r"^v[0-9]*\.[0-9]*\.[0-9]*$").unwrap(); + if re.is_match(version) { + Ok(version.to_string().replace('v', "")) + } else { + Err(RoverClientError::UnparseableReleaseVersion) + } + } + None => Err(RoverClientError::UnparseableReleaseVersion), + } } diff --git a/crates/rover-client/src/shared/build_errors.rs b/crates/rover-client/src/shared/build_errors.rs deleted file mode 100644 index 1e7e66c8c..000000000 --- a/crates/rover-client/src/shared/build_errors.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::{ - error::Error, - fmt::{self, Display}, - iter::FromIterator, -}; - -use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct BuildError { - message: String, - code: Option, - r#type: BuildErrorType, -} - -impl BuildError { - pub fn composition_error(message: String, code: Option) -> BuildError { - BuildError { - message, - code, - r#type: BuildErrorType::Composition, - } - } - - pub fn get_message(&self) -> String { - self.message.clone() - } - - pub fn get_code(&self) -> Option { - self.code.clone() - } -} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum BuildErrorType { - Composition, -} - -impl Display for BuildError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(code) = &self.code { - write!(f, "{}: ", code)?; - } else { - write!(f, "UNKNOWN: ")?; - } - write!(f, "{}", &self.message) - } -} - -#[derive(Debug, Deserialize, Default, Clone, PartialEq)] -pub struct BuildErrors { - build_errors: Vec, -} - -impl Serialize for BuildErrors { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut sequence = serializer.serialize_seq(Some(self.build_errors.len()))?; - for build_error in &self.build_errors { - sequence.serialize_element(build_error)?; - } - sequence.end() - } -} - -impl BuildErrors { - pub fn new() -> Self { - BuildErrors { - build_errors: Vec::new(), - } - } - - pub fn len(&self) -> usize { - self.build_errors.len() - } - - pub fn length_string(&self) -> String { - let num_failures = self.build_errors.len(); - if num_failures == 0 { - unreachable!("No build errors were encountered while composing the supergraph."); - } - - match num_failures { - 1 => "1 build error".to_string(), - _ => format!("{} build errors", num_failures), - } - } - - pub fn push(&mut self, error: BuildError) { - self.build_errors.push(error); - } - - pub fn is_empty(&self) -> bool { - self.build_errors.is_empty() - } -} - -impl Display for BuildErrors { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for build_error in &self.build_errors { - writeln!(f, "{}", build_error)?; - } - Ok(()) - } -} - -impl From> for BuildErrors { - fn from(build_errors: Vec) -> Self { - BuildErrors { build_errors } - } -} - -impl FromIterator for BuildErrors { - fn from_iter>(iter: I) -> Self { - let mut c = BuildErrors::new(); - - for i in iter { - c.push(i); - } - - c - } -} - -impl Error for BuildError {} -impl Error for BuildErrors {} - -#[cfg(test)] -mod tests { - use super::{BuildError, BuildErrors}; - - use serde_json::{json, Value}; - - #[test] - fn it_can_serialize_empty_errors() { - let build_errors = BuildErrors::new(); - assert_eq!( - serde_json::to_string(&build_errors).expect("Could not serialize build errors"), - json!([]).to_string() - ); - } - - #[test] - fn it_can_serialize_some_build_errors() { - let build_errors: BuildErrors = vec![ - BuildError::composition_error("wow".to_string(), None), - BuildError::composition_error("boo".to_string(), Some("BOO".to_string())), - ] - .into(); - - let actual_value: Value = serde_json::from_str( - &serde_json::to_string(&build_errors) - .expect("Could not convert build errors to string"), - ) - .expect("Could not convert build error string to serde_json::Value"); - - let expected_value = json!([ - { - "code": null, - "message": "wow", - "type": "composition" - }, - { - "code": "BOO", - "message": "boo", - "type": "composition" - } - ]); - assert_eq!(actual_value, expected_value); - } -} diff --git a/crates/rover-client/src/shared/check_response.rs b/crates/rover-client/src/shared/check_response.rs deleted file mode 100644 index 2831ab25a..000000000 --- a/crates/rover-client/src/shared/check_response.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self}; -use std::str::FromStr; - -use crate::shared::GraphRef; -use crate::RoverClientError; - -use prettytable::format::consts::FORMAT_BOX_CHARS; -use serde::Serialize; - -use prettytable::{cell, row, Table}; -use serde_json::{json, Value}; - -/// CheckResponse is the return type of the -/// `graph` and `subgraph` check operations -#[derive(Debug, Serialize, Clone, PartialEq)] -pub struct CheckResponse { - target_url: Option, - operation_check_count: u64, - changes: Vec, - #[serde(skip_serializing)] - result: ChangeSeverity, - failure_count: u64, -} - -impl CheckResponse { - pub fn try_new( - target_url: Option, - operation_check_count: u64, - changes: Vec, - result: ChangeSeverity, - graph_ref: GraphRef, - ) -> Result { - let mut failure_count = 0; - for change in &changes { - if let ChangeSeverity::FAIL = change.severity { - failure_count += 1; - } - } - - let check_response = CheckResponse { - target_url, - operation_check_count, - changes, - result, - failure_count, - }; - - match failure_count.cmp(&0) { - Ordering::Equal => Ok(check_response), - Ordering::Greater => Err(RoverClientError::OperationCheckFailure { - graph_ref, - check_response, - }), - Ordering::Less => unreachable!("Somehow encountered a negative number of failures."), - } - } - - pub fn get_table(&self) -> String { - let num_changes = self.changes.len(); - - let mut msg = match num_changes { - 0 => "There were no changes detected in the composed schema.".to_string(), - _ => format!( - "Compared {} schema changes against {} operations", - num_changes, self.operation_check_count - ), - }; - - msg.push('\n'); - - if !self.changes.is_empty() { - let mut table = Table::new(); - - table.set_format(*FORMAT_BOX_CHARS); - - // bc => sets top row to be bold and center - table.add_row(row![bc => "Change", "Code", "Description"]); - for check in &self.changes { - table.add_row(row![check.severity, check.code, check.description]); - } - - msg.push_str(&table.to_string()); - } - - if let Some(url) = &self.target_url { - msg.push_str(&format!("View full details at {}", url)); - } - - msg - } - - pub fn get_failure_count(&self) -> u64 { - self.failure_count - } - - pub fn get_json(&self) -> Value { - json!(self) - } -} - -/// ChangeSeverity indicates whether a proposed change -/// in a GraphQL schema passed or failed the check -#[derive(Debug, Serialize, Clone, PartialEq)] -pub enum ChangeSeverity { - /// The proposed schema has passed the checks - PASS, - - /// The proposed schema has failed the checks - FAIL, -} - -impl ChangeSeverity { - // This message should be used when matching on a - // ChangeSeverity originating from auto-generated - // types from graphql-client - // - // We want to panic in this situation so that we - // get bug reports if Rover doesn't know the proper type - pub(crate) fn unreachable() -> ! { - unreachable!("Unknown change severity") - } -} - -impl fmt::Display for ChangeSeverity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let msg = match self { - ChangeSeverity::PASS => "PASS", - ChangeSeverity::FAIL => "FAIL", - }; - write!(f, "{}", msg) - } -} - -#[derive(Debug, Serialize, Clone, PartialEq)] -pub struct SchemaChange { - /// The code associated with a given change - /// e.g. 'TYPE_REMOVED' - pub code: String, - - /// Explanation of a given change - pub description: String, - - /// The severity of a given change - pub severity: ChangeSeverity, -} - -/// CheckConfig is used as an input to check operations -#[derive(Debug, Clone, PartialEq)] -pub struct CheckConfig { - pub query_count_threshold: Option, - pub query_count_threshold_percentage: Option, - pub validation_period: Option, -} - -#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone)] -pub struct ValidationPeriod { - pub from: i64, - pub to: i64, -} - -// Validation period is parsed as human readable time. -// such as "10m 50s" -impl FromStr for ValidationPeriod { - type Err = RoverClientError; - fn from_str(period: &str) -> Result { - // attempt to parse strings like - // 15h 10m 2s into number of seconds - if period.contains("ns") || period.contains("us") || period.contains("ms") { - return Err(RoverClientError::ValidationPeriodTooGranular); - }; - let duration = humantime::parse_duration(period)?; - - let from = duration.as_secs() as i64; - let from = -from; - - let to = 0; - - Ok(ValidationPeriod { - // search "from" a negative time window - from: -from, - // search "to" now (-0) seconds - to: -to, - }) - } -} diff --git a/crates/rover-client/src/shared/fetch_response.rs b/crates/rover-client/src/shared/fetch_response.rs deleted file mode 100644 index b5ddc80bb..000000000 --- a/crates/rover-client/src/shared/fetch_response.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::Serialize; - -#[derive(Debug, Clone, Serialize, PartialEq)] -pub struct FetchResponse { - pub sdl: Sdl, -} - -#[derive(Debug, Clone, Serialize, PartialEq)] -pub struct Sdl { - pub contents: String, - #[serde(skip_serializing)] - pub r#type: SdlType, -} - -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all(serialize = "lowercase"))] -pub enum SdlType { - Graph, - Subgraph, - Supergraph, -} diff --git a/crates/rover-client/src/shared/graph_ref.rs b/crates/rover-client/src/shared/graph_ref.rs deleted file mode 100644 index 99d0c16f1..000000000 --- a/crates/rover-client/src/shared/graph_ref.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::fmt; -use std::str::FromStr; - -use crate::RoverClientError; - -use regex::Regex; -use serde::Serialize; - -#[derive(Debug, Serialize, Clone, PartialEq)] -pub struct GraphRef { - pub name: String, - pub variant: String, -} - -impl fmt::Display for GraphRef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}@{}", self.name, self.variant) - } -} - -impl FromStr for GraphRef { - type Err = RoverClientError; - - /// NOTE: THIS IS A TEMPORARY SOLUTION. IN THE FUTURE, ALL GRAPH ID PARSING - /// WILL HAPPEN IN THE BACKEND TO KEEP EVERYTHING CONSISTENT. THIS IS AN - /// INCOMPLETE PLACEHOLDER, AND MAY NOT COVER EVERY SINGLE VALID USE CASE - fn from_str(graph_id: &str) -> Result { - let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]{0,63}$").unwrap(); - let variant_pattern = Regex::new(r"^([a-zA-Z][a-zA-Z0-9_-]{0,63})@(.{0,63})$").unwrap(); - - let valid_graph_name_only = pattern.is_match(graph_id); - let valid_graph_with_variant = variant_pattern.is_match(graph_id); - - if valid_graph_name_only { - Ok(GraphRef { - name: graph_id.to_string(), - variant: "current".to_string(), - }) - } else if valid_graph_with_variant { - let matches = variant_pattern.captures(graph_id).unwrap(); - let name = matches.get(1).unwrap().as_str(); - let variant = matches.get(2).unwrap().as_str(); - Ok(GraphRef { - name: name.to_string(), - variant: variant.to_string(), - }) - } else { - Err(RoverClientError::InvalidGraphRef) - } - } -} - -#[cfg(test)] -mod tests { - use super::GraphRef; - use std::str::FromStr; - - #[test] - fn from_str_works() { - assert!(GraphRef::from_str("engine#%^").is_err()); - assert!(GraphRef::from_str( - "1234567890123456789012345678901234567890123456789012345678901234567890" - ) - .is_err()); - assert!(GraphRef::from_str("1boi").is_err()); - assert!(GraphRef::from_str("_eng").is_err()); - assert!(GraphRef::from_str( - "engine@1234567890123456789012345678901234567890123456789012345678901234567890" - ) - .is_err()); - assert!(GraphRef::from_str( - "engine1234567890123456789012345678901234567890123456789012345678901234567890@prod" - ) - .is_err()); - - assert_eq!( - GraphRef::from_str("engine@okay").unwrap(), - GraphRef { - name: "engine".to_string(), - variant: "okay".to_string() - } - ); - assert_eq!( - GraphRef::from_str("studio").unwrap(), - GraphRef { - name: "studio".to_string(), - variant: "current".to_string() - } - ); - assert_eq!( - GraphRef::from_str("this_should_work").unwrap(), - GraphRef { - name: "this_should_work".to_string(), - variant: "current".to_string() - } - ); - assert_eq!( - GraphRef::from_str("it-is-cool@my-special/variant:from$hell").unwrap(), - GraphRef { - name: "it-is-cool".to_string(), - variant: "my-special/variant:from$hell".to_string() - } - ); - } -} diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs deleted file mode 100644 index e09603fb1..000000000 --- a/crates/rover-client/src/shared/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod build_errors; -mod check_response; -mod fetch_response; -mod git_context; -mod graph_ref; - -pub use build_errors::{BuildError, BuildErrors}; -pub use check_response::{ - ChangeSeverity, CheckConfig, CheckResponse, SchemaChange, ValidationPeriod, -}; -pub use fetch_response::{FetchResponse, Sdl, SdlType}; -pub use git_context::GitContext; -pub use graph_ref::GraphRef; diff --git a/crates/rover-client/src/operations/graph/introspect/fixtures/interfaces.json b/crates/rover-client/tests/fixtures/interfaces.json similarity index 100% rename from crates/rover-client/src/operations/graph/introspect/fixtures/interfaces.json rename to crates/rover-client/tests/fixtures/interfaces.json diff --git a/crates/rover-client/src/operations/graph/introspect/fixtures/simple.json b/crates/rover-client/tests/fixtures/simple.json similarity index 100% rename from crates/rover-client/src/operations/graph/introspect/fixtures/simple.json rename to crates/rover-client/tests/fixtures/simple.json diff --git a/crates/rover-client/src/operations/graph/introspect/fixtures/swapi.json b/crates/rover-client/tests/fixtures/swapi.json similarity index 100% rename from crates/rover-client/src/operations/graph/introspect/fixtures/swapi.json rename to crates/rover-client/tests/fixtures/swapi.json diff --git a/crates/rover-client/src/operations/graph/introspect/schema.rs b/crates/rover-client/tests/schema.rs similarity index 78% rename from crates/rover-client/src/operations/graph/introspect/schema.rs rename to crates/rover-client/tests/schema.rs index a6dbbe8f0..4678ec852 100644 --- a/crates/rover-client/src/operations/graph/introspect/schema.rs +++ b/crates/rover-client/tests/schema.rs @@ -1,374 +1,20 @@ -//! Schema encoding module used to work with Introspection result. -//! -//! More information on Schema Definition language(SDL) can be found in [this -//! documentation](https://www.apollographql.com/docs/apollo-server/schema/schema/). -//! -use sdl_encoder::{ - Directive, EnumDef, EnumValue, Field, InputField, InputObjectDef, InputValue, InterfaceDef, - ObjectDef, ScalarDef, Schema as SDL, SchemaDef, Type_, UnionDef, -}; -use serde::Deserialize; +use graphql_client::Response; +use rover_client::introspection::Schema; use std::convert::TryFrom; - -use crate::operations::graph::introspect::runner::graph_introspect_query; - -type FullTypeField = graph_introspect_query::FullTypeFields; -type FullTypeInputField = graph_introspect_query::FullTypeInputFields; -type FullTypeFieldArg = graph_introspect_query::FullTypeFieldsArgs; -type IntrospectionResult = graph_introspect_query::ResponseData; -type SchemaMutationType = graph_introspect_query::GraphIntrospectQuerySchemaMutationType; -type SchemaQueryType = graph_introspect_query::GraphIntrospectQuerySchemaQueryType; -type SchemaType = graph_introspect_query::GraphIntrospectQuerySchemaTypes; -type SchemaDirective = graph_introspect_query::GraphIntrospectQuerySchemaDirectives; -type SchemaSubscriptionType = graph_introspect_query::GraphIntrospectQuerySchemaSubscriptionType; -type __TypeKind = graph_introspect_query::__TypeKind; - -// Represents GraphQL types we will not be encoding to SDL. -const GRAPHQL_NAMED_TYPES: [&str; 12] = [ - "__Schema", - "__Type", - "__TypeKind", - "__Field", - "__InputValue", - "__EnumValue", - "__DirectiveLocation", - "__Directive", - "Boolean", - "String", - "Int", - "ID", -]; - -// Represents GraphQL directives we will not be encoding to SDL. -const SPECIFIED_DIRECTIVES: [&str; 3] = ["skip", "include", "deprecated"]; - -/// A representation of a GraphQL Schema. -/// -/// Contains Schema Types and Directives. -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Schema { - types: Vec, - directives: Vec, - mutation_type: Option, - query_type: SchemaQueryType, - subscription_type: Option, -} - -impl Schema { - /// Encode Schema into an SDL. - pub fn encode(self) -> String { - let mut sdl = SDL::new(); - - // When we have a defined mutation and subscription, we record - // everything to Schema Definition. - // https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/ - if self.mutation_type.is_some() | self.subscription_type.is_some() { - let mut schema_def = SchemaDef::new(); - if let Some(mutation_type) = self.mutation_type { - schema_def.mutation(mutation_type.name.unwrap()); - } - if let Some(subscription_type) = self.subscription_type { - schema_def.subscription(subscription_type.name.unwrap()); - } - if let Some(name) = self.query_type.name { - schema_def.query(name); - } - sdl.schema(schema_def); - } else if let Some(name) = self.query_type.name { - // If we don't have a mutation or a subscription, but do have a - // query type, only create a Schema Definition when it's something - // other than `Query`. - if name != "Query" { - let mut schema_def = SchemaDef::new(); - schema_def.query(name); - sdl.schema(schema_def); - } - } - - // Exclude GraphQL directives like 'skip' and 'include' before encoding directives. - self.directives - .into_iter() - .filter(|directive| !SPECIFIED_DIRECTIVES.contains(&directive.name.as_str())) - .for_each(|directive| Self::encode_directives(directive, &mut sdl)); - - // Exclude GraphQL named types like __Schema before encoding full type. - self.types - .into_iter() - .filter(|type_| match type_.full_type.name.as_deref() { - Some(name) => !GRAPHQL_NAMED_TYPES.contains(&name), - None => false, - }) - .for_each(|type_| Self::encode_full_type(type_, &mut sdl)); - - sdl.finish() - } - - fn encode_directives(directive: SchemaDirective, sdl: &mut SDL) { - let mut directive_ = Directive::new(directive.name); - directive_.description(directive.description); - for location in directive.locations { - // Location is of a __DirectiveLocation enum that doesn't implement - // Display (meaning we can't just do .to_string). This next line - // just forces it into a String with format! debug. - directive_.location(format!("{:?}", location)); - } - - sdl.directive(directive_) - } - - fn encode_full_type(type_: SchemaType, sdl: &mut SDL) { - let ty = type_.full_type; - - match ty.kind { - __TypeKind::OBJECT => { - let mut object_def = ObjectDef::new(ty.name.unwrap_or_else(String::new)); - object_def.description(ty.description); - if let Some(interfaces) = ty.interfaces { - for interface in interfaces { - object_def.interface(interface.type_ref.name.unwrap_or_else(String::new)); - } - } - if let Some(field) = ty.fields { - for f in field { - let field_def = Self::encode_field(f); - object_def.field(field_def); - } - sdl.object(object_def); - } - } - __TypeKind::INPUT_OBJECT => { - let mut input_def = InputObjectDef::new(ty.name.unwrap_or_else(String::new)); - input_def.description(ty.description); - if let Some(field) = ty.input_fields { - for f in field { - let input_field_def = Self::encode_input_field(f); - input_def.field(input_field_def); - } - sdl.input(input_def); - } - } - __TypeKind::INTERFACE => { - let mut interface_def = InterfaceDef::new(ty.name.unwrap_or_else(String::new)); - interface_def.description(ty.description); - if let Some(interfaces) = ty.interfaces { - for interface in interfaces { - interface_def - .interface(interface.type_ref.name.unwrap_or_else(String::new)); - } - } - if let Some(field) = ty.fields { - for f in field { - let field_def = Self::encode_field(f); - interface_def.field(field_def); - } - sdl.interface(interface_def); - } - } - __TypeKind::SCALAR => { - let mut scalar_def = ScalarDef::new(ty.name.unwrap_or_else(String::new)); - scalar_def.description(ty.description); - sdl.scalar(scalar_def); - } - __TypeKind::UNION => { - let mut union_def = UnionDef::new(ty.name.unwrap_or_else(String::new)); - union_def.description(ty.description); - if let Some(possible_types) = ty.possible_types { - for possible_type in possible_types { - union_def.member(possible_type.type_ref.name.unwrap_or_else(String::new)); - } - } - sdl.union(union_def); - } - __TypeKind::ENUM => { - let mut enum_def = EnumDef::new(ty.name.unwrap_or_else(String::new)); - if let Some(enums) = ty.enum_values { - for enum_ in enums { - let mut enum_value = EnumValue::new(enum_.name); - enum_value.description(enum_.description); - - if enum_.is_deprecated { - enum_value.deprecated(enum_.deprecation_reason); - } - - enum_def.value(enum_value); - } - } - sdl.enum_(enum_def); - } - _ => (), - } - } - - fn encode_field(field: FullTypeField) -> Field { - let ty = Self::encode_type(field.type_.type_ref); - let mut field_def = Field::new(field.name, ty); - - for value in field.args { - let field_value = Self::encode_arg(value); - field_def.arg(field_value); - } - - if field.is_deprecated { - field_def.deprecated(field.deprecation_reason); - } - field_def.description(field.description); - field_def - } - - fn encode_input_field(field: FullTypeInputField) -> InputField { - let ty = Self::encode_type(field.input_value.type_.type_ref); - let mut field_def = InputField::new(field.input_value.name, ty); - - field_def.default(field.input_value.default_value); - field_def.description(field.input_value.description); - field_def - } - - fn encode_arg(value: FullTypeFieldArg) -> InputValue { - let ty = Self::encode_type(value.input_value.type_.type_ref); - let mut value_def = InputValue::new(value.input_value.name, ty); - - value_def.default(value.input_value.default_value); - value_def.description(value.input_value.description); - value_def - } - - fn encode_type(ty: impl OfType) -> Type_ { - use graph_introspect_query::__TypeKind::*; - match ty.kind() { - SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT => Type_::NamedType { - name: ty.name().unwrap().to_string(), - }, - NON_NULL => { - let ty = Self::encode_type(ty.of_type().unwrap()); - Type_::NonNull { ty: Box::new(ty) } - } - LIST => { - let ty = Self::encode_type(ty.of_type().unwrap()); - Type_::List { ty: Box::new(ty) } - } - Other(ty) => panic!("Unknown type: {}", ty), - } - } -} - -impl TryFrom for Schema { - type Error = &'static str; - - fn try_from(src: IntrospectionResult) -> Result { - match src.schema { - Some(s) => Ok(Self { - types: s.types, - directives: s.directives, - mutation_type: s.mutation_type, - query_type: s.query_type, - subscription_type: s.subscription_type, - }), - None => Err("Schema not found in Introspection Result."), - } - } -} - -/// This trait is used to be able to iterate over ofType fields in -/// IntrospectionResponse. -pub trait OfType { - type TypeRef: OfType; - - fn kind(&self) -> &__TypeKind; - fn name(&self) -> Option<&str>; - fn of_type(self) -> Option; -} - -macro_rules! impl_of_type { - ($target:ty, $assoc:ty) => { - impl OfType for $target { - type TypeRef = $assoc; - - fn kind(&self) -> &__TypeKind { - &self.kind - } - - fn name(&self) -> Option<&str> { - self.name.as_deref() - } - - fn of_type(self) -> Option { - self.of_type - } - } - }; -} - -impl_of_type!( - graph_introspect_query::TypeRef, - graph_introspect_query::TypeRefOfType -); - -impl_of_type!( - graph_introspect_query::TypeRefOfType, - graph_introspect_query::TypeRefOfTypeOfType -); - -impl_of_type!( - graph_introspect_query::TypeRefOfTypeOfType, - graph_introspect_query::TypeRefOfTypeOfTypeOfType -); - -impl_of_type!( - graph_introspect_query::TypeRefOfTypeOfTypeOfType, - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfType -); - -impl_of_type!( - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfType, - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType -); - -impl_of_type!( - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType, - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType -); - -impl_of_type!( - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType, - graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType -); - -// NOTE(lrlna): This is a **hack**. This makes sure that the last possible -// generated ofType by graphql_client can return a None for of_type method. -impl OfType for graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType { - type TypeRef = graph_introspect_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType; - - fn kind(&self) -> &graph_introspect_query::__TypeKind { - &self.kind - } - - fn name(&self) -> Option<&str> { - self.name.as_deref() - } - - fn of_type(self) -> Option { - None - } -} +use std::fs::File; #[cfg(test)] mod tests { use super::*; - - use graphql_client::Response; use indoc::indoc; use pretty_assertions::assert_eq; - use std::convert::TryFrom; - use std::fs::File; - - use crate::operations::graph::introspect::types::QueryResponseData; + use rover_client::query::graph::introspect; + pub type IntrospectionResult = introspect::introspection_query::ResponseData; #[test] fn it_builds_simple_schema() { - let file = File::open("src/operations/graph/introspect/fixtures/simple.json").unwrap(); - let res: Response = serde_json::from_reader(file).unwrap(); + let file = File::open("tests/fixtures/simple.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); let schema = Schema::try_from(data).unwrap(); @@ -401,8 +47,8 @@ mod tests { #[test] fn it_builds_swapi_schema() { - let file = File::open("src/operations/graph/introspect/fixtures/swapi.json").unwrap(); - let res: Response = serde_json::from_reader(file).unwrap(); + let file = File::open("tests/fixtures/swapi.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); let schema = Schema::try_from(data).unwrap(); @@ -1364,8 +1010,8 @@ mod tests { #[test] fn it_builds_schema_with_interfaces() { - let file = File::open("src/operations/graph/introspect/fixtures/interfaces.json").unwrap(); - let res: Response = serde_json::from_reader(file).unwrap(); + let file = File::open("tests/fixtures/interfaces.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); let data = res.data.unwrap(); let schema = Schema::try_from(data).unwrap(); diff --git a/crates/sdl-encoder/src/schema_def.rs b/crates/sdl-encoder/src/schema_def.rs index 2d74b7c45..19905380e 100644 --- a/crates/sdl-encoder/src/schema_def.rs +++ b/crates/sdl-encoder/src/schema_def.rs @@ -81,7 +81,7 @@ impl Default for SchemaDef { impl Display for SchemaDef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(description) = &self.description { - // We determine whether to have description formatted as + // We are determing on whether to have description formatted as // a multiline comment based on whether or not it already includes a // \n. match description.contains('\n') { diff --git a/docs/source/errors.md b/docs/source/errors.md index a52161e75..f952ad647 100644 --- a/docs/source/errors.md +++ b/docs/source/errors.md @@ -232,17 +232,3 @@ To resolve this error, inspect the printed errors and correct the subgraph schem This error occurs when Rover could not connect to an HTTP endpoint. If you encountered this error while running introspection, you'll want to make sure that you typed the endpoint correctly, your Internet connection is stable, and that your server is responding to requests. You may wish to run the command again with `--log=debug`. - -### E029 - -This error occurs when you propose a subgraph schema that could not be built. - -There are many reasons why you may run into build errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). - -Some build errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). - - -### E030 - -This error occurs when an operation check fails. This means that you proposed a schema that would break operations in use by existing clients. You can configure this behavior in the Checks -> Configuration view in [Apollo Studio](https://studio.apollographql.com/), and you can read more about client checks [here](https://www.apollographql.com/docs/studio/schema-checks/). - diff --git a/installers/binstall/src/error.rs b/installers/binstall/src/error.rs index dc6f92353..26ca51377 100644 --- a/installers/binstall/src/error.rs +++ b/installers/binstall/src/error.rs @@ -2,26 +2,21 @@ use thiserror::Error; use std::io; -/// InstallerError is the type of Error that occurred while installing. +/// InstallerError is the type of Error that occured while installing. #[derive(Error, Debug)] pub enum InstallerError { - /// Something went wrong with system I/O #[error(transparent)] IoError(#[from] io::Error), - /// Couldn't find a valid install location on Unix #[error("Could not find the home directory of the current user")] NoHomeUnix, - /// Couldn't find a valid install location on Windows #[error("Could not find the user profile folder")] NoHomeWindows, - /// Something went wrong while adding the executable to zsh config #[error("Zsh setup failed")] ZshSetup, - /// A specified path was not valid UTF-8 #[error(transparent)] PathNotUtf8(#[from] camino::FromPathBufError), } diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index 4ea6e51b9..ecf494167 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -15,19 +15,12 @@ pub struct Installer { } impl Installer { - /// Installs the executable and returns the location it was installed. pub fn install(&self) -> Result, InstallerError> { let install_path = self.do_install()?; Ok(install_path) } - /// Gets the location the executable will be installed to - pub fn get_bin_dir_path(&self) -> Result { - let bin_dir = self.get_base_dir_path()?.join("bin"); - Ok(bin_dir) - } - fn do_install(&self) -> Result, InstallerError> { let bin_destination = self.get_bin_path()?; @@ -59,6 +52,11 @@ impl Installer { Ok(base_dir.join(&format!(".{}", &self.binary_name))) } + pub fn get_bin_dir_path(&self) -> Result { + let bin_dir = self.get_base_dir_path()?.join("bin"); + Ok(bin_dir) + } + fn create_bin_dir(&self) -> Result<(), InstallerError> { fs::create_dir_all(self.get_bin_dir_path()?)?; Ok(()) diff --git a/src/bin/rover.rs b/src/bin/rover.rs index 25ce697d0..cb663447c 100644 --- a/src/bin/rover.rs +++ b/src/bin/rover.rs @@ -1,16 +1,65 @@ +use command::RoverStdout; use robot_panic::setup_panic; -use rover::cli::Rover; +use rover::*; +use sputnik::Session; use structopt::StructOpt; +use std::{process, thread}; + fn main() { setup_panic!(Metadata { - name: rover::PKG_NAME.into(), - version: rover::PKG_VERSION.into(), - authors: rover::PKG_AUTHORS.into(), - homepage: rover::PKG_HOMEPAGE.into(), - repository: rover::PKG_REPOSITORY.into() + name: PKG_NAME.into(), + version: PKG_VERSION.into(), + authors: PKG_AUTHORS.into(), + homepage: PKG_HOMEPAGE.into(), + repository: PKG_REPOSITORY.into() }); + if let Err(error) = run() { + tracing::debug!(?error); + eprint!("{}", error); + process::exit(1) + } else { + process::exit(0) + } +} + +fn run() -> Result<()> { + let app = cli::Rover::from_args(); + timber::init(app.log_level); + tracing::trace!(command_structure = ?app); + + // attempt to create a new `Session` to capture anonymous usage data + let output: RoverStdout = match Session::new(&app) { + // if successful, report the usage data in the background + Ok(session) => { + // kicks off the reporting on a background thread + let report_thread = thread::spawn(move || { + // log + ignore errors because it is not in the critical path + let _ = session.report().map_err(|telemetry_error| { + tracing::debug!(?telemetry_error); + telemetry_error + }); + }); + + // kicks off the app on the main thread + // don't return an error with ? quite yet + // since we still want to report the usage data + let app_result = app.run(); + + // makes sure the reporting finishes in the background + // before continuing. + // ignore errors because it is not in the critical path + let _ = report_thread.join(); + + // return result of app execution + // now that we have reported our usage data + app_result + } + + // otherwise just run the app without reporting + Err(_) => app.run(), + }?; - let app = Rover::from_args(); - app.run(); + output.print(); + Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index 7bda87609..4271bbec7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,25 +1,21 @@ -use camino::Utf8PathBuf; use reqwest::blocking::Client; use serde::Serialize; use structopt::{clap::AppSettings, StructOpt}; -use crate::command::output::JsonOutput; -use crate::command::{self, RoverOutput}; +use crate::command::{self, RoverStdout}; use crate::utils::{ client::StudioClientConfig, env::{RoverEnv, RoverEnvKey}, - stringify::option_from_display, + git::GitContext, + stringify::from_display, version, }; -use crate::{anyhow, Result}; - +use crate::Result; use config::Config; use houston as config; -use rover_client::shared::GitContext; -use sputnik::Session; use timber::{Level, LEVELS}; -use std::{process, str::FromStr, thread}; +use camino::Utf8PathBuf; #[derive(Debug, Serialize, StructOpt)] #[structopt( @@ -55,20 +51,16 @@ You can open the full documentation for Rover by running: ")] pub struct Rover { #[structopt(subcommand)] - command: Command, + pub command: Command, /// Specify Rover's log level #[structopt(long = "log", short = "l", global = true, possible_values = &LEVELS, case_insensitive = true)] - #[serde(serialize_with = "option_from_display")] - log_level: Option, - - /// Specify Rover's output type - #[structopt(long = "output", default_value = "plain", possible_values = &["json", "plain"], case_insensitive = true, global = true)] - output_type: OutputType, + #[serde(serialize_with = "from_display")] + pub log_level: Option, #[structopt(skip)] #[serde(skip_serializing)] - pub(crate) env_store: RoverEnv, + pub env_store: RoverEnv, #[structopt(skip)] #[serde(skip_serializing)] @@ -76,96 +68,6 @@ pub struct Rover { } impl Rover { - pub fn run(&self) -> ! { - timber::init(self.log_level); - tracing::trace!(command_structure = ?self); - - // attempt to create a new `Session` to capture anonymous usage data - let rover_output = match Session::new(self) { - // if successful, report the usage data in the background - Ok(session) => { - // kicks off the reporting on a background thread - let report_thread = thread::spawn(move || { - // log + ignore errors because it is not in the critical path - let _ = session.report().map_err(|telemetry_error| { - tracing::debug!(?telemetry_error); - telemetry_error - }); - }); - - // kicks off the app on the main thread - // don't return an error with ? quite yet - // since we still want to report the usage data - let app_result = self.execute_command(); - - // makes sure the reporting finishes in the background - // before continuing. - // ignore errors because it is not in the critical path - let _ = report_thread.join(); - - // return result of app execution - // now that we have reported our usage data - app_result - } - - // otherwise just run the app without reporting - Err(_) => self.execute_command(), - }; - - match rover_output { - Ok(output) => { - match self.output_type { - OutputType::Plain => output.print(), - OutputType::Json => println!("{}", JsonOutput::from(output)), - } - process::exit(0); - } - Err(error) => { - match self.output_type { - OutputType::Json => println!("{}", JsonOutput::from(error)), - OutputType::Plain => { - tracing::debug!(?error); - error.print(); - } - } - process::exit(1); - } - } - } - - pub fn execute_command(&self) -> Result { - // before running any commands, we check if rover is up to date - // this only happens once a day automatically - // we skip this check for the `rover update` commands, since they - // do their own checks - - if let Command::Update(_) = &self.command { /* skip check */ - } else { - let config = self.get_rover_config(); - if let Ok(config) = config { - let _ = version::check_for_update(config, false, self.get_reqwest_client()); - } - } - - match &self.command { - Command::Config(command) => command.run(self.get_client_config()?), - Command::Supergraph(command) => command.run(self.get_client_config()?), - Command::Docs(command) => command.run(), - Command::Graph(command) => { - command.run(self.get_client_config()?, self.get_git_context()?) - } - Command::Subgraph(command) => { - command.run(self.get_client_config()?, self.get_git_context()?) - } - Command::Update(command) => { - command.run(self.get_rover_config()?, self.get_reqwest_client()) - } - Command::Install(command) => command.run(self.get_install_override_path()?), - Command::Info(command) => command.run(), - Command::Explain(command) => command.run(), - } - } - pub(crate) fn get_rover_config(&self) -> Result { let override_home: Option = self .env_store @@ -194,14 +96,7 @@ impl Rover { pub(crate) fn get_git_context(&self) -> Result { // constructing GitContext with a set of overrides from env vars - let override_git_context = GitContext { - branch: self.env_store.get(RoverEnvKey::VcsBranch).ok().flatten(), - commit: self.env_store.get(RoverEnvKey::VcsCommit).ok().flatten(), - author: self.env_store.get(RoverEnvKey::VcsAuthor).ok().flatten(), - remote_url: self.env_store.get(RoverEnvKey::VcsRemoteUrl).ok().flatten(), - }; - - let git_context = GitContext::new_with_override(override_git_context); + let git_context = GitContext::try_from_rover_env(&self.env_store)?; tracing::debug!(?git_context); Ok(git_context) } @@ -244,26 +139,37 @@ pub enum Command { Explain(command::Explain), } -#[derive(Debug, Serialize, Clone, PartialEq)] -pub enum OutputType { - Plain, - Json, -} - -impl FromStr for OutputType { - type Err = anyhow::Error; +impl Rover { + pub fn run(&self) -> Result { + // before running any commands, we check if rover is up to date + // this only happens once a day automatically + // we skip this check for the `rover update` commands, since they + // do their own checks - fn from_str(input: &str) -> std::result::Result { - match input { - "plain" => Ok(Self::Plain), - "json" => Ok(Self::Json), - _ => Err(anyhow!("Invalid output type.")), + if let Command::Update(_) = &self.command { /* skip check */ + } else { + let config = self.get_rover_config(); + if let Ok(config) = config { + let _ = version::check_for_update(config, false, self.get_reqwest_client()); + } } - } -} -impl Default for OutputType { - fn default() -> Self { - OutputType::Plain + match &self.command { + Command::Config(command) => command.run(self.get_client_config()?), + Command::Supergraph(command) => command.run(self.get_client_config()?), + Command::Docs(command) => command.run(), + Command::Graph(command) => { + command.run(self.get_client_config()?, self.get_git_context()?) + } + Command::Subgraph(command) => { + command.run(self.get_client_config()?, self.get_git_context()?) + } + Command::Update(command) => { + command.run(self.get_rover_config()?, self.get_reqwest_client()) + } + Command::Install(command) => command.run(self.get_install_override_path()?), + Command::Info(command) => command.run(), + Command::Explain(command) => command.run(), + } } } diff --git a/src/command/config/auth.rs b/src/command/config/auth.rs index 90951bcec..ae15cb16f 100644 --- a/src/command/config/auth.rs +++ b/src/command/config/auth.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use config::Profile; use houston as config; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::{anyhow, Result}; #[derive(Debug, Serialize, StructOpt)] @@ -26,13 +26,13 @@ pub struct Auth { } impl Auth { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { let api_key = api_key_prompt()?; Profile::set_api_key(&self.profile_name, &config, &api_key)?; Profile::get_credential(&self.profile_name, &config).map(|_| { eprintln!("Successfully saved API key."); })?; - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/config/clear.rs b/src/command/config/clear.rs index 5ebc9f214..a587885cb 100644 --- a/src/command/config/clear.rs +++ b/src/command/config/clear.rs @@ -1,7 +1,7 @@ use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::Result; use houston as config; @@ -13,9 +13,9 @@ use houston as config; pub struct Clear {} impl Clear { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { config.clear()?; eprintln!("Successfully cleared all configuration."); - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/config/delete.rs b/src/command/config/delete.rs index e282ae0bb..9c9664b4f 100644 --- a/src/command/config/delete.rs +++ b/src/command/config/delete.rs @@ -3,7 +3,7 @@ use structopt::StructOpt; use houston as config; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::Result; #[derive(Debug, Serialize, StructOpt)] @@ -20,9 +20,9 @@ pub struct Delete { } impl Delete { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { config::Profile::delete(&self.name, &config)?; eprintln!("Successfully deleted profile \"{}\"", &self.name); - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/config/list.rs b/src/command/config/list.rs index 9bd56b892..907bc0f9c 100644 --- a/src/command/config/list.rs +++ b/src/command/config/list.rs @@ -4,15 +4,15 @@ use structopt::StructOpt; use crate::Result; use houston as config; -use crate::command::RoverOutput; +use crate::command::RoverStdout; #[derive(Serialize, Debug, StructOpt)] /// List all configuration profiles pub struct List {} impl List { - pub fn run(&self, config: config::Config) -> Result { + pub fn run(&self, config: config::Config) -> Result { let profiles = config::Profile::list(&config)?; - Ok(RoverOutput::Profiles(profiles)) + Ok(RoverStdout::Profiles(profiles)) } } diff --git a/src/command/config/mod.rs b/src/command/config/mod.rs index 71a1b87aa..a9eed151a 100644 --- a/src/command/config/mod.rs +++ b/src/command/config/mod.rs @@ -7,7 +7,7 @@ mod whoami; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -36,7 +36,7 @@ pub enum Command { } impl Config { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { match &self.command { Command::Auth(command) => command.run(client_config.config), Command::List(command) => command.run(client_config.config), diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index c0d7f7d35..0a77288ec 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -1,12 +1,12 @@ use ansi_term::Colour::Green; -use rover_client::operations::config::who_am_i::{self, Actor, ConfigWhoAmIInput}; use serde::Serialize; use structopt::StructOpt; use houston::CredentialOrigin; +use rover_client::query::config::whoami; use crate::anyhow; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::utils::env::RoverEnvKey; use crate::Result; @@ -22,11 +22,11 @@ pub struct WhoAmI { } impl WhoAmI { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; eprintln!("Checking identity of your API key against the registry."); - let identity = who_am_i::run(ConfigWhoAmIInput {}, &client)?; + let identity = whoami::run(whoami::who_am_i_query::Variables {}, &client)?; let mut message = format!( "{}: {:?}\n", @@ -35,7 +35,7 @@ impl WhoAmI { ); match identity.key_actor_type { - Actor::GRAPH => { + whoami::Actor::GRAPH => { if let Some(graph_title) = identity.graph_title { message.push_str(&format!( "{}: {}\n", @@ -50,7 +50,7 @@ impl WhoAmI { )); Ok(()) } - Actor::USER => { + whoami::Actor::USER => { message.push_str(&format!( "{}: {}\n", Green.normal().paint("User ID"), @@ -63,7 +63,7 @@ impl WhoAmI { )), }?; - let origin = match client.get_credential_origin() { + let origin = match client.credential.origin { CredentialOrigin::ConfigFile(path) => format!("--profile {}", &path), CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key), }; @@ -80,6 +80,6 @@ impl WhoAmI { eprintln!("{}", message); - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/docs/list.rs b/src/command/docs/list.rs index 3f951f57a..6a1190f2b 100644 --- a/src/command/docs/list.rs +++ b/src/command/docs/list.rs @@ -1,4 +1,4 @@ -use crate::{command::RoverOutput, Result}; +use crate::{command::RoverStdout, Result}; use super::shortlinks; @@ -9,8 +9,8 @@ use structopt::StructOpt; pub struct List {} impl List { - pub fn run(&self) -> Result { - Ok(RoverOutput::DocsList( + pub fn run(&self) -> Result { + Ok(RoverStdout::DocsList( shortlinks::get_shortlinks_with_description(), )) } diff --git a/src/command/docs/mod.rs b/src/command/docs/mod.rs index ce3c0f6c8..0950c0eb4 100644 --- a/src/command/docs/mod.rs +++ b/src/command/docs/mod.rs @@ -5,7 +5,7 @@ pub mod shortlinks; use serde::Serialize; use structopt::StructOpt; -use crate::{command::RoverOutput, Result}; +use crate::{command::RoverStdout, Result}; #[derive(Debug, Serialize, StructOpt)] pub struct Docs { @@ -23,7 +23,7 @@ pub enum Command { } impl Docs { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { match &self.command { Command::List(command) => command.run(), Command::Open(command) => command.run(), diff --git a/src/command/docs/open.rs b/src/command/docs/open.rs index 8f39136d3..7e43ff6db 100644 --- a/src/command/docs/open.rs +++ b/src/command/docs/open.rs @@ -1,4 +1,4 @@ -use crate::{anyhow, command::RoverOutput, Result}; +use crate::{anyhow, command::RoverStdout, Result}; use super::shortlinks; @@ -15,7 +15,7 @@ pub struct Open { } impl Open { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { let url = shortlinks::get_url_from_slug(&self.slug); let yellow_browser_var = format!("{}", Yellow.normal().paint("$BROWSER")); let cyan_url = format!("{}", Cyan.normal().paint(&url)); @@ -40,6 +40,6 @@ impl Open { Ok(()) }?; - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/docs/shortlinks.rs b/src/command/docs/shortlinks.rs index 6471824c0..18833e758 100644 --- a/src/command/docs/shortlinks.rs +++ b/src/command/docs/shortlinks.rs @@ -1,9 +1,9 @@ pub const URL_BASE: &str = "https://go.apollo.dev/r"; -use std::collections::BTreeMap; +use std::collections::HashMap; -pub fn get_shortlinks_with_description() -> BTreeMap<&'static str, &'static str> { - let mut links = BTreeMap::new(); +pub fn get_shortlinks_with_description() -> HashMap<&'static str, &'static str> { + let mut links = HashMap::new(); links.insert("docs", "Rover's Documentation Homepage"); links.insert("api-keys", "Understanding Apollo's API Keys"); links.insert("contributing", "Contributing to Rover"); diff --git a/src/command/explain.rs b/src/command/explain.rs index 4de0510d7..4c2d3a5ad 100644 --- a/src/command/explain.rs +++ b/src/command/explain.rs @@ -1,4 +1,4 @@ -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::error::metadata::code::Code; use crate::Result; use serde::Serialize; @@ -12,8 +12,8 @@ pub struct Explain { } impl Explain { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { let explanation = &self.code.explain(); - Ok(RoverOutput::ErrorExplanation(explanation.clone())) + Ok(RoverStdout::Markdown(explanation.clone())) } } diff --git a/src/command/graph/check.rs b/src/command/graph/check.rs index d18851021..f4720436e 100644 --- a/src/command/graph/check.rs +++ b/src/command/graph/check.rs @@ -1,23 +1,24 @@ use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::graph::check::{self, GraphCheckInput}; -use rover_client::shared::{CheckConfig, GitContext, GraphRef, ValidationPeriod}; +use rover_client::query::graph::check; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::git::GitContext; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ - parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, - SchemaSource, + parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, + parse_schema_source, parse_validation_period, GraphRef, SchemaSource, ValidationPeriod, }; +use crate::utils::table::{self, cell, row}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Check { /// @ of graph in Apollo Studio to validate. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -44,7 +45,7 @@ pub struct Check { query_percentage_threshold: Option, /// Size of the time window with which to validate schema against (i.e "24h" or "1w 2d 5h") - #[structopt(long)] + #[structopt(long, parse(try_from_str = parse_validation_period))] validation_period: Option, } @@ -53,29 +54,81 @@ impl Check { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; - let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; - - eprintln!( - "Checking the proposed schema against metrics from {}", - &self.graph - ); - + let sdl = load_schema_from_flag(&self.schema, std::io::stdin())?; let res = check::run( - GraphCheckInput { - graph_ref: self.graph.clone(), - proposed_schema, - git_context, - config: CheckConfig { + check::check_schema_query::Variables { + graph_id: self.graph.name.clone(), + variant: Some(self.graph.variant.clone()), + schema: Some(sdl), + git_context: git_context.into(), + config: check::check_schema_query::HistoricQueryParameters { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, - validation_period: self.validation_period.clone(), + from: self.validation_period.clone().unwrap_or_default().from, + to: self.validation_period.clone().unwrap_or_default().to, + // we don't support configuring these, but we can't leave them out + excluded_clients: None, + ignored_operations: None, + included_variants: None, }, }, &client, )?; - Ok(RoverOutput::CheckResponse(res)) + eprintln!( + "Validated the proposed subgraph against metrics from {}", + &self.graph + ); + + let num_changes = res.changes.len(); + + let msg = match num_changes { + 0 => "There is no difference between the proposed graph and the graph that already exists in the graph registry. Try making a change to your proposed graph before running this command.".to_string(), + _ => format!("Compared {} schema changes against {} operations", res.changes.len(), res.number_of_checked_operations), + }; + + eprintln!("{}", &msg); + + let num_failures = print_changes(&res.changes); + + if let Some(url) = res.target_url { + eprintln!("View full details at {}", &url); + } + + match num_failures { + 0 => Ok(RoverStdout::None), + 1 => Err(anyhow::anyhow!("Encountered 1 failure.").into()), + _ => Err(anyhow::anyhow!("Encountered {} failures.", num_failures).into()), + } + } +} + +fn print_changes( + checks: &[check::check_schema_query::CheckSchemaQueryServiceCheckSchemaDiffToPreviousChanges], +) -> u64 { + let mut num_failures = 0; + + if !checks.is_empty() { + let mut table = table::get_table(); + + // bc => sets top row to be bold and center + table.add_row(row![bc => "Change", "Code", "Description"]); + for check in checks { + let change = match check.severity { + check::check_schema_query::ChangeSeverity::NOTICE => "PASS", + check::check_schema_query::ChangeSeverity::FAILURE => { + num_failures += 1; + "FAIL" + } + _ => unreachable!("Unknown change severity"), + }; + table.add_row(row![change, check.code, check.description]); + } + + eprintln!("{}", table); } + + num_failures } diff --git a/src/command/graph/fetch.rs b/src/command/graph/fetch.rs index 478811b2d..c1c147d31 100644 --- a/src/command/graph/fetch.rs +++ b/src/command/graph/fetch.rs @@ -2,18 +2,18 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::graph::fetch::{self, GraphFetchInput}; -use rover_client::shared::GraphRef; +use rover_client::query::graph::fetch; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Fetch { /// @ of graph in Apollo Studio to fetch from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -24,7 +24,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -33,13 +33,15 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let fetch_response = fetch::run( - GraphFetchInput { - graph_ref: self.graph.clone(), + let sdl = fetch::run( + fetch::fetch_schema_query::Variables { + graph_id: self.graph.name.clone(), + hash: None, + variant: Some(self.graph.variant.clone()), }, &client, )?; - Ok(RoverOutput::FetchResponse(fetch_response)) + Ok(RoverStdout::Sdl(sdl)) } } diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs index da66e047b..a12ac60c9 100644 --- a/src/command/graph/introspect.rs +++ b/src/command/graph/introspect.rs @@ -5,12 +5,9 @@ use std::collections::HashMap; use structopt::StructOpt; use url::Url; -use rover_client::{ - blocking::GraphQLClient, - operations::graph::introspect::{self, GraphIntrospectInput}, -}; +use rover_client::{blocking::GraphQLClient, query::graph::introspect}; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::parsers::parse_header; #[derive(Debug, Serialize, StructOpt)] @@ -31,7 +28,7 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client) -> Result { + pub fn run(&self, client: Client) -> Result { let client = GraphQLClient::new(&self.endpoint.to_string(), client)?; // add the flag headers to a hashmap to pass along to rover-client @@ -42,10 +39,8 @@ impl Introspect { } } - let introspection_response = introspect::run(GraphIntrospectInput { headers }, &client)?; + let introspection_response = introspect::run(&client, &headers)?; - Ok(RoverOutput::Introspection( - introspection_response.schema_sdl, - )) + Ok(RoverStdout::Introspection(introspection_response.result)) } } diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 0603fb4e5..13744518e 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -6,12 +6,10 @@ mod publish; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; -use crate::utils::client::StudioClientConfig; +use crate::command::RoverStdout; +use crate::utils::{client::StudioClientConfig, git::GitContext}; use crate::Result; -use rover_client::shared::GitContext; - #[derive(Debug, Serialize, StructOpt)] pub struct Graph { #[structopt(subcommand)] @@ -39,7 +37,7 @@ impl Graph { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { match &self.command { Command::Check(command) => command.run(client_config, git_context), Command::Fetch(command) => command.run(client_config), diff --git a/src/command/graph/publish.rs b/src/command/graph/publish.rs index c6208e5df..053d3b953 100644 --- a/src/command/graph/publish.rs +++ b/src/command/graph/publish.rs @@ -2,20 +2,20 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::graph::publish::{self, GraphPublishInput}; -use rover_client::shared::{GitContext, GraphRef}; +use rover_client::query::graph::publish; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::git::GitContext; use crate::utils::loaders::load_schema_from_flag; -use crate::utils::parsers::{parse_schema_source, SchemaSource}; +use crate::utils::parsers::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Publish { /// @ of graph in Apollo Studio to publish to. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -36,7 +36,7 @@ impl Publish { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -45,22 +45,53 @@ impl Publish { Yellow.normal().paint(&self.profile_name) ); - let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; + let schema_document = load_schema_from_flag(&self.schema, std::io::stdin())?; - tracing::debug!("Publishing \n{}", &proposed_schema); + tracing::debug!("Publishing \n{}", &schema_document); let publish_response = publish::run( - GraphPublishInput { - graph_ref: self.graph.clone(), - proposed_schema, - git_context, + publish::publish_schema_mutation::Variables { + graph_id: self.graph.name.clone(), + variant: self.graph.variant.clone(), + schema_document: Some(schema_document), + git_context: git_context.into(), }, &client, )?; - Ok(RoverOutput::GraphPublishResponse { - graph_ref: self.graph.clone(), - publish_response, - }) + let hash = handle_response(&self.graph, publish_response); + Ok(RoverStdout::SchemaHash(hash)) + } +} + +/// handle all output logging from operation +fn handle_response(graph: &GraphRef, response: publish::PublishResponse) -> String { + eprintln!( + "{}#{} Published successfully {}", + graph, response.schema_hash, response.change_summary + ); + + response.schema_hash +} + +#[cfg(test)] +mod tests { + use super::{handle_response, publish, GraphRef}; + + #[test] + fn handle_response_doesnt_err() { + let expected_hash = "123456".to_string(); + let graph = GraphRef { + name: "harambe".to_string(), + variant: "inside-job".to_string(), + }; + let actual_hash = handle_response( + &graph, + publish::PublishResponse { + schema_hash: expected_hash.clone(), + change_summary: "".to_string(), + }, + ); + assert_eq!(actual_hash, expected_hash); } } diff --git a/src/command/info.rs b/src/command/info.rs index 0f719690c..55756b6e0 100644 --- a/src/command/info.rs +++ b/src/command/info.rs @@ -1,4 +1,4 @@ -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::Result; use crate::PKG_VERSION; use serde::Serialize; @@ -9,7 +9,7 @@ use structopt::StructOpt; pub struct Info {} impl Info { - pub fn run(&self) -> Result { + pub fn run(&self) -> Result { let os = os_info::get(); // something like "/usr/bin/zsh" or "Unknown" @@ -28,6 +28,6 @@ impl Info { PKG_VERSION, location, os, shell ); - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/install/mod.rs b/src/command/install/mod.rs index 293a4b941..ca2f34be3 100644 --- a/src/command/install/mod.rs +++ b/src/command/install/mod.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use binstall::Installer; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::PKG_NAME; use crate::{anyhow, Context, Result}; use crate::{command::docs::shortlinks, utils::env::RoverEnvKey}; @@ -20,7 +20,7 @@ pub struct Install { } impl Install { - pub fn run(&self, override_install_path: Option) -> Result { + pub fn run(&self, override_install_path: Option) -> Result { let binary_name = PKG_NAME.to_string(); if let Ok(executable_location) = env::current_exe() { let executable_location = Utf8PathBuf::try_from(executable_location)?; @@ -68,7 +68,7 @@ impl Install { } else { eprintln!("{} was not installed. To override the existing installation, you can pass the `--force` flag to the installer.", &binary_name); } - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } else { Err(anyhow!("Failed to get the current executable's path.").into()) } diff --git a/src/command/mod.rs b/src/command/mod.rs index 7cf6e8bd3..dfc4d344b 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -4,19 +4,18 @@ mod explain; mod graph; mod info; mod install; +mod output; mod subgraph; mod supergraph; mod update; -pub(crate) mod output; - pub use config::Config; pub use docs::Docs; pub use explain::Explain; pub use graph::Graph; pub use info::Info; pub use install::Install; -pub use output::RoverOutput; +pub use output::RoverStdout; pub use subgraph::Subgraph; pub use supergraph::Supergraph; pub use update::Update; diff --git a/src/command/output.rs b/src/command/output.rs index 0f275a17a..bd154b5d3 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -1,65 +1,42 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Display}; +use std::fmt::Debug; +use std::{collections::HashMap, fmt::Display}; -use crate::error::RoverError; use crate::utils::table::{self, cell, row}; -use ansi_term::{ - Colour::{Cyan, Red, Yellow}, - Style, -}; +use ansi_term::{Colour::Yellow, Style}; use atty::Stream; use crossterm::style::Attribute::Underlined; -use rover_client::operations::graph::publish::GraphPublishResponse; -use rover_client::operations::subgraph::delete::SubgraphDeleteResponse; -use rover_client::operations::subgraph::list::SubgraphListResponse; -use rover_client::operations::subgraph::publish::SubgraphPublishResponse; -use rover_client::shared::{CheckResponse, FetchResponse, GraphRef, SdlType}; -use rover_client::RoverClientError; -use serde::Serialize; -use serde_json::{json, Value}; +use rover_client::query::subgraph::list::ListDetails; use termimad::MadSkin; -/// RoverOutput defines all of the different types of data that are printed -/// to `stdout`. Every one of Rover's commands should return `anyhow::Result` +/// RoverStdout defines all of the different types of data that are printed +/// to `stdout`. Every one of Rover's commands should return `anyhow::Result` /// If the command needs to output some type of data, it should be structured -/// in this enum, and its print logic should be handled in `RoverOutput::print` +/// in this enum, and its print logic should be handled in `RoverStdout::print` /// /// Not all commands will output machine readable information, and those should -/// return `Ok(RoverOutput::EmptySuccess)`. If a new command is added and it needs to +/// return `Ok(RoverStdout::None)`. If a new command is added and it needs to /// return something that is not described well in this enum, it should be added. #[derive(Clone, PartialEq, Debug)] -pub enum RoverOutput { - DocsList(BTreeMap<&'static str, &'static str>), - FetchResponse(FetchResponse), +pub enum RoverStdout { + DocsList(HashMap<&'static str, &'static str>), + SupergraphSdl(String), + Sdl(String), CoreSchema(String), - SubgraphList(SubgraphListResponse), - CheckResponse(CheckResponse), - GraphPublishResponse { - graph_ref: GraphRef, - publish_response: GraphPublishResponse, - }, - SubgraphPublishResponse { - graph_ref: GraphRef, - subgraph: String, - publish_response: SubgraphPublishResponse, - }, - SubgraphDeleteResponse { - graph_ref: GraphRef, - subgraph: String, - dry_run: bool, - delete_response: SubgraphDeleteResponse, - }, + SchemaHash(String), + SubgraphList(ListDetails), + VariantList(Vec), Profiles(Vec), Introspection(String), - ErrorExplanation(String), - EmptySuccess, + Markdown(String), + PlainText(String), + None, } -impl RoverOutput { +impl RoverStdout { pub fn print(&self) { match self { - RoverOutput::DocsList(shortlinks) => { + RoverStdout::DocsList(shortlinks) => { eprintln!( "You can open any of these documentation pages by running {}.\n", Yellow.normal().paint("`rover docs open `") @@ -73,108 +50,23 @@ impl RoverOutput { } println!("{}", table); } - RoverOutput::FetchResponse(fetch_response) => { - match fetch_response.sdl.r#type { - SdlType::Graph | SdlType::Subgraph => print_descriptor("SDL"), - SdlType::Supergraph => print_descriptor("Supergraph SDL"), - } - print_content(&fetch_response.sdl.contents); - } - RoverOutput::GraphPublishResponse { - graph_ref, - publish_response, - } => { - eprintln!( - "{}#{} Published successfully {}", - graph_ref, publish_response.api_schema_hash, publish_response.change_summary - ); - print_one_line_descriptor("Schema Hash"); - print_content(&publish_response.api_schema_hash); + RoverStdout::SupergraphSdl(sdl) => { + print_descriptor("Supergraph SDL"); + print_content(&sdl); } - RoverOutput::SubgraphPublishResponse { - graph_ref, - subgraph, - publish_response, - } => { - if publish_response.subgraph_was_created { - eprintln!( - "A new subgraph called '{}' for the '{}' graph was created", - subgraph, graph_ref - ); - } else { - eprintln!( - "The '{}' subgraph for the '{}' graph was updated", - subgraph, graph_ref - ); - } - - if publish_response.supergraph_was_updated { - eprintln!("The gateway for the '{}' graph was updated with a new schema, composed from the updated '{}' subgraph", graph_ref, subgraph); - } else { - eprintln!( - "The gateway for the '{}' graph was NOT updated with a new schema", - graph_ref - ); - } - - if !publish_response.build_errors.is_empty() { - let warn_prefix = Red.normal().paint("WARN:"); - eprintln!("{} The following build errors occurred:", warn_prefix,); - eprintln!("{}", &publish_response.build_errors); - } - } - RoverOutput::SubgraphDeleteResponse { - graph_ref, - subgraph, - dry_run, - delete_response, - } => { - let warn_prefix = Red.normal().paint("WARN:"); - if *dry_run { - if !delete_response.build_errors.is_empty() { - eprintln!( - "{} Deleting the {} subgraph from {} would result in the following build errors:", - warn_prefix, - Cyan.normal().paint(subgraph), - Cyan.normal().paint(graph_ref.to_string()), - ); - - eprintln!("{}", &delete_response.build_errors); - eprintln!("{} This is only a prediction. If the graph changes before confirming, these errors could change.", warn_prefix); - } else { - eprintln!("{} At the time of checking, there would be no build errors resulting from the deletion of this subgraph.", warn_prefix); - eprintln!("{} This is only a prediction. If the graph changes before confirming, there could be build errors.", warn_prefix) - } - } else { - if delete_response.supergraph_was_updated { - eprintln!( - "The {} subgraph was removed from {}. Remaining subgraphs were composed.", - Cyan.normal().paint(subgraph), - Cyan.normal().paint(graph_ref.to_string()), - ) - } else { - eprintln!( - "{} The gateway for {} was not updated. See errors below.", - warn_prefix, - Cyan.normal().paint(graph_ref.to_string()) - ) - } - - if !delete_response.build_errors.is_empty() { - eprintln!( - "{} There were build errors as a result of deleting the subgraph:", - warn_prefix, - ); - - eprintln!("{}", &delete_response.build_errors); - } - } + RoverStdout::Sdl(sdl) => { + print_descriptor("SDL"); + print_content(&sdl); } - RoverOutput::CoreSchema(csdl) => { + RoverStdout::CoreSchema(csdl) => { print_descriptor("CoreSchema"); print_content(&csdl); } - RoverOutput::SubgraphList(details) => { + RoverStdout::SchemaHash(hash) => { + print_one_line_descriptor("Schema Hash"); + print_content(&hash); + } + RoverStdout::SubgraphList(details) => { let mut table = table::get_table(); // bc => sets top row to be bold and center @@ -191,7 +83,7 @@ impl RoverOutput { } else { url }; - let formatted_updated_at: String = if let Some(dt) = subgraph.updated_at.local { + let formatted_updated_at: String = if let Some(dt) = subgraph.updated_at { dt.format("%Y-%m-%d %H:%M:%S %Z").to_string() } else { "N/A".to_string() @@ -203,14 +95,16 @@ impl RoverOutput { println!("{}", table); println!( "View full details at {}/graph/{}/service-list", - details.root_url, details.graph_ref.name + details.root_url, details.graph_name ); } - RoverOutput::CheckResponse(check_response) => { - print_descriptor("Check Result"); - print_content(check_response.get_table()); + RoverStdout::VariantList(variants) => { + print_descriptor("Variants"); + for variant in variants { + println!("{}", variant); + } } - RoverOutput::Profiles(profiles) => { + RoverStdout::Profiles(profiles) => { if profiles.is_empty() { eprintln!("No profiles found."); } else { @@ -221,101 +115,23 @@ impl RoverOutput { println!("{}", profile); } } - RoverOutput::Introspection(introspection_response) => { + RoverStdout::Introspection(introspection_response) => { print_descriptor("Introspection Response"); print_content(&introspection_response); } - RoverOutput::ErrorExplanation(explanation) => { + RoverStdout::Markdown(markdown_string) => { // underline bolded md let mut skin = MadSkin::default(); skin.bold.add_attr(Underlined); - println!("{}", skin.inline(explanation)); + println!("{}", skin.inline(markdown_string)); } - RoverOutput::EmptySuccess => (), - } - } - - pub(crate) fn get_internal_data_json(&self) -> Value { - match self { - RoverOutput::DocsList(shortlinks) => { - let mut shortlink_vec = Vec::with_capacity(shortlinks.len()); - for (shortlink_slug, shortlink_description) in shortlinks { - shortlink_vec.push( - json!({"slug": shortlink_slug, "description": shortlink_description }), - ); - } - json!({ "shortlinks": shortlink_vec }) - } - RoverOutput::FetchResponse(fetch_response) => json!(fetch_response), - RoverOutput::CoreSchema(csdl) => json!({ "core_schema": csdl }), - RoverOutput::GraphPublishResponse { - graph_ref: _, - publish_response, - } => json!(publish_response), - RoverOutput::SubgraphPublishResponse { - graph_ref: _, - subgraph: _, - publish_response, - } => json!(publish_response), - RoverOutput::SubgraphDeleteResponse { - graph_ref: _, - subgraph: _, - dry_run: _, - delete_response, - } => { - json!(delete_response) + RoverStdout::PlainText(text) => { + println!("{}", text); } - RoverOutput::SubgraphList(list_response) => json!(list_response), - RoverOutput::CheckResponse(check_response) => check_response.get_json(), - RoverOutput::Profiles(profiles) => json!({ "profiles": profiles }), - RoverOutput::Introspection(introspection_response) => { - json!({ "introspection_response": introspection_response }) - } - RoverOutput::ErrorExplanation(explanation_markdown) => { - json!({ "explanation_markdown": explanation_markdown }) - } - RoverOutput::EmptySuccess => json!(null), + RoverStdout::None => (), } } - - pub(crate) fn get_internal_error_json(&self) -> Value { - let rover_error = match self { - RoverOutput::SubgraphPublishResponse { - graph_ref, - subgraph, - publish_response, - } => { - if !publish_response.build_errors.is_empty() { - Some(RoverError::from(RoverClientError::SubgraphBuildErrors { - subgraph: subgraph.clone(), - graph_ref: graph_ref.clone(), - source: publish_response.build_errors.clone(), - })) - } else { - None - } - } - RoverOutput::SubgraphDeleteResponse { - graph_ref, - subgraph, - dry_run: _, - delete_response, - } => { - if !delete_response.build_errors.is_empty() { - Some(RoverError::from(RoverClientError::SubgraphBuildErrors { - subgraph: subgraph.clone(), - graph_ref: graph_ref.clone(), - source: delete_response.build_errors.clone(), - })) - } else { - None - } - } - _ => None, - }; - json!(rover_error) - } } fn print_descriptor(descriptor: impl Display) { @@ -339,756 +155,3 @@ fn print_content(content: impl Display) { print!("{}", content) } } - -#[derive(Debug, Clone, Serialize)] -pub(crate) struct JsonOutput { - json_version: JsonVersion, - data: JsonData, - error: Value, -} - -impl JsonOutput { - pub(crate) fn success(data: Value, error: Value) -> JsonOutput { - JsonOutput { - json_version: JsonVersion::OneBeta, - data: JsonData::success(data), - error, - } - } - - pub(crate) fn failure(data: Value, error: Value) -> JsonOutput { - JsonOutput { - json_version: JsonVersion::OneBeta, - data: JsonData::failure(data), - error, - } - } -} - -impl fmt::Display for JsonOutput { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", json!(self)) - } -} - -impl From for JsonOutput { - fn from(error: RoverError) -> Self { - let data = error.get_internal_data_json(); - let error = error.get_internal_error_json(); - JsonOutput::failure(data, error) - } -} - -impl From for JsonOutput { - fn from(output: RoverOutput) -> Self { - let data = output.get_internal_data_json(); - let error = output.get_internal_error_json(); - JsonOutput::success(data, error) - } -} - -#[derive(Debug, Clone, Serialize)] -pub(crate) struct JsonData { - #[serde(flatten)] - inner: Value, - success: bool, -} - -impl JsonData { - pub(crate) fn success(inner: Value) -> JsonData { - JsonData { - inner, - success: true, - } - } - - pub(crate) fn failure(inner: Value) -> JsonData { - JsonData { - inner, - success: false, - } - } -} - -#[derive(Debug, Clone, Serialize)] -pub(crate) enum JsonVersion { - #[serde(rename = "1.beta")] - OneBeta, -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use assert_json_diff::assert_json_eq; - use chrono::{DateTime, Local, Utc}; - use rover_client::{ - operations::{ - graph::publish::{ChangeSummary, FieldChanges, TypeChanges}, - subgraph::{ - delete::SubgraphDeleteResponse, - list::{SubgraphInfo, SubgraphUpdatedAt}, - }, - }, - shared::{BuildError, BuildErrors, ChangeSeverity, SchemaChange, Sdl}, - }; - - use crate::anyhow; - - use super::*; - - #[test] - fn docs_list_json() { - let mut mock_shortlinks = BTreeMap::new(); - mock_shortlinks.insert("slug_one", "description_one"); - mock_shortlinks.insert("slug_two", "description_two"); - let actual_json: JsonOutput = RoverOutput::DocsList(mock_shortlinks).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "shortlinks": [ - { - "slug": "slug_one", - "description": "description_one" - }, - { - "slug": "slug_two", - "description": "description_two" - } - ], - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn fetch_response_json() { - let mock_fetch_response = FetchResponse { - sdl: Sdl { - contents: "sdl contents".to_string(), - r#type: SdlType::Subgraph, - }, - }; - let actual_json: JsonOutput = RoverOutput::FetchResponse(mock_fetch_response).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "sdl": { - "contents": "sdl contents", - }, - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn core_schema_json() { - let mock_core_schema = "core schema contents".to_string(); - let actual_json: JsonOutput = RoverOutput::CoreSchema(mock_core_schema).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "core_schema": "core schema contents", - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn subgraph_list_json() { - let now_utc: DateTime = Utc::now(); - let now_local: DateTime = now_utc.into(); - let mock_subgraph_list_response = SubgraphListResponse { - subgraphs: vec![ - SubgraphInfo { - name: "subgraph one".to_string(), - url: Some("http://localhost:4001".to_string()), - updated_at: SubgraphUpdatedAt { - local: Some(now_local), - utc: Some(now_utc), - }, - }, - SubgraphInfo { - name: "subgraph two".to_string(), - url: None, - updated_at: SubgraphUpdatedAt { - local: None, - utc: None, - }, - }, - ], - root_url: "https://studio.apollographql.com/".to_string(), - graph_ref: GraphRef { - name: "graph".to_string(), - variant: "current".to_string(), - }, - }; - let actual_json: JsonOutput = RoverOutput::SubgraphList(mock_subgraph_list_response).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "subgraphs": [ - { - "name": "subgraph one", - "url": "http://localhost:4001", - "updated_at": { - "local": now_local, - "utc": now_utc - } - }, - { - "name": "subgraph two", - "url": null, - "updated_at": { - "local": null, - "utc": null - } - } - ], - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn subgraph_delete_success_json() { - let mock_subgraph_delete = SubgraphDeleteResponse { - supergraph_was_updated: true, - build_errors: BuildErrors::new(), - }; - let actual_json: JsonOutput = RoverOutput::SubgraphDeleteResponse { - delete_response: mock_subgraph_delete, - subgraph: "subgraph".to_string(), - dry_run: false, - graph_ref: GraphRef { - name: "name".to_string(), - variant: "current".to_string(), - }, - } - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "supergraph_was_updated": true, - "success": true, - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn subgraph_delete_build_errors_json() { - let mock_subgraph_delete = SubgraphDeleteResponse { - supergraph_was_updated: false, - build_errors: vec![ - BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), - Some("AN_ERROR_CODE".to_string()), - ), - BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), - None, - ), - ] - .into(), - }; - let actual_json: JsonOutput = RoverOutput::SubgraphDeleteResponse { - delete_response: mock_subgraph_delete, - subgraph: "subgraph".to_string(), - dry_run: true, - graph_ref: GraphRef { - name: "name".to_string(), - variant: "current".to_string(), - }, - } - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "supergraph_was_updated": false, - "success": true, - }, - "error": { - "message": "Encountered 2 build errors while trying to build subgraph \"subgraph\" into supergraph \"name@current\".", - "code": "E029", - "details": { - "build_errors": [ - { - "message": "[Accounts] -> Things went really wrong", - "code": "AN_ERROR_CODE", - "type": "composition" - }, - { - "message": "[Films] -> Something else also went wrong", - "code": null, - "type": "composition" - } - ], - } - } - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn supergraph_fetch_no_successful_publishes_json() { - let graph_ref = GraphRef { - name: "name".to_string(), - variant: "current".to_string(), - }; - let source = BuildErrors::from(vec![ - BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), - Some("AN_ERROR_CODE".to_string()), - ), - BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), - None, - ), - ]); - let actual_json: JsonOutput = - RoverError::new(RoverClientError::NoSupergraphBuilds { graph_ref, source }).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "success": false - }, - "error": { - "message": "No supergraph SDL exists for \"name@current\" because its subgraphs failed to build.", - "details": { - "build_errors": [ - { - "message": "[Accounts] -> Things went really wrong", - "code": "AN_ERROR_CODE", - "type": "composition", - }, - { - "message": "[Films] -> Something else also went wrong", - "code": null, - "type": "composition" - } - ] - }, - "code": "E027" - } - }); - assert_json_eq!(actual_json, expected_json); - } - - #[test] - fn check_success_response_json() { - let graph_ref = GraphRef { - name: "name".to_string(), - variant: "current".to_string(), - }; - let mock_check_response = CheckResponse::try_new( - Some("https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current".to_string()), - 10, - vec![ - SchemaChange { - code: "SOMETHING_HAPPENED".to_string(), - description: "beeg yoshi".to_string(), - severity: ChangeSeverity::PASS, - }, - SchemaChange { - code: "WOW".to_string(), - description: "that was so cool".to_string(), - severity: ChangeSeverity::PASS, - } - ], - ChangeSeverity::PASS, - graph_ref, - ); - if let Ok(mock_check_response) = mock_check_response { - let actual_json: JsonOutput = RoverOutput::CheckResponse(mock_check_response).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "target_url": "https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current", - "operation_check_count": 10, - "changes": [ - { - "code": "SOMETHING_HAPPENED", - "description": "beeg yoshi", - "severity": "PASS" - }, - { - "code": "WOW", - "description": "that was so cool", - "severity": "PASS" - }, - ], - "failure_count": 0, - "success": true, - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } else { - panic!("The shape of this response should return a CheckResponse") - } - } - - #[test] - fn check_failure_response_json() { - let graph_ref = GraphRef { - name: "name".to_string(), - variant: "current".to_string(), - }; - let check_response = CheckResponse::try_new( - Some("https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current".to_string()), - 10, - vec![ - SchemaChange { - code: "SOMETHING_HAPPENED".to_string(), - description: "beeg yoshi".to_string(), - severity: ChangeSeverity::FAIL, - }, - SchemaChange { - code: "WOW".to_string(), - description: "that was so cool".to_string(), - severity: ChangeSeverity::FAIL, - } - ], - ChangeSeverity::FAIL, graph_ref); - - if let Err(operation_check_failure) = check_response { - let actual_json: JsonOutput = RoverError::new(operation_check_failure).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "target_url": "https://studio.apollographql.com/graph/my-graph/composition/big-hash?variant=current", - "operation_check_count": 10, - "changes": [ - { - "code": "SOMETHING_HAPPENED", - "description": "beeg yoshi", - "severity": "FAIL" - }, - { - "code": "WOW", - "description": "that was so cool", - "severity": "FAIL" - }, - ], - "failure_count": 2, - "success": false, - }, - "error": { - "message": "This operation check has encountered 2 schema changes that would break operations from existing client traffic.", - "code": "E030", - } - }); - assert_json_eq!(expected_json, actual_json); - } else { - panic!("The shape of this response should return a RoverClientError") - } - } - - #[test] - fn graph_publish_response_json() { - let mock_publish_response = GraphPublishResponse { - api_schema_hash: "123456".to_string(), - change_summary: ChangeSummary { - field_changes: FieldChanges { - additions: 2, - removals: 1, - edits: 0, - }, - type_changes: TypeChanges { - additions: 4, - removals: 0, - edits: 7, - }, - }, - }; - let actual_json: JsonOutput = RoverOutput::GraphPublishResponse { - graph_ref: GraphRef { - name: "graph".to_string(), - variant: "variant".to_string(), - }, - publish_response: mock_publish_response, - } - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "api_schema_hash": "123456", - "field_changes": { - "additions": 2, - "removals": 1, - "edits": 0 - }, - "type_changes": { - "additions": 4, - "removals": 0, - "edits": 7 - }, - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn subgraph_publish_success_response_json() { - let mock_publish_response = SubgraphPublishResponse { - api_schema_hash: Some("123456".to_string()), - build_errors: BuildErrors::new(), - supergraph_was_updated: true, - subgraph_was_created: true, - }; - let actual_json: JsonOutput = RoverOutput::SubgraphPublishResponse { - graph_ref: GraphRef { - name: "graph".to_string(), - variant: "variant".to_string(), - }, - subgraph: "subgraph".to_string(), - publish_response: mock_publish_response, - } - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "api_schema_hash": "123456", - "supergraph_was_updated": true, - "subgraph_was_created": true, - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn subgraph_publish_failure_response_json() { - let mock_publish_response = SubgraphPublishResponse { - api_schema_hash: None, - - build_errors: vec![ - BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), - Some("AN_ERROR_CODE".to_string()), - ), - BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), - None, - ), - ] - .into(), - supergraph_was_updated: false, - subgraph_was_created: false, - }; - let actual_json: JsonOutput = RoverOutput::SubgraphPublishResponse { - graph_ref: GraphRef { - name: "name".to_string(), - variant: "current".to_string(), - }, - subgraph: "subgraph".to_string(), - publish_response: mock_publish_response, - } - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "api_schema_hash": null, - "subgraph_was_created": false, - "supergraph_was_updated": false, - "success": true - }, - "error": { - "message": "Encountered 2 build errors while trying to build subgraph \"subgraph\" into supergraph \"name@current\".", - "code": "E029", - "details": { - "build_errors": [ - { - "message": "[Accounts] -> Things went really wrong", - "code": "AN_ERROR_CODE", - "type": "composition", - }, - { - "message": "[Films] -> Something else also went wrong", - "code": null, - "type": "composition" - } - ] - } - } - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn profiles_json() { - let mock_profiles = vec!["default".to_string(), "staging".to_string()]; - let actual_json: JsonOutput = RoverOutput::Profiles(mock_profiles).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "profiles": [ - "default", - "staging" - ], - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn introspection_json() { - let actual_json: JsonOutput = RoverOutput::Introspection( - "i cant believe its not a real introspection response".to_string(), - ) - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "introspection_response": "i cant believe its not a real introspection response", - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn error_explanation_json() { - let actual_json: JsonOutput = RoverOutput::ErrorExplanation( - "this error occurs when stuff is real complicated... I wouldn't worry about it" - .to_string(), - ) - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "explanation_markdown": "this error occurs when stuff is real complicated... I wouldn't worry about it", - "success": true - }, - "error": null - } - - ); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn empty_success_json() { - let actual_json: JsonOutput = RoverOutput::EmptySuccess.into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "success": true - }, - "error": null - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn base_error_message_json() { - let actual_json: JsonOutput = RoverError::new(anyhow!("Some random error")).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "success": false - }, - "error": { - "message": "Some random error", - "code": null - } - }); - assert_json_eq!(expected_json, actual_json); - } - - #[test] - fn coded_error_message_json() { - let actual_json: JsonOutput = RoverError::new(RoverClientError::NoSubgraphInGraph { - invalid_subgraph: "invalid_subgraph".to_string(), - valid_subgraphs: Vec::new(), - }) - .into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "success": false - }, - "error": { - "message": "Could not find subgraph \"invalid_subgraph\".", - "code": "E009" - } - }); - assert_json_eq!(expected_json, actual_json) - } - - #[test] - fn composition_error_message_json() { - let source = BuildErrors::from(vec![ - BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), - Some("AN_ERROR_CODE".to_string()), - ), - BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), - None, - ), - ]); - let actual_json: JsonOutput = - RoverError::from(RoverClientError::BuildErrors { source }).into(); - let expected_json = json!( - { - "json_version": "1.beta", - "data": { - "success": false - }, - "error": { - "details": { - "build_errors": [ - { - "message": "[Accounts] -> Things went really wrong", - "code": "AN_ERROR_CODE", - "type": "composition" - }, - { - "message": "[Films] -> Something else also went wrong", - "code": null, - "type": "composition" - } - ], - }, - "message": "Encountered 2 build errors while trying to build a supergraph.", - "code": "E029" - } - }); - assert_json_eq!(expected_json, actual_json) - } -} diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index 696ae2b2f..9fff514ec 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -1,23 +1,25 @@ +use ansi_term::Colour::Red; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::subgraph::check::{self, SubgraphCheckInput}; -use rover_client::shared::{CheckConfig, GitContext, GraphRef, ValidationPeriod}; +use rover_client::query::subgraph::check; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::git::GitContext; use crate::utils::loaders::load_schema_from_flag; use crate::utils::parsers::{ - parse_query_count_threshold, parse_query_percentage_threshold, parse_schema_source, - SchemaSource, + parse_graph_ref, parse_query_count_threshold, parse_query_percentage_threshold, + parse_schema_source, parse_validation_period, GraphRef, SchemaSource, ValidationPeriod, }; +use crate::utils::table::{self, cell, row}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Check { /// @ of graph in Apollo Studio to validate. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -49,7 +51,7 @@ pub struct Check { query_percentage_threshold: Option, /// Size of the time window with which to validate schema against (i.e "24h" or "1w 2d 5h") - #[structopt(long)] + #[structopt(long, parse(try_from_str = parse_validation_period))] validation_period: Option, } @@ -58,31 +60,116 @@ impl Check { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; - let proposed_schema = load_schema_from_flag(&self.schema, std::io::stdin())?; + let sdl = load_schema_from_flag(&self.schema, std::io::stdin())?; - eprintln!( - "Checking the proposed schema for subgraph {} against {}", - &self.subgraph, &self.graph - ); + let partial_schema = check::check_partial_schema_query::PartialSchemaInput { + sdl: Some(sdl), + // we never need to send the hash since the back end computes it from SDL + hash: None, + }; let res = check::run( - SubgraphCheckInput { - graph_ref: self.graph.clone(), - proposed_schema, - subgraph: self.subgraph.clone(), - git_context, - config: CheckConfig { + check::check_partial_schema_query::Variables { + graph_id: self.graph.name.clone(), + variant: self.graph.variant.clone(), + partial_schema, + implementing_service_name: self.subgraph.clone(), + git_context: git_context.into(), + config: check::check_partial_schema_query::HistoricQueryParameters { query_count_threshold: self.query_count_threshold, query_count_threshold_percentage: self.query_percentage_threshold, - validation_period: self.validation_period.clone(), + from: self.validation_period.clone().unwrap_or_default().from, + to: self.validation_period.clone().unwrap_or_default().to, + // we don't support configuring these, but we can't leave them out + excluded_clients: None, + ignored_operations: None, + included_variants: None, }, }, &client, )?; - Ok(RoverOutput::CheckResponse(res)) + eprintln!("Checked the proposed subgraph against {}", &self.graph); + + match res { + check::CheckResponse::CompositionErrors(composition_errors) => { + handle_composition_errors(&composition_errors) + } + check::CheckResponse::CheckResult(check_result) => handle_checks(check_result), + } + } +} + +fn handle_checks(check_result: check::CheckResult) -> Result { + let num_changes = check_result.changes.len(); + + let msg = match num_changes { + 0 => "There were no changes detected in the composed schema.".to_string(), + _ => format!( + "Compared {} schema changes against {} operations", + check_result.changes.len(), + check_result.number_of_checked_operations + ), + }; + + eprintln!("{}", &msg); + + let mut num_failures = 0; + + if !check_result.changes.is_empty() { + let mut table = table::get_table(); + + // bc => sets top row to be bold and center + table.add_row(row![bc => "Change", "Code", "Description"]); + for check in check_result.changes { + let change = match check.severity { + check::check_partial_schema_query::ChangeSeverity::NOTICE => "PASS", + check::check_partial_schema_query::ChangeSeverity::FAILURE => { + num_failures += 1; + "FAIL" + } + _ => unreachable!("Unknown change severity"), + }; + table.add_row(row![change, check.code, check.description]); + } + + eprintln!("{}", table); + } + + if let Some(url) = check_result.target_url { + eprintln!("View full details at {}", &url); + } + + match num_failures { + 0 => Ok(RoverStdout::None), + 1 => Err(anyhow::anyhow!("Encountered 1 failure while checking your subgraph.").into()), + _ => Err(anyhow::anyhow!( + "Encountered {} failures while checking your subgraph.", + num_failures + ) + .into()), + } +} + +fn handle_composition_errors( + composition_errors: &[check::check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCompositionValidationResultErrors], +) -> Result { + let num_failures = composition_errors.len(); + for error in composition_errors { + eprintln!("{} {}", Red.bold().paint("error:"), &error.message); + } + match num_failures { + 0 => Ok(RoverStdout::None), + 1 => Err( + anyhow::anyhow!("Encountered 1 composition error while composing the subgraph.").into(), + ), + _ => Err(anyhow::anyhow!( + "Encountered {} composition errors while composing the subgraph.", + num_failures + ) + .into()), } } diff --git a/src/command/subgraph/delete.rs b/src/command/subgraph/delete.rs index 4e8c3ac9e..d6f6ea524 100644 --- a/src/command/subgraph/delete.rs +++ b/src/command/subgraph/delete.rs @@ -1,19 +1,19 @@ -use ansi_term::Colour::{Cyan, Yellow}; +use ansi_term::Colour::{Cyan, Red, Yellow}; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; -use rover_client::operations::subgraph::delete::{self, SubgraphDeleteInput}; -use rover_client::shared::GraphRef; +use rover_client::query::subgraph::delete::{self, DeleteServiceResponse}; #[derive(Debug, Serialize, StructOpt)] pub struct Delete { /// @ of federated graph in Apollo Studio to delete subgraph from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -28,68 +28,75 @@ pub struct Delete { subgraph: String, /// Skips the step where the command asks for user confirmation before - /// deleting the subgraph. Also skips preview of build errors that + /// deleting the subgraph. Also skips preview of composition errors that /// might occur #[structopt(long)] confirm: bool, } impl Delete { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; + let graph_ref = self.graph.to_string(); eprintln!( - "Checking for build errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", + "Checking for composition errors resulting from deleting subgraph {} from {} using credentials from the {} profile.", Cyan.normal().paint(&self.subgraph), - Cyan.normal().paint(self.graph.to_string()), + Cyan.normal().paint(&graph_ref), Yellow.normal().paint(&self.profile_name) ); // this is probably the normal path -- preview a subgraph delete // and make the user confirm it manually. if !self.confirm { - let dry_run = true; - // run delete with dryRun, so we can preview build errors + // run delete with dryRun, so we can preview composition errors let delete_dry_run_response = delete::run( - SubgraphDeleteInput { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), - dry_run, + delete::delete_service_mutation::Variables { + graph_id: self.graph.name.clone(), + graph_variant: self.graph.variant.clone(), + name: self.subgraph.clone(), + dry_run: true, }, &client, )?; - RoverOutput::SubgraphDeleteResponse { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), - dry_run, - delete_response: delete_dry_run_response, - } - .print(); + handle_dry_run_response(delete_dry_run_response, &self.subgraph, &graph_ref); // I chose not to error here, since this is a perfectly valid path if !confirm_delete()? { eprintln!("Delete cancelled by user"); - return Ok(RoverOutput::EmptySuccess); + return Ok(RoverStdout::None); } } - let dry_run = false; - let delete_response = delete::run( - SubgraphDeleteInput { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), - dry_run, + delete::delete_service_mutation::Variables { + graph_id: self.graph.name.clone(), + graph_variant: self.graph.variant.clone(), + name: self.subgraph.clone(), + dry_run: false, }, &client, )?; - Ok(RoverOutput::SubgraphDeleteResponse { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), - dry_run, - delete_response, - }) + handle_response(delete_response, &self.subgraph, &graph_ref); + Ok(RoverStdout::None) + } +} + +fn handle_dry_run_response(response: DeleteServiceResponse, subgraph: &str, graph_ref: &str) { + let warn_prefix = Red.normal().paint("WARN:"); + if let Some(errors) = response.composition_errors { + eprintln!( + "{} Deleting the {} subgraph from {} would result in the following composition errors: \n{}", + warn_prefix, + Cyan.normal().paint(subgraph), + Cyan.normal().paint(graph_ref), + errors.join("\n") + ); + eprintln!("{} This is only a prediction. If the graph changes before confirming, these errors could change.", warn_prefix); + } else { + eprintln!("{} At the time of checking, there would be no composition errors resulting from the deletion of this subgraph.", warn_prefix); + eprintln!("{} This is only a prediction. If the graph changes before confirming, there could be composition errors.", warn_prefix) } } @@ -103,3 +110,61 @@ fn confirm_delete() -> Result { Ok(false) } } + +fn handle_response(response: DeleteServiceResponse, subgraph: &str, graph_ref: &str) { + let warn_prefix = Red.normal().paint("WARN:"); + if response.updated_gateway { + eprintln!( + "The {} subgraph was removed from {}. Remaining subgraphs were composed.", + Cyan.normal().paint(subgraph), + Cyan.normal().paint(graph_ref), + ) + } else { + eprintln!( + "{} The gateway for {} was not updated. See errors below.", + warn_prefix, + Cyan.normal().paint(graph_ref) + ) + } + + if let Some(errors) = response.composition_errors { + eprintln!( + "{} There were composition errors as a result of deleting the subgraph: \n{}", + warn_prefix, + errors.join("\n") + ) + } else { + eprintln!("There were no composition errors as a result of deleting the subgraph."); + } +} + +#[cfg(test)] +mod tests { + use super::{handle_response, DeleteServiceResponse}; + + #[test] + fn handle_response_doesnt_error_with_all_successes() { + let response = DeleteServiceResponse { + composition_errors: None, + updated_gateway: true, + }; + + handle_response(response, "accounts", "my-graph@current"); + } + + #[test] + fn handle_response_doesnt_error_with_all_failures() { + let response = DeleteServiceResponse { + composition_errors: Some(vec![ + "a bad thing happened".to_string(), + "another bad thing".to_string(), + ]), + updated_gateway: false, + }; + + handle_response(response, "accounts", "my-graph@prod"); + } + + // TODO: test the actual output of the logs whenever we do design work + // for the commands :) +} diff --git a/src/command/subgraph/fetch.rs b/src/command/subgraph/fetch.rs index 92643e71b..020dad04a 100644 --- a/src/command/subgraph/fetch.rs +++ b/src/command/subgraph/fetch.rs @@ -2,18 +2,18 @@ use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; -use rover_client::shared::GraphRef; +use rover_client::query::subgraph::fetch; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct Fetch { /// @ of graph in Apollo Studio to fetch from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -29,7 +29,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -39,14 +39,15 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let fetch_response = fetch::run( - SubgraphFetchInput { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), + let sdl = fetch::run( + fetch::fetch_subgraph_query::Variables { + graph_id: self.graph.name.clone(), + variant: self.graph.variant.clone(), }, &client, + &self.subgraph, )?; - Ok(RoverOutput::FetchResponse(fetch_response)) + Ok(RoverStdout::Sdl(sdl)) } } diff --git a/src/command/subgraph/introspect.rs b/src/command/subgraph/introspect.rs index 5b527495d..cae22c14c 100644 --- a/src/command/subgraph/introspect.rs +++ b/src/command/subgraph/introspect.rs @@ -4,12 +4,9 @@ use std::collections::HashMap; use structopt::StructOpt; use url::Url; -use rover_client::{ - blocking::GraphQLClient, - operations::subgraph::introspect::{self, SubgraphIntrospectInput}, -}; +use rover_client::{blocking::GraphQLClient, query::subgraph::introspect}; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::parsers::parse_header; use crate::Result; @@ -36,7 +33,7 @@ pub struct Introspect { } impl Introspect { - pub fn run(&self, client: Client) -> Result { + pub fn run(&self, client: Client) -> Result { let client = GraphQLClient::new(&self.endpoint.to_string(), client)?; // add the flag headers to a hashmap to pass along to rover-client @@ -47,8 +44,8 @@ impl Introspect { } } - let introspection_response = introspect::run(SubgraphIntrospectInput { headers }, &client)?; + let introspection_response = introspect::run(&client, &headers)?; - Ok(RoverOutput::Introspection(introspection_response.result)) + Ok(RoverStdout::Introspection(introspection_response.result)) } } diff --git a/src/command/subgraph/list.rs b/src/command/subgraph/list.rs index c1794801e..e469b68db 100644 --- a/src/command/subgraph/list.rs +++ b/src/command/subgraph/list.rs @@ -2,18 +2,18 @@ use ansi_term::Colour::Cyan; use serde::Serialize; use structopt::StructOpt; -use rover_client::operations::subgraph::list::{self, SubgraphListInput}; -use rover_client::shared::GraphRef; +use rover_client::query::subgraph::list; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::parsers::{parse_graph_ref, GraphRef}; use crate::Result; #[derive(Debug, Serialize, StructOpt)] pub struct List { /// @ of graph in Apollo Studio to list subgraphs from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -24,7 +24,7 @@ pub struct List { } impl List { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; eprintln!( @@ -34,12 +34,13 @@ impl List { ); let list_details = list::run( - SubgraphListInput { - graph_ref: self.graph.clone(), + list::list_subgraphs_query::Variables { + graph_id: self.graph.name.clone(), + variant: self.graph.variant.clone(), }, &client, )?; - Ok(RoverOutput::SubgraphList(list_details)) + Ok(RoverStdout::SubgraphList(list_details)) } } diff --git a/src/command/subgraph/mod.rs b/src/command/subgraph/mod.rs index 08ba2adb0..9361fcf70 100644 --- a/src/command/subgraph/mod.rs +++ b/src/command/subgraph/mod.rs @@ -8,12 +8,10 @@ mod publish; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; -use crate::utils::client::StudioClientConfig; +use crate::command::RoverStdout; +use crate::utils::{client::StudioClientConfig, git::GitContext}; use crate::Result; -use rover_client::shared::GitContext; - #[derive(Debug, Serialize, StructOpt)] pub struct Subgraph { #[structopt(subcommand)] @@ -22,7 +20,7 @@ pub struct Subgraph { #[derive(Debug, Serialize, StructOpt)] pub enum Command { - /// Check for build errors and breaking changes caused by an updated subgraph schema + /// Check for composition errors and breaking changes caused by an updated subgraph schema /// against the federated graph in the Apollo graph registry Check(check::Check), @@ -47,7 +45,7 @@ impl Subgraph { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { match &self.command { Command::Publish(command) => command.run(client_config, git_context), Command::Introspect(command) => command.run(client_config.get_reqwest_client()), diff --git a/src/command/subgraph/publish.rs b/src/command/subgraph/publish.rs index 4d2f4490e..34f68f58a 100644 --- a/src/command/subgraph/publish.rs +++ b/src/command/subgraph/publish.rs @@ -1,23 +1,23 @@ -use ansi_term::Colour::{Cyan, Yellow}; +use ansi_term::Colour::{Cyan, Red, Yellow}; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::{ client::StudioClientConfig, + git::GitContext, loaders::load_schema_from_flag, - parsers::{parse_schema_source, SchemaSource}, + parsers::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}, }; use crate::Result; -use rover_client::operations::subgraph::publish::{self, SubgraphPublishInput}; -use rover_client::shared::{GitContext, GraphRef}; +use rover_client::query::subgraph::publish::{self, PublishPartialSchemaResponse}; #[derive(Debug, Serialize, StructOpt)] pub struct Publish { /// @ of federated graph in Apollo Studio to publish to. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -54,35 +54,110 @@ impl Publish { &self, client_config: StudioClientConfig, git_context: GitContext, - ) -> Result { + ) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; + let graph_ref = format!("{}:{}", &self.graph.name, &self.graph.variant); eprintln!( "Publishing SDL to {} (subgraph: {}) using credentials from the {} profile.", - Cyan.normal().paint(&self.graph.to_string()), + Cyan.normal().paint(&graph_ref), Cyan.normal().paint(&self.subgraph), Yellow.normal().paint(&self.profile_name) ); - let schema = load_schema_from_flag(&self.schema, std::io::stdin())?; + let schema_document = load_schema_from_flag(&self.schema, std::io::stdin())?; - tracing::debug!("Publishing \n{}", &schema); + tracing::debug!("Publishing \n{}", &schema_document); let publish_response = publish::run( - SubgraphPublishInput { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), + publish::publish_partial_schema_mutation::Variables { + graph_id: self.graph.name.clone(), + graph_variant: self.graph.variant.clone(), + name: self.subgraph.clone(), + active_partial_schema: + publish::publish_partial_schema_mutation::PartialSchemaInput { + sdl: Some(schema_document), + hash: None, + }, + revision: "".to_string(), url: self.routing_url.clone(), - schema, - git_context, - convert_to_federated_graph: self.convert, + git_context: git_context.into(), }, &client, + self.convert, )?; - Ok(RoverOutput::SubgraphPublishResponse { - graph_ref: self.graph.clone(), - subgraph: self.subgraph.clone(), - publish_response, - }) + handle_publish_response(publish_response, &self.subgraph, &self.graph.name); + Ok(RoverStdout::None) } } + +fn handle_publish_response(response: PublishPartialSchemaResponse, subgraph: &str, graph: &str) { + if response.service_was_created { + eprintln!( + "A new subgraph called '{}' for the '{}' graph was created", + subgraph, graph + ); + } else { + eprintln!( + "The '{}' subgraph for the '{}' graph was updated", + subgraph, graph + ); + } + + if response.did_update_gateway { + eprintln!("The gateway for the '{}' graph was updated with a new schema, composed from the updated '{}' subgraph", graph, subgraph); + } else { + eprintln!( + "The gateway for the '{}' graph was NOT updated with a new schema", + graph + ); + } + + if let Some(errors) = response.composition_errors { + let warn_prefix = Red.normal().paint("WARN:"); + eprintln!( + "{} The following composition errors occurred: \n{}", + warn_prefix, + errors.join("\n") + ); + } else { + eprintln!("There were no composition errors as a result of publishing the subgraph."); + } +} + +#[cfg(test)] +mod tests { + use super::{handle_publish_response, PublishPartialSchemaResponse}; + + // this test is a bit weird, since we can't test the output. We just verify it + // doesn't error + #[test] + fn handle_response_doesnt_error_with_all_successes() { + let response = PublishPartialSchemaResponse { + schema_hash: Some("123456".to_string()), + did_update_gateway: true, + service_was_created: true, + composition_errors: None, + }; + + handle_publish_response(response, "accounts", "my-graph"); + } + + #[test] + fn handle_response_doesnt_error_with_all_failures() { + let response = PublishPartialSchemaResponse { + schema_hash: None, + did_update_gateway: false, + service_was_created: false, + composition_errors: Some(vec![ + "a bad thing happened".to_string(), + "another bad thing".to_string(), + ]), + }; + + handle_publish_response(response, "accounts", "my-graph"); + } + + // TODO: test the actual output of the logs whenever we do design work + // for the commands :) +} diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 0ca764f6a..fc6ba1096 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -1,19 +1,19 @@ use crate::command::supergraph::config::{self, SchemaSource, SupergraphConfig}; -use crate::utils::client::StudioClientConfig; -use crate::{anyhow, command::RoverOutput, error::RoverError, Result, Suggestion}; - -use rover_client::blocking::GraphQLClient; -use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; -use rover_client::operations::subgraph::introspect::{self, SubgraphIntrospectInput}; -use rover_client::shared::{BuildError, GraphRef}; -use rover_client::RoverClientError; +use crate::utils::{client::StudioClientConfig, parsers::parse_graph_ref}; +use crate::{anyhow, command::RoverStdout, error::RoverError, Result, Suggestion}; +use ansi_term::Colour::Red; use camino::Utf8PathBuf; -use harmonizer::ServiceDefinition as SubgraphDefinition; + +use rover_client::{ + blocking::GraphQLClient, + query::subgraph::{fetch, introspect}, +}; use serde::Serialize; +use std::{collections::HashMap, fs}; use structopt::StructOpt; -use std::{collections::HashMap, fs, str::FromStr}; +use harmonizer::ServiceDefinition as SubgraphDefinition; #[derive(Debug, Serialize, StructOpt)] pub struct Compose { @@ -29,7 +29,7 @@ pub struct Compose { } impl Compose { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let supergraph_config = config::parse_supergraph_config(&self.config_path)?; let subgraph_definitions = get_subgraph_definitions( supergraph_config, @@ -39,20 +39,24 @@ impl Compose { )?; match harmonizer::harmonize(subgraph_definitions) { - Ok(core_schema) => Ok(RoverOutput::CoreSchema(core_schema)), - Err(harmonizer_composition_errors) => { - let mut build_errors = Vec::with_capacity(harmonizer_composition_errors.len()); - for harmonizer_composition_error in harmonizer_composition_errors { - if let Some(message) = &harmonizer_composition_error.message { - build_errors.push(BuildError::composition_error( - message.to_string(), - Some(harmonizer_composition_error.code().to_string()), - )); - } + Ok(core_schema) => Ok(RoverStdout::CoreSchema(core_schema)), + Err(composition_errors) => { + let num_failures = composition_errors.len(); + for composition_error in composition_errors { + eprintln!("{} {}", Red.bold().paint("error:"), &composition_error) + } + match num_failures { + 0 => unreachable!("Composition somehow failed with no composition errors."), + 1 => Err( + anyhow!("Encountered 1 composition error while composing the graph.") + .into(), + ), + _ => Err(anyhow!( + "Encountered {} composition errors while composing the graph.", + num_failures + ) + .into()), } - Err(RoverError::new(RoverClientError::BuildErrors { - source: build_errors.into(), - })) } } } @@ -103,12 +107,7 @@ pub(crate) fn get_subgraph_definitions( client_config.get_reqwest_client(), )?; - let introspection_response = introspect::run( - SubgraphIntrospectInput { - headers: HashMap::new(), - }, - &client, - )?; + let introspection_response = introspect::run(&client, &HashMap::new())?; let schema = introspection_response.result; // We don't require a routing_url for this variant of a schema, @@ -121,19 +120,18 @@ pub(crate) fn get_subgraph_definitions( let subgraph_definition = SubgraphDefinition::new(subgraph_name, url, &schema); subgraphs.push(subgraph_definition); } - SchemaSource::Subgraph { - graphref: graph_ref, - subgraph, - } => { - // given a graph_ref and subgraph, run subgraph fetch to + SchemaSource::Subgraph { graphref, subgraph } => { + // given a graphref and subgraph, run subgraph fetch to // obtain SDL and add it to subgraph_definition. let client = client_config.get_authenticated_client(profile_name)?; - let result = fetch::run( - SubgraphFetchInput { - graph_ref: GraphRef::from_str(graph_ref)?, - subgraph: subgraph.clone(), + let graphref = parse_graph_ref(graphref)?; + let schema = fetch::run( + fetch::fetch_subgraph_query::Variables { + graph_id: graphref.name.clone(), + variant: graphref.variant.clone(), }, &client, + subgraph, )?; // We don't require a routing_url for this variant of a schema, @@ -143,8 +141,7 @@ pub(crate) fn get_subgraph_definitions( // and use that when no routing_url is provided. let url = &subgraph_data.routing_url.clone().unwrap_or_default(); - let subgraph_definition = - SubgraphDefinition::new(subgraph_name, url, &result.sdl.contents); + let subgraph_definition = SubgraphDefinition::new(subgraph_name, url, &schema); subgraphs.push(subgraph_definition); } } diff --git a/src/command/supergraph/compose/no_compose.rs b/src/command/supergraph/compose/no_compose.rs index dea61423f..bc9c6904e 100644 --- a/src/command/supergraph/compose/no_compose.rs +++ b/src/command/supergraph/compose/no_compose.rs @@ -5,7 +5,7 @@ use structopt::StructOpt; use crate::utils::client::StudioClientConfig; use crate::{ anyhow, - command::RoverOutput, + command::RoverStdout, error::{RoverError, Suggestion}, Result, }; @@ -24,7 +24,7 @@ pub struct Compose { } impl Compose { - pub fn run(&self, _client_config: StudioClientConfig) -> Result { + pub fn run(&self, _client_config: StudioClientConfig) -> Result { let mut err = RoverError::new(anyhow!( "This version of Rover does not support this command." )); diff --git a/src/command/supergraph/fetch.rs b/src/command/supergraph/fetch.rs index 793af1f87..3bc84ded4 100644 --- a/src/command/supergraph/fetch.rs +++ b/src/command/supergraph/fetch.rs @@ -1,8 +1,8 @@ use crate::utils::client::StudioClientConfig; -use crate::{command::RoverOutput, Result}; +use crate::utils::parsers::{parse_graph_ref, GraphRef}; +use crate::{command::RoverStdout, Result}; -use rover_client::operations::supergraph::fetch::{self, SupergraphFetchInput}; -use rover_client::shared::GraphRef; +use rover_client::query::supergraph::fetch; use ansi_term::Colour::{Cyan, Yellow}; use serde::Serialize; @@ -12,7 +12,7 @@ use structopt::StructOpt; pub struct Fetch { /// @ of graph in Apollo Studio to fetch from. /// @ may be left off, defaulting to @current - #[structopt(name = "GRAPH_REF")] + #[structopt(name = "GRAPH_REF", parse(try_from_str = parse_graph_ref))] #[serde(skip_serializing)] graph: GraphRef, @@ -23,7 +23,7 @@ pub struct Fetch { } impl Fetch { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { let client = client_config.get_authenticated_client(&self.profile_name)?; let graph_ref = self.graph.to_string(); eprintln!( @@ -32,13 +32,14 @@ impl Fetch { Yellow.normal().paint(&self.profile_name) ); - let fetch_response = fetch::run( - SupergraphFetchInput { - graph_ref: self.graph.clone(), + let supergraph_sdl = fetch::run( + fetch::fetch_supergraph_query::Variables { + graph_id: self.graph.name.clone(), + variant: self.graph.variant.clone(), }, &client, )?; - Ok(RoverOutput::FetchResponse(fetch_response)) + Ok(RoverStdout::SupergraphSdl(supergraph_sdl)) } } diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index 51e2141a3..8b575b5fa 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -5,7 +5,7 @@ mod fetch; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; use crate::Result; @@ -26,7 +26,7 @@ pub enum Command { } impl Supergraph { - pub fn run(&self, client_config: StudioClientConfig) -> Result { + pub fn run(&self, client_config: StudioClientConfig) -> Result { match &self.command { Command::Fetch(command) => command.run(client_config), Command::Compose(command) => command.run(client_config), diff --git a/src/command/update/check.rs b/src/command/update/check.rs index 3b73ab4ad..f4fe31ebd 100644 --- a/src/command/update/check.rs +++ b/src/command/update/check.rs @@ -2,7 +2,7 @@ use reqwest::blocking::Client; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::{utils::version, Result}; use houston as config; @@ -13,8 +13,8 @@ pub struct Check { } impl Check { - pub fn run(&self, config: config::Config, client: Client) -> Result { + pub fn run(&self, config: config::Config, client: Client) -> Result { version::check_for_update(config, true, client)?; - Ok(RoverOutput::EmptySuccess) + Ok(RoverStdout::None) } } diff --git a/src/command/update/mod.rs b/src/command/update/mod.rs index 8964ff90e..9296ba172 100644 --- a/src/command/update/mod.rs +++ b/src/command/update/mod.rs @@ -4,7 +4,7 @@ use reqwest::blocking::Client; use serde::Serialize; use structopt::StructOpt; -use crate::command::RoverOutput; +use crate::command::RoverStdout; use crate::Result; use houston as config; @@ -22,7 +22,7 @@ pub enum Command { } impl Update { - pub fn run(&self, config: config::Config, client: Client) -> Result { + pub fn run(&self, config: config::Config, client: Client) -> Result { match &self.command { Command::Check(command) => command.run(config, client), } diff --git a/src/error/metadata/code.rs b/src/error/metadata/code.rs index 86782c40b..07e09efb7 100644 --- a/src/error/metadata/code.rs +++ b/src/error/metadata/code.rs @@ -34,8 +34,6 @@ pub enum Code { E026, E027, E028, - E029, - E030, } impl Display for Code { @@ -77,8 +75,6 @@ impl Code { (Code::E026, include_str!("./codes/E026.md").to_string()), (Code::E027, include_str!("./codes/E027.md").to_string()), (Code::E028, include_str!("./codes/E028.md").to_string()), - (Code::E029, include_str!("./codes/E029.md").to_string()), - (Code::E030, include_str!("./codes/E030.md").to_string()), ]; contents.into_iter().collect() } @@ -88,8 +84,8 @@ impl Code { pub fn explain(&self) -> String { let all_explanations = Code::explanations(); let explanation = all_explanations.get(self); - if let Some(explanation) = explanation { - format!("**{}**\n\n{}\n\n", &self, &explanation) + if let Some(expl) = explanation { + format!("**{}**\n\n{}\n\n", self.to_string(), expl.clone()) } else { "Explanation not available".to_string() } diff --git a/src/error/metadata/codes/E028.md b/src/error/metadata/codes/E028.md index c31e8ea2f..52b1dd800 100644 --- a/src/error/metadata/codes/E028.md +++ b/src/error/metadata/codes/E028.md @@ -1,3 +1,3 @@ This error occurs when Rover could not connect to an HTTP endpoint. -If you encountered this error while running introspection, you'll want to make sure that you typed the endpoint correctly, your Internet connection is stable, and that your server is responding to requests. You may wish to run the command again with `--log=debug`. \ No newline at end of file +If you encountered this error while running introspection, you'll want to make sure that you typed the endpoint correctly, your Internet connection is stable, and that your server is responding to requests. You may wish to run the command again with `--log=debug`. diff --git a/src/error/metadata/codes/E029.md b/src/error/metadata/codes/E029.md deleted file mode 100644 index 195333e5f..000000000 --- a/src/error/metadata/codes/E029.md +++ /dev/null @@ -1,5 +0,0 @@ -This error occurs when you propose a subgraph schema that could not be built. - -There are many reasons why you may run into build errors. This error should include information about _why_ the proposed subgraph schema could not be composed. Error code references can be found [here](https://www.apollographql.com/docs/federation/errors/). - -Some build errors are part of normal workflows. For instance, you may need to publish a subgraph that does not compose if you are trying to [migrate an entity or field](https://www.apollographql.com/docs/federation/entities/#migrating-entities-and-fields-advanced). diff --git a/src/error/metadata/codes/E030.md b/src/error/metadata/codes/E030.md deleted file mode 100644 index b761379ac..000000000 --- a/src/error/metadata/codes/E030.md +++ /dev/null @@ -1 +0,0 @@ -This error occurs when an operation check fails. This means that you proposed a schema that would break operations in use by existing clients. You can configure this behavior in the Checks -> Configuration view in [Apollo Studio](https://studio.apollographql.com/), and you can read more about client checks [here](https://www.apollographql.com/docs/studio/schema-checks/). \ No newline at end of file diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index 39887b477..8bc0d67ad 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -11,19 +11,15 @@ use crate::utils::env::RoverEnvKey; use std::{env, fmt::Display}; -use serde::Serialize; +use ansi_term::Colour::Red; /// Metadata contains extra information about specific errors /// Currently this includes an optional error `Code` /// and an optional `Suggestion` -#[derive(Default, Serialize, Debug)] +#[derive(Default, Debug)] pub struct Metadata { - // skip serializing for now until we can appropriately strip color codes - #[serde(skip_serializing)] pub suggestion: Option, pub code: Option, - - #[serde(skip_serializing)] pub is_parse_error: bool, } @@ -62,34 +58,11 @@ impl From<&mut anyhow::Error> for Metadata { RoverClientError::InvalidSeverity => { (Some(Suggestion::SubmitIssue), Some(Code::E006)) } - RoverClientError::SubgraphBuildErrors { - graph_ref, - subgraph, - source: _, - } => ( - Some(Suggestion::FixSubgraphSchema { - graph_ref: graph_ref.clone(), - subgraph: subgraph.clone(), - }), - Some(Code::E029), - ), - RoverClientError::BuildErrors { .. } => { - (Some(Suggestion::FixCompositionErrors), Some(Code::E029)) - } - RoverClientError::OperationCheckFailure { - graph_ref, - check_response: _, - } => ( - Some(Suggestion::FixOperationsInSchema { - graph_ref: graph_ref.clone(), - }), - Some(Code::E030), - ), RoverClientError::SubgraphIntrospectionNotAvailable => { (Some(Suggestion::UseFederatedGraph), Some(Code::E007)) } RoverClientError::ExpectedFederatedGraph { - graph_ref: _, + graph: _, can_operation_convert, } => { if *can_operation_convert { @@ -99,12 +72,14 @@ impl From<&mut anyhow::Error> for Metadata { } } RoverClientError::NoSchemaForVariant { - graph_ref, + graph, + invalid_variant, valid_variants, frontend_url_root, } => ( Some(Suggestion::ProvideValidVariant { - graph_ref: graph_ref.clone(), + graph_name: graph.clone(), + invalid_variant: invalid_variant.clone(), valid_variants: valid_variants.clone(), frontend_url_root: frontend_url_root.clone(), }), @@ -117,32 +92,30 @@ impl From<&mut anyhow::Error> for Metadata { Some(Suggestion::ProvideValidSubgraph(valid_subgraphs.clone())), Some(Code::E009), ), - RoverClientError::GraphNotFound { .. } => { + RoverClientError::NoService { graph: _ } => { (Some(Suggestion::CheckGraphNameAndAuth), Some(Code::E010)) } - RoverClientError::GraphQl { .. } => (None, None), - RoverClientError::IntrospectionError { .. } => (None, Some(Code::E011)), - RoverClientError::ClientError { .. } => (None, Some(Code::E012)), + RoverClientError::GraphQl { msg: _ } => (None, None), + RoverClientError::IntrospectionError { msg: _ } => (None, Some(Code::E011)), + RoverClientError::ClientError { msg: _ } => (None, Some(Code::E012)), RoverClientError::InvalidKey => (Some(Suggestion::CheckKey), Some(Code::E013)), RoverClientError::MalformedKey => (Some(Suggestion::ProperKey), Some(Code::E014)), - RoverClientError::UnparseableReleaseVersion { source: _ } => { + RoverClientError::UnparseableReleaseVersion => { (Some(Suggestion::SubmitIssue), Some(Code::E015)) } - RoverClientError::BadReleaseUrl => (Some(Suggestion::SubmitIssue), None), - RoverClientError::NoSupergraphBuilds { .. } => { + RoverClientError::NoCompositionPublishes { + graph: _, + composition_errors, + } => { + for composition_error in composition_errors { + eprintln!("{} {}", Red.bold().paint("error:"), composition_error); + } (Some(Suggestion::RunComposition), Some(Code::E027)) } - RoverClientError::AdhocError { .. } => (None, None), + RoverClientError::AdhocError { msg: _ } => (None, None), RoverClientError::CouldNotConnect { .. } => { (Some(Suggestion::CheckServerConnection), Some(Code::E028)) } - RoverClientError::InvalidGraphRef { .. } => { - unreachable!("Graph ref parse errors should be caught via structopt") - } - RoverClientError::InvalidValidationPeriodDuration(_) - | RoverClientError::ValidationPeriodTooGranular => { - unreachable!("Validation period parse errors should be caught via structopt") - } }; return Metadata { suggestion, diff --git a/src/error/metadata/suggestion.rs b/src/error/metadata/suggestion.rs index 506166681..36a234eb4 100644 --- a/src/error/metadata/suggestion.rs +++ b/src/error/metadata/suggestion.rs @@ -2,14 +2,11 @@ use std::cmp::Ordering; use std::fmt::{self, Display}; use ansi_term::Colour::{Cyan, Yellow}; -use rover_client::shared::GraphRef; use crate::utils::env::RoverEnvKey; -use serde::Serialize; - /// `Suggestion` contains possible suggestions for remedying specific errors. -#[derive(Serialize, Debug)] +#[derive(Debug)] pub enum Suggestion { SubmitIssue, SetConfigHome, @@ -21,7 +18,8 @@ pub enum Suggestion { CheckGraphNameAndAuth, ProvideValidSubgraph(Vec), ProvideValidVariant { - graph_ref: GraphRef, + graph_name: String, + invalid_variant: String, valid_variants: Vec, frontend_url_root: String, }, @@ -34,14 +32,6 @@ pub enum Suggestion { CheckServerConnection, ConvertGraphToSubgraph, CheckGnuVersion, - FixSubgraphSchema { - graph_ref: GraphRef, - subgraph: String, - }, - FixCompositionErrors, - FixOperationsInSchema { - graph_ref: GraphRef, - }, } impl Display for Suggestion { @@ -75,7 +65,7 @@ impl Display for Suggestion { ) } Suggestion::RunComposition => { - format!("Try resolving the build errors in your subgraph(s), and publish them with the {} command.", Yellow.normal().paint("`rover subgraph publish`")) + format!("Try resolving the composition errors in your subgraph(s), and publish them with the {} command.", Yellow.normal().paint("`rover subgraph publish`")) } Suggestion::UseFederatedGraph => { "Try running the command on a valid federated graph, or use the appropriate `rover graph` command instead of `rover subgraph`.".to_string() @@ -89,15 +79,15 @@ impl Display for Suggestion { valid_subgraphs.join(", ") ) } - Suggestion::ProvideValidVariant { graph_ref, valid_variants, frontend_url_root} => { - if let Some(maybe_variant) = did_you_mean(&graph_ref.variant, valid_variants).pop() { - format!("Did you mean \"{}@{}\"?", graph_ref.name, maybe_variant) + Suggestion::ProvideValidVariant { graph_name, invalid_variant, valid_variants, frontend_url_root} => { + if let Some(maybe_variant) = did_you_mean(invalid_variant, valid_variants).pop() { + format!("Did you mean \"{}@{}\"?", graph_name, maybe_variant) } else { let num_valid_variants = valid_variants.len(); match num_valid_variants { - 0 => unreachable!(&format!("Graph \"{}\" exists but has no variants.", graph_ref.name)), - 1 => format!("The only existing variant for graph \"{}\" is \"{}\".", graph_ref.name, valid_variants[0]), - 2 => format!("The existing variants for graph \"{}\" are \"{}\" and \"{}\".", graph_ref.name, valid_variants[0], valid_variants[1]), + 0 => unreachable!(&format!("Graph \"{}\" exists but has no variants.", graph_name)), + 1 => format!("The only existing variant for graph \"{}\" is \"{}\".", graph_name, valid_variants[0]), + 2 => format!("The existing variants for graph \"{}\" are \"{}\" and \"{}\".", graph_name, valid_variants[0], valid_variants[1]), 3 ..= 10 => { let mut valid_variants_msg = "".to_string(); for (i, variant) in valid_variants.iter().enumerate() { @@ -109,11 +99,11 @@ impl Display for Suggestion { valid_variants_msg.push_str(", "); } } - format!("The existing variants for graph \"{}\" are {}.", &graph_ref.name, &valid_variants_msg) + format!("The existing variants for graph \"{}\" are {}.", graph_name, &valid_variants_msg) } _ => { - let graph_url = format!("{}/graph/{}/settings", &frontend_url_root, &graph_ref.name); - format!("You can view the variants for graph \"{}\" by visiting {}", graph_ref.name, Cyan.normal().paint(&graph_url)) + let graph_url = format!("{}/graph/{}/settings", &frontend_url_root, &graph_name); + format!("You can view the variants for graph \"{}\" by visiting {}", graph_name, Cyan.normal().paint(&graph_url)) } } } @@ -139,9 +129,6 @@ impl Display for Suggestion { Suggestion::CheckServerConnection => "Make sure the endpoint is accepting connections and is spelled correctly".to_string(), Suggestion::ConvertGraphToSubgraph => "If you are sure you want to convert a non-federated graph to a subgraph, you can re-run the same command with a `--convert` flag.".to_string(), Suggestion::CheckGnuVersion => "This is likely an issue with your current version of `glibc`. Try running `ldd --version`, and if the version >= 2.18, we suggest installing the Rover binary built for `x86_64-unknown-linux-gnu`".to_string(), - Suggestion::FixSubgraphSchema { graph_ref, subgraph } => format!("The changes in the schema you proposed for subgraph {} are incompatible with supergraph {}. See {} for more information on resolving build errors.", Yellow.normal().paint(subgraph.to_string()), Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), - Suggestion::FixCompositionErrors => format!("The subgraph schemas you provided are incompatible with each other. See {} for more information on resolving build errors.", Cyan.normal().paint("https://www.apollographql.com/docs/federation/errors/")), - Suggestion::FixOperationsInSchema { graph_ref } => format!("The changes in the schema you proposed are incompatible with graph {}. See {} for more information on resolving operation check errors.", Yellow.normal().paint(graph_ref.to_string()), Cyan.normal().paint("https://www.apollographql.com/docs/studio/schema-checks/")) }; write!(formatter, "{}", &suggestion) } diff --git a/src/error/mod.rs b/src/error/mod.rs index dde8b47a1..1da93d72b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -6,64 +6,21 @@ pub(crate) use metadata::Metadata; pub type Result = std::result::Result; use ansi_term::Colour::{Cyan, Red}; -use rover_client::RoverClientError; -use serde::ser::SerializeStruct; -use serde::{Serialize, Serializer}; -use serde_json::{json, Value}; use std::borrow::BorrowMut; -use std::error::Error; use std::fmt::{self, Debug, Display}; pub use self::metadata::Suggestion; -use rover_client::shared::BuildErrors; - /// A specialized `Error` type for Rover that wraps `anyhow` /// and provides some extra `Metadata` for end users depending /// on the specific error they encountered. -#[derive(Serialize, Debug)] +#[derive(Debug)] pub struct RoverError { - #[serde(flatten, serialize_with = "serialize_anyhow")] error: anyhow::Error, - - #[serde(flatten)] metadata: Metadata, } -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -enum RoverDetails { - BuildErrors(BuildErrors), -} - -fn serialize_anyhow(error: &anyhow::Error, serializer: S) -> std::result::Result -where - S: Serializer, -{ - let top_level_struct = "error"; - let message_field_name = "message"; - let details_struct = "details"; - - if let Some(rover_client_error) = error.downcast_ref::() { - if let Some(rover_client_error_source) = rover_client_error.source() { - if let Some(build_errors) = rover_client_error_source.downcast_ref::() { - let mut top_level_data = serializer.serialize_struct(top_level_struct, 2)?; - top_level_data.serialize_field(message_field_name, &error.to_string())?; - top_level_data.serialize_field( - details_struct, - &RoverDetails::BuildErrors(build_errors.clone()), - )?; - return top_level_data.end(); - } - } - } - - let mut data = serializer.serialize_struct(top_level_struct, 1)?; - data.serialize_field(message_field_name, &error.to_string())?; - data.end() -} - impl RoverError { pub fn new(error: E) -> Self where @@ -92,33 +49,6 @@ impl RoverError { pub fn suggestion(&mut self) -> &Option { &self.metadata.suggestion } - - pub fn print(&self) { - if let Some(RoverClientError::OperationCheckFailure { - graph_ref: _, - check_response, - }) = self.error.downcast_ref::() - { - println!("{}", check_response.get_table()); - } - - eprintln!("{}", self); - } - - pub(crate) fn get_internal_data_json(&self) -> Value { - if let Some(RoverClientError::OperationCheckFailure { - graph_ref: _, - check_response, - }) = self.error.downcast_ref::() - { - return check_response.get_json(); - } - Value::Null - } - - pub(crate) fn get_internal_error_json(&self) -> Value { - json!(self) - } } impl Display for RoverError { @@ -134,7 +64,7 @@ impl Display for RoverError { "error:".to_string() }; let error_descriptor = Red.bold().paint(&error_descriptor_message); - writeln!(formatter, "{} {:?}", error_descriptor, &self.error)?; + writeln!(formatter, "{} {}", error_descriptor, &self.error)?; error_descriptor_message }; diff --git a/crates/rover-client/src/shared/git_context.rs b/src/utils/git.rs similarity index 55% rename from crates/rover-client/src/shared/git_context.rs rename to src/utils/git.rs index 6d065d800..48b4ffd37 100644 --- a/crates/rover-client/src/shared/git_context.rs +++ b/src/utils/git.rs @@ -1,99 +1,95 @@ +use crate::utils::env::{RoverEnv, RoverEnvKey}; +use crate::Result; +use rover_client::query::{graph, subgraph}; + use std::env; use git2::{Reference, Repository}; use git_url_parse::GitUrl; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, PartialEq)] pub struct GitContext { pub branch: Option, pub author: Option, pub commit: Option, + pub message: Option, pub remote_url: Option, } impl GitContext { - pub fn new_with_override(override_git_context: GitContext) -> Self { - let repo = GitContext::get_repo(); - - let mut remote_url = override_git_context.remote_url; - - if let Some(repo) = repo { - remote_url = remote_url.or_else(|| GitContext::get_remote_url(&repo)); - if let Ok(head) = repo.head() { - let branch = override_git_context - .branch - .or_else(|| GitContext::get_branch(&head)); - - let author = override_git_context - .author - .or_else(|| GitContext::get_author(&head)); - - let commit = override_git_context - .commit - .or_else(|| GitContext::get_commit(&head)); - - return GitContext { - branch, - author, - commit, - remote_url, - }; + pub fn try_from_rover_env(env: &RoverEnv) -> Result { + let repo = Repository::discover(env::current_dir()?); + match repo { + Ok(repo) => { + let head = repo.head().ok(); + Ok(Self { + branch: GitContext::get_branch(env, head.as_ref())?, + commit: GitContext::get_commit(env, head.as_ref())?, + author: GitContext::get_author(env, head.as_ref())?, + remote_url: GitContext::get_remote_url(env, Some(&repo))?, + message: None, + }) } + Err(_) => Ok(Self { + branch: GitContext::get_branch(env, None)?, + commit: GitContext::get_commit(env, None)?, + author: GitContext::get_author(env, None)?, + remote_url: GitContext::get_remote_url(env, None)?, + message: None, + }), } - - GitContext { - branch: override_git_context.branch, - author: override_git_context.author, - commit: override_git_context.commit, - remote_url, - } - } - - pub fn default() -> Self { - GitContext::new_with_override(GitContext { - author: None, - branch: None, - commit: None, - remote_url: None, - }) } - fn get_repo() -> Option { - env::current_dir() - .map(|d| Repository::discover(d).ok()) - .ok() - .flatten() + fn get_branch(env: &RoverEnv, head: Option<&Reference>) -> Result> { + Ok(env.get(RoverEnvKey::VcsBranch)?.or_else(|| { + let mut branch = None; + if let Some(head) = head { + branch = head.shorthand().map(|s| s.to_string()) + } + branch + })) } - fn get_branch(head: &Reference) -> Option { - head.shorthand().map(|s| s.to_string()) + fn get_commit(env: &RoverEnv, head: Option<&Reference>) -> Result> { + Ok(env.get(RoverEnvKey::VcsCommit)?.or_else(|| { + let mut commit = None; + if let Some(head) = head { + if let Ok(head_commit) = head.peel_to_commit() { + commit = Some(head_commit.id().to_string()) + } + } + commit + })) } - fn get_commit(head: &Reference) -> Option { - if let Ok(head_commit) = head.peel_to_commit() { - Some(head_commit.id().to_string()) - } else { - None - } + fn get_author(env: &RoverEnv, head: Option<&Reference>) -> Result> { + Ok(env.get(RoverEnvKey::VcsAuthor)?.or_else(|| { + let mut author = None; + if let Some(head) = head { + if let Ok(head_commit) = head.peel_to_commit() { + author = Some(head_commit.author().to_string()) + } + } + author + })) } - fn get_author(head: &Reference) -> Option { - if let Ok(head_commit) = head.peel_to_commit() { - Some(head_commit.author().to_string()) - } else { - None - } - } + fn get_remote_url(env: &RoverEnv, repo: Option<&Repository>) -> Result> { + let remote_url = env.get(RoverEnvKey::VcsRemoteUrl)?.or_else(|| { + let mut remote_url = None; + if let Some(repo) = repo { + if let Ok(remote) = repo.find_remote("origin") { + remote_url = remote.url().map(|r| r.to_string()) + } + } + remote_url + }); - fn get_remote_url(repo: &Repository) -> Option { - let remote_url = if let Ok(remote) = repo.find_remote("origin") { - remote.url().map(|r| r.to_string()) + Ok(if let Some(remote_url) = remote_url { + GitContext::sanitize_remote_url(&remote_url) } else { None - }; - remote_url - .map(|r| GitContext::sanitize_remote_url(&r)) - .flatten() + }) } // Parses and sanitizes git remote urls according to the same rules as @@ -135,9 +131,63 @@ impl GitContext { } } +type GraphPublishContextInput = graph::publish::publish_schema_mutation::GitContextInput; +impl From for GraphPublishContextInput { + fn from(git_context: GitContext) -> GraphPublishContextInput { + GraphPublishContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: git_context.message, + } + } +} + +type GraphCheckContextInput = graph::check::check_schema_query::GitContextInput; +impl From for GraphCheckContextInput { + fn from(git_context: GitContext) -> GraphCheckContextInput { + GraphCheckContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: git_context.message, + } + } +} + +type SubgraphPublishContextInput = + subgraph::publish::publish_partial_schema_mutation::GitContextInput; +impl From for SubgraphPublishContextInput { + fn from(git_context: GitContext) -> SubgraphPublishContextInput { + SubgraphPublishContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: git_context.message, + } + } +} + +type SubgraphCheckContextInput = subgraph::check::check_partial_schema_query::GitContextInput; +impl From for SubgraphCheckContextInput { + fn from(git_context: GitContext) -> SubgraphCheckContextInput { + SubgraphCheckContextInput { + branch: git_context.branch, + commit: git_context.commit, + committer: git_context.author, + remote_url: git_context.remote_url, + message: git_context.message, + } + } +} + #[cfg(test)] mod tests { use super::*; + use crate::PKG_NAME; #[test] fn removed_user_from_remote_with_only_user() { @@ -268,21 +318,30 @@ mod tests { let commit = "f84b32caddddfdd9fa87d7ce2140d56eabe805ee".to_string(); let remote_url = "git@bitbucket.org:roku/theworstremoteintheworld.git".to_string(); - let override_git_context = GitContext { + let mut rover_env = RoverEnv::new(); + rover_env.insert(RoverEnvKey::VcsBranch, &branch); + rover_env.insert(RoverEnvKey::VcsAuthor, &author); + rover_env.insert(RoverEnvKey::VcsCommit, &commit); + rover_env.insert(RoverEnvKey::VcsRemoteUrl, &remote_url); + + let expected_git_context = GitContext { branch: Some(branch), author: Some(author), commit: Some(commit), + message: None, remote_url: Some(remote_url), }; - let actual_git_context = GitContext::new_with_override(override_git_context.clone()); + let actual_git_context = GitContext::try_from_rover_env(&rover_env) + .expect("Could not create GitContext from RoverEnv"); - assert_eq!(override_git_context, actual_git_context); + assert_eq!(expected_git_context, actual_git_context); } #[test] - fn it_can_create_git_context_commit_author_remote_url() { - let git_context = GitContext::default(); + fn it_can_create_git_context_committ_author_remote_url() { + let git_context = + GitContext::try_from_rover_env(&RoverEnv::new()).expect("Could not create git context"); assert!(git_context.branch.is_some()); assert!(git_context.author.is_some()); @@ -293,8 +352,10 @@ mod tests { panic!("Could not find the commit hash"); } + assert!(git_context.message.is_none()); + if let Some(remote_url) = git_context.remote_url { - assert!(remote_url.contains("apollographql")); + assert!(remote_url.contains(PKG_NAME)); } else { panic!("GitContext could not find the remote url"); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 61b7c1331..989333fed 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,6 @@ pub mod client; pub mod env; +pub mod git; pub mod loaders; pub mod parsers; pub mod pkg; diff --git a/src/utils/parsers.rs b/src/utils/parsers.rs index 492e484be..0f6c31024 100644 --- a/src/utils/parsers.rs +++ b/src/utils/parsers.rs @@ -1,4 +1,8 @@ use camino::Utf8PathBuf; +use regex::Regex; +use serde::Serialize; + +use std::{convert::TryInto, fmt}; use crate::{error::RoverError, Result}; @@ -21,6 +25,84 @@ pub fn parse_schema_source(loc: &str) -> Result { } } +#[derive(Debug, Clone, PartialEq)] +pub struct GraphRef { + pub name: String, + pub variant: String, +} + +impl fmt::Display for GraphRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}", self.name, self.variant) + } +} + +/// NOTE: THIS IS A TEMPORARY SOLUTION. IN THE FUTURE, ALL GRAPH ID PARSING +/// WILL HAPPEN IN THE BACKEND TO KEEP EVERYTHING CONSISTENT. THIS IS AN +/// INCOMPLETE PLACEHOLDER, AND MAY NOT COVER EVERY SINGLE VALID USE CASE +pub fn parse_graph_ref(graph_id: &str) -> Result { + let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_-]{0,63}$").unwrap(); + let variant_pattern = Regex::new(r"^([a-zA-Z][a-zA-Z0-9_-]{0,63})@(.{0,63})$").unwrap(); + + let valid_graph_name_only = pattern.is_match(graph_id); + let valid_graph_with_variant = variant_pattern.is_match(graph_id); + + if valid_graph_name_only { + Ok(GraphRef { + name: graph_id.to_string(), + variant: "current".to_string(), + }) + } else if valid_graph_with_variant { + let matches = variant_pattern.captures(graph_id).unwrap(); + let name = matches.get(1).unwrap().as_str(); + let variant = matches.get(2).unwrap().as_str(); + Ok(GraphRef { + name: name.to_string(), + variant: variant.to_string(), + }) + } else { + Err(RoverError::parse_error("Graph IDs must be in the format or @, where can only contain letters, numbers, or the characters `-` or `_`, and must be 64 characters or less. must be 64 characters or less.")) + } +} + +#[derive(Debug, Serialize, Default, Clone)] +pub struct ValidationPeriod { + // these timestamps could be represented as i64, but the API expects + // Option + pub from: Option, + pub to: Option, +} + +// Validation period is parsed as human readable time. +// such as "10m 50s" +pub fn parse_validation_period(period: &str) -> Result { + // attempt to parse strings like + // 15h 10m 2s into number of seconds + if period.contains("ns") || period.contains("us") || period.contains("ms") { + return Err(RoverError::parse_error( + "You can only specify a duration as granular as seconds.", + )); + }; + let duration = humantime::parse_duration(period).map_err(RoverError::parse_error)?; + let window: i64 = duration + .as_secs() + .try_into() + .map_err(RoverError::parse_error)?; + + if window > 0 { + Ok(ValidationPeriod { + // search "from" a negative time window + from: Some(format!("{}", -window)), + // search "to" now (-0) seconds + to: Some("-0".to_string()), + }) + } else { + Err(RoverError::parse_error( + "The number of seconds must be a positive integer.", + )) + } +} + pub fn parse_query_count_threshold(threshold: &str) -> Result { let threshold = threshold.parse::()?; if threshold < 1 { @@ -58,7 +140,7 @@ pub fn parse_header(header: &str) -> Result<(String, String)> { #[cfg(test)] mod tests { - use super::{parse_schema_source, SchemaSource}; + use super::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource}; #[test] fn it_correctly_parses_stdin_flag() { @@ -81,4 +163,52 @@ mod tests { let loc = parse_schema_source(""); assert!(loc.is_err()); } + + #[test] + fn parse_graph_ref_works() { + assert!(parse_graph_ref("engine#%^").is_err()); + assert!(parse_graph_ref( + "1234567890123456789012345678901234567890123456789012345678901234567890" + ) + .is_err()); + assert!(parse_graph_ref("1boi").is_err()); + assert!(parse_graph_ref("_eng").is_err()); + assert!(parse_graph_ref( + "engine@1234567890123456789012345678901234567890123456789012345678901234567890" + ) + .is_err()); + assert!(parse_graph_ref( + "engine1234567890123456789012345678901234567890123456789012345678901234567890@prod" + ) + .is_err()); + + assert_eq!( + parse_graph_ref("engine@okay").unwrap(), + GraphRef { + name: "engine".to_string(), + variant: "okay".to_string() + } + ); + assert_eq!( + parse_graph_ref("studio").unwrap(), + GraphRef { + name: "studio".to_string(), + variant: "current".to_string() + } + ); + assert_eq!( + parse_graph_ref("this_should_work").unwrap(), + GraphRef { + name: "this_should_work".to_string(), + variant: "current".to_string() + } + ); + assert_eq!( + parse_graph_ref("it-is-cool@my-special/variant:from$hell").unwrap(), + GraphRef { + name: "it-is-cool".to_string(), + variant: "my-special/variant:from$hell".to_string() + } + ); + } } diff --git a/src/utils/stringify.rs b/src/utils/stringify.rs index 3d45e594c..6bd57471e 100644 --- a/src/utils/stringify.rs +++ b/src/utils/stringify.rs @@ -2,30 +2,20 @@ //! a struct with the `Display`/`FromStr` implementations //! if it does not implement `Serialize`/`Deserialize` //! code taken from this: https://github.com/serde-rs/serde/issues/1316 -//! and can be used by annotating a field with either -//! #[serde(serialize_with = "from_display")] or -//! #[serde(serialize_with = "option_from_display")] -//! depending on if the type you're serializing is nested in an Option +//! and can be used by annotating a field with +//! #[serde(serialize_with = "from_display")] use std::fmt::Display; use serde::Serializer; -pub fn option_from_display(value: &Option, serializer: S) -> Result +pub fn from_display(value: &Option, serializer: S) -> Result where T: Display, S: Serializer, { if let Some(value) = value { - from_display(value, serializer) + serializer.collect_str(value) } else { serializer.serialize_none() } } - -pub fn from_display(value: &T, serializer: S) -> Result -where - T: Display, - S: Serializer, -{ - serializer.collect_str(value) -} diff --git a/src/utils/table.rs b/src/utils/table.rs index 1b098c1ec..c19ad7778 100644 --- a/src/utils/table.rs +++ b/src/utils/table.rs @@ -1,16 +1,9 @@ -use prettytable::{ - format::{consts::FORMAT_BOX_CHARS, TableFormat}, - Table, -}; +use prettytable::{format::consts::FORMAT_BOX_CHARS, Table}; pub use prettytable::{cell, row}; pub fn get_table() -> Table { let mut table = Table::new(); - table.set_format(get_table_format()); + table.set_format(*FORMAT_BOX_CHARS); table } - -pub fn get_table_format() -> TableFormat { - *FORMAT_BOX_CHARS -} diff --git a/src/utils/version.rs b/src/utils/version.rs index 9153c0b91..8ac32c7a8 100644 --- a/src/utils/version.rs +++ b/src/utils/version.rs @@ -4,10 +4,11 @@ use ansi_term::Colour::{Cyan, Yellow}; use billboard::{Alignment, Billboard}; use camino::Utf8PathBuf; use reqwest::blocking::Client; +use semver::Version; use crate::{Result, PKG_VERSION}; use houston as config; -use rover_client::releases::{get_latest_release, Version}; +use rover_client::releases::get_latest_release; const ONE_HOUR: u64 = 60 * 60; const ONE_DAY: u64 = ONE_HOUR * 24; @@ -54,11 +55,12 @@ fn do_update_check( should_output_if_updated: bool, client: Client, ) -> Result<()> { - let latest_version = get_latest_release(client)?; - let pretty_latest = Cyan.normal().paint(format!("v{}", latest_version)); - if latest_version > Version::parse(PKG_VERSION)? { + let latest = get_latest_release(client)?; + let pretty_latest = Cyan.normal().paint(format!("v{}", latest)); + let update_available = is_latest_newer(&latest, PKG_VERSION)?; + if update_available { let message = format!( - "There is a newer version of Rover available: {} (currently running v{})\n\nFor instructions on how to install, run {}", + "There is a newer version of Rover available: {} (currently running v{})\n\nFor instructions on how to install, run {}", &pretty_latest, PKG_VERSION, Yellow.normal().paint("`rover docs open start`") @@ -93,3 +95,9 @@ fn get_last_checked_time_from_disk(version_file: &Utf8PathBuf) -> Option Result { + let latest = Version::parse(latest)?; + let running = Version::parse(running)?; + Ok(latest > running) +} diff --git a/xtask/src/commands/lint.rs b/xtask/src/commands/lint.rs index 6e423ec2f..3e065e120 100644 --- a/xtask/src/commands/lint.rs +++ b/xtask/src/commands/lint.rs @@ -1,7 +1,7 @@ use anyhow::Result; use structopt::StructOpt; -use crate::tools::{CargoRunner, NpmRunner}; +use crate::tools::CargoRunner; #[derive(Debug, StructOpt)] pub struct Lint {} @@ -10,8 +10,6 @@ impl Lint { pub fn run(&self, verbose: bool) -> Result<()> { let mut cargo_runner = CargoRunner::new(verbose)?; cargo_runner.lint()?; - let npm_runner = NpmRunner::new(verbose)?; - npm_runner.lint()?; Ok(()) } } diff --git a/xtask/src/commands/prep/mod.rs b/xtask/src/commands/prep/mod.rs index 2f3a0637d..4be5ea4a0 100644 --- a/xtask/src/commands/prep/mod.rs +++ b/xtask/src/commands/prep/mod.rs @@ -14,7 +14,6 @@ impl Prep { pub fn run(&self, verbose: bool) -> Result<()> { let npm_runner = NpmRunner::new(verbose)?; npm_runner.prepare_package()?; - npm_runner.update_linter()?; installers::update_versions()?; let docs_runner = DocsRunner::new()?; docs_runner diff --git a/xtask/src/tools/npm.rs b/xtask/src/tools/npm.rs index 8a6d79b87..a5a3bb239 100644 --- a/xtask/src/tools/npm.rs +++ b/xtask/src/tools/npm.rs @@ -5,42 +5,30 @@ use std::str; use crate::{ tools::Runner, - utils::{CommandOutput, PKG_PROJECT_ROOT, PKG_VERSION}, + utils::{self, CommandOutput, PKG_VERSION}, }; pub(crate) struct NpmRunner { runner: Runner, - npm_installer_package_directory: Utf8PathBuf, - npm_lint_directory: Utf8PathBuf, + npm_package_directory: Utf8PathBuf, } impl NpmRunner { pub(crate) fn new(verbose: bool) -> Result { let runner = Runner::new("npm", verbose)?; - let project_root = PKG_PROJECT_ROOT.clone(); - - let npm_lint_directory = project_root.join("crates").join("rover-client"); - let npm_installer_package_directory = project_root.join("installers").join("npm"); - - if !npm_installer_package_directory.exists() { - return Err(anyhow!( + let npm_package_directory = utils::project_root()?.join("installers").join("npm"); + + if npm_package_directory.exists() { + Ok(Self { + runner, + npm_package_directory, + }) + } else { + Err(anyhow!( "Rover's npm installer package does not seem to be located here:\n{}", - &npm_installer_package_directory - )); - } - - if !npm_lint_directory.exists() { - return Err(anyhow!( - "Rover's GraphQL linter package does not seem to be located here:\n{}", - &npm_lint_directory - )); + &npm_package_directory + )) } - - Ok(Self { - runner, - npm_installer_package_directory, - npm_lint_directory, - }) } /// prepares our npm installer package for release @@ -63,52 +51,32 @@ impl NpmRunner { Ok(()) } - pub(crate) fn update_linter(&self) -> Result<()> { - self.npm_exec(&["update"], &self.npm_lint_directory)?; - Ok(()) - } - - pub(crate) fn lint(&self) -> Result<()> { - self.npm_exec(&["install"], &self.npm_lint_directory)?; - self.npm_exec(&["run", "lint"], &self.npm_lint_directory)?; - Ok(()) - } - fn update_dependency_tree(&self) -> Result<()> { - self.npm_exec(&["update"], &self.npm_installer_package_directory)?; + self.npm_exec(&["update"])?; Ok(()) } fn install_dependencies(&self) -> Result<()> { // we --ignore-scripts so that we do not attempt to download and unpack a // released rover tarball - self.npm_exec( - &["install", "--ignore-scripts"], - &self.npm_installer_package_directory, - )?; + self.npm_exec(&["install", "--ignore-scripts"])?; Ok(()) } fn update_version(&self) -> Result<()> { - self.npm_exec( - &["version", &PKG_VERSION, "--allow-same-version"], - &self.npm_installer_package_directory, - )?; + self.npm_exec(&["version", &PKG_VERSION, "--allow-same-version"])?; Ok(()) } fn publish_dry_run(&self) -> Result<()> { - let command_output = self.npm_exec( - &["publish", "--dry-run"], - &self.npm_installer_package_directory, - )?; + let command_output = self.npm_exec(&["publish", "--dry-run"])?; assert_publish_includes(&command_output) .with_context(|| "There were problems with the output of 'npm publish --dry-run'.") } - fn npm_exec(&self, args: &[&str], directory: &Utf8PathBuf) -> Result { - self.runner.exec(args, directory, None) + fn npm_exec(&self, args: &[&str]) -> Result { + self.runner.exec(args, &self.npm_package_directory, None) } } diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs index d7849c6e9..83cf1e097 100644 --- a/xtask/src/utils.rs +++ b/xtask/src/utils.rs @@ -33,7 +33,7 @@ fn rover_version() -> Result { .to_string()) } -fn project_root() -> Result { +pub(crate) fn project_root() -> Result { let manifest_dir = Utf8PathBuf::try_from(MANIFEST_DIR) .with_context(|| "Could not find the root directory.")?; let root_dir = manifest_dir