From 29c45dfbfc8ab87e9e84fec9c8def41ba01c3fe8 Mon Sep 17 00:00:00 2001 From: jdolle <1841898+jdolle@users.noreply.github.com> Date: Wed, 8 Jan 2025 04:16:55 -0800 Subject: [PATCH] Add subgraphs type to cli schema:fetch (#6255) --- .changeset/big-experts-melt.md | 5 ++ integration-tests/testkit/cli.ts | 15 ++++ integration-tests/tests/cli/schema.spec.ts | 43 +++++++++- packages/libraries/apollo/src/version.ts | 2 +- packages/libraries/cli/README.md | 6 +- packages/libraries/cli/package.json | 1 + .../cli/src/commands/schema/fetch.ts | 84 ++++++++++++++----- .../libraries/cli/src/helpers/print-table.ts | 29 +++++++ packages/libraries/envelop/src/version.ts | 2 +- packages/libraries/yoga/src/version.ts | 2 +- .../docs/src/pages/docs/api-reference/cli.mdx | 6 ++ 11 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 .changeset/big-experts-melt.md create mode 100644 packages/libraries/cli/src/helpers/print-table.ts diff --git a/.changeset/big-experts-melt.md b/.changeset/big-experts-melt.md new file mode 100644 index 000000000..48dc180d9 --- /dev/null +++ b/.changeset/big-experts-melt.md @@ -0,0 +1,5 @@ +--- +'@graphql-hive/cli': minor +--- + +Added subgraph type to schema:fetch cmd to print subgraph details diff --git a/integration-tests/testkit/cli.ts b/integration-tests/testkit/cli.ts index fdbe3e0f2..8131c834a 100644 --- a/integration-tests/testkit/cli.ts +++ b/integration-tests/testkit/cli.ts @@ -62,6 +62,14 @@ export async function schemaDelete(args: string[]) { ); } +export async function schemaFetch(args: string[]) { + const registryAddress = await getServiceHost('server', 8082); + + return await exec( + ['schema:fetch', `--registry.endpoint`, `http://${registryAddress}/graphql`, ...args].join(' '), + ); +} + async function dev(args: string[]) { const registryAddress = await getServiceHost('server', 8082); @@ -288,10 +296,17 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { ]); } + async function fetchCmd(input: { type: 'subgraphs' | 'supergraph' | 'sdl'; actionId: string }) { + const cmd = schemaFetch(['--token', tokens.readwrite, '--type', input.type, input.actionId]); + + return cmd; + } + return { publish, check, delete: deleteCommand, dev: devCmd, + fetch: fetchCmd, }; } diff --git a/integration-tests/tests/cli/schema.spec.ts b/integration-tests/tests/cli/schema.spec.ts index e9739ee42..ac82e7b5a 100644 --- a/integration-tests/tests/cli/schema.spec.ts +++ b/integration-tests/tests/cli/schema.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable no-process-env */ import { createHash } from 'node:crypto'; import { ProjectType } from 'testkit/gql/graphql'; -import { createCLI, schemaCheck, schemaPublish } from '../../testkit/cli'; +import { createCLI, schemaCheck, schemaFetch, schemaPublish } from '../../testkit/cli'; import { initSeed } from '../../testkit/seed'; describe.each` @@ -281,4 +281,45 @@ describe.each` }), ); }); + + test.concurrent('schema:fetch can fetch a schema with target:registry:read access', async () => { + const { createOrg } = await initSeed().createOwner(); + const { inviteAndJoinMember, createProject } = await createOrg(); + await inviteAndJoinMember(); + const { createTargetAccessToken } = await createProject(projectType, { + useLegacyRegistryModels: model === 'legacy', + }); + const { secret, latestSchema } = await createTargetAccessToken({}); + + const cli = createCLI({ + readonly: secret, + readwrite: secret, + }); + + await schemaPublish([ + '--registry.accessToken', + secret, + '--author', + 'Kamil', + '--commit', + 'abc123', + ...serviceNameArgs, + ...serviceUrlArgs, + 'fixtures/init-schema.graphql', + ]); + + const schema = await latestSchema(); + const numSchemas = schema.latestVersion?.schemas.nodes.length; + const fetchCmd = cli.fetch({ + type: 'subgraphs', + actionId: 'abc123', + }); + const rHeader = `service\\s+url\\s+date`; + const rUrl = `http:\\/\\/\\S+(:\\d+)?|n/a`; + const rSubgraph = `[-]+\\s+\\S+\\s+(${rUrl})\\s+\\S+Z\\s+`; + const rFooter = `subgraphs length: ${numSchemas}`; + await expect(fetchCmd).resolves.toMatch( + new RegExp(`${rHeader}\\s+(${rSubgraph}){${numSchemas}}${rFooter}`), + ); + }); }); diff --git a/packages/libraries/apollo/src/version.ts b/packages/libraries/apollo/src/version.ts index 2c5a35465..55e04c82c 100644 --- a/packages/libraries/apollo/src/version.ts +++ b/packages/libraries/apollo/src/version.ts @@ -1 +1 @@ -export const version = '0.36.2'; +export const version = '0.36.3'; diff --git a/packages/libraries/cli/README.md b/packages/libraries/cli/README.md index 5dee86ca9..170669c3e 100644 --- a/packages/libraries/cli/README.md +++ b/packages/libraries/cli/README.md @@ -383,7 +383,7 @@ _See code: ## `hive schema:fetch ACTIONID` -fetch schema or supergraph from the Hive API +fetch a schema, supergraph, or list of subgraphs from the Hive API ``` USAGE @@ -399,11 +399,11 @@ FLAGS --registry.accessToken= registry access token --registry.endpoint= registry endpoint --token= api token - --type= Type to fetch (possible types: sdl, supergraph) + --type= Type to fetch (possible types: sdl, supergraph, subgraphs) --write= Write to a file (possible extensions: .graphql, .gql, .gqls, .graphqls) DESCRIPTION - fetch schema or supergraph from the Hive API + fetch a schema, supergraph, or list of subgraphs from the Hive API ``` _See code: diff --git a/packages/libraries/cli/package.json b/packages/libraries/cli/package.json index 81f11b588..fb408d729 100644 --- a/packages/libraries/cli/package.json +++ b/packages/libraries/cli/package.json @@ -40,6 +40,7 @@ "schema:check:federation": "pnpm start schema:check examples/federation.graphql --service reviews", "schema:check:single": "pnpm start schema:check examples/single.graphql", "schema:check:stitching": "pnpm start schema:check --service posts examples/stitching.posts.graphql", + "schema:fetch:subgraphs": "pnpm start schema:fetch --type=subgraphs", "schema:publish:federation": "pnpm start schema:publish --service reviews --url reviews.com/graphql examples/federation.reviews.graphql", "start": "./bin/dev", "version": "oclif readme && git add README.md" diff --git a/packages/libraries/cli/src/commands/schema/fetch.ts b/packages/libraries/cli/src/commands/schema/fetch.ts index ff50c43ac..34db9335b 100644 --- a/packages/libraries/cli/src/commands/schema/fetch.ts +++ b/packages/libraries/cli/src/commands/schema/fetch.ts @@ -5,24 +5,42 @@ import Command from '../../base-command'; import { graphql } from '../../gql'; import { graphqlEndpoint } from '../../helpers/config'; import { ACCESS_TOKEN_MISSING } from '../../helpers/errors'; +import { printTable } from '../../helpers/print-table'; const SchemaVersionForActionIdQuery = graphql(/* GraphQL */ ` query SchemaVersionForActionId( $actionId: ID! $includeSDL: Boolean! $includeSupergraph: Boolean! + $includeSubgraphs: Boolean! ) { schemaVersionForActionId(actionId: $actionId) { id valid sdl @include(if: $includeSDL) supergraph @include(if: $includeSupergraph) + schemas @include(if: $includeSubgraphs) { + nodes { + __typename + ... on SingleSchema { + id + date + } + ... on CompositeSchema { + id + date + url + service + } + } + total + } } } `); export default class SchemaFetch extends Command { - static description = 'fetch schema or supergraph from the Hive API'; + static description = 'fetch a schema, supergraph, or list of subgraphs from the Hive API'; static flags = { /** @deprecated */ registry: Flags.string({ @@ -48,7 +66,7 @@ export default class SchemaFetch extends Command { }), type: Flags.string({ aliases: ['T'], - description: 'Type to fetch (possible types: sdl, supergraph)', + description: 'Type to fetch (possible types: sdl, supergraph, subgraphs)', }), write: Flags.string({ aliases: ['W'], @@ -105,6 +123,7 @@ export default class SchemaFetch extends Command { actionId, includeSDL: sdlType === 'sdl', includeSupergraph: sdlType === 'supergraph', + includeSubgraphs: sdlType === 'subgraphs', }, }); @@ -116,28 +135,49 @@ export default class SchemaFetch extends Command { return this.error(`Schema is invalid for action id ${actionId}`); } - const schema = - result.schemaVersionForActionId.sdl ?? result.schemaVersionForActionId.supergraph; + if (result.schemaVersionForActionId?.schemas) { + const { total, nodes } = result.schemaVersionForActionId.schemas; + const table = [ + ['service', 'url', 'date'], + ...nodes.map(node => [ + /** @ts-expect-error: If service is undefined then use id. */ + node.service ?? node.id, + node.__typename === 'CompositeSchema' ? node.url : 'n/a', + node.date as string, + ]), + ]; + const stats = `subgraphs length: ${total}`; + const printed = `${printTable(table)}\n\r${stats}`; - if (schema == null) { - return this.error(`No ${sdlType} found for action id ${actionId}`); - } - - if (flags.write) { - const filepath = resolve(process.cwd(), flags.write); - switch (extname(flags.write.toLowerCase())) { - case '.graphql': - case '.gql': - case '.gqls': - case '.graphqls': - await writeFile(filepath, schema, 'utf8'); - break; - default: - this.fail(`Unsupported file extension ${extname(flags.write)}`); - this.exit(1); + if (flags.write) { + const filepath = resolve(process.cwd(), flags.write); + await writeFile(filepath, printed, 'utf8'); } - return; + this.log(printed); + } else { + const schema = + result.schemaVersionForActionId.sdl ?? result.schemaVersionForActionId.supergraph; + + if (schema == null) { + return this.error(`No ${sdlType} found for action id ${actionId}`); + } + + if (flags.write) { + const filepath = resolve(process.cwd(), flags.write); + switch (extname(flags.write.toLowerCase())) { + case '.graphql': + case '.gql': + case '.gqls': + case '.graphqls': + await writeFile(filepath, schema, 'utf8'); + break; + default: + this.fail(`Unsupported file extension ${extname(flags.write)}`); + this.exit(1); + } + return; + } + this.log(schema); } - this.log(schema); } } diff --git a/packages/libraries/cli/src/helpers/print-table.ts b/packages/libraries/cli/src/helpers/print-table.ts new file mode 100644 index 000000000..78fde3265 --- /dev/null +++ b/packages/libraries/cli/src/helpers/print-table.ts @@ -0,0 +1,29 @@ +type TableCell = string | number | Date; +type Table = TableCell[][]; + +function printCell(cell: TableCell): string { + if (cell instanceof Date) { + return cell.toISOString(); + } + return String(cell); +} + +function mapTable(table: T[][], map: (cell: T, row: number, col: number) => O): O[][] { + return table.map((row, r) => row.map((cell, c) => map(cell, r, c))); +} + +export function printTable(table: Table) { + const printedTable = mapTable(table, printCell); + const columnWidths: number[] = []; + for (let row of printedTable) { + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + columnWidths[i] = Math.max(columnWidths[i] || 0, cell.length); + } + } + const totalWidth = columnWidths.reduce((acc, n) => acc + n, 0) + (columnWidths.length - 1) * 2; + const divider = `${'-'.repeat(totalWidth)}\n`; + + const paddedTable = mapTable(printedTable, (cell, r, c) => cell.padEnd(columnWidths[c] + 2)); + return paddedTable.map(row => `${row.join('')}\n`).join(divider); +} diff --git a/packages/libraries/envelop/src/version.ts b/packages/libraries/envelop/src/version.ts index a8388e14a..6a03babc2 100644 --- a/packages/libraries/envelop/src/version.ts +++ b/packages/libraries/envelop/src/version.ts @@ -1 +1 @@ -export const version = '0.33.10'; +export const version = '0.33.11'; diff --git a/packages/libraries/yoga/src/version.ts b/packages/libraries/yoga/src/version.ts index cfad1a37b..13dc8be48 100644 --- a/packages/libraries/yoga/src/version.ts +++ b/packages/libraries/yoga/src/version.ts @@ -1 +1 @@ -export const version = '0.39.0'; +export const version = '0.39.1'; diff --git a/packages/web/docs/src/pages/docs/api-reference/cli.mdx b/packages/web/docs/src/pages/docs/api-reference/cli.mdx index d48047bb8..e44ec70c7 100644 --- a/packages/web/docs/src/pages/docs/api-reference/cli.mdx +++ b/packages/web/docs/src/pages/docs/api-reference/cli.mdx @@ -398,6 +398,12 @@ For projects with a supergraph it is also possible to fetch the supergraph. hive schema:fetch --type supergraph --write supergraph.graphql feb8aa9ec8932eb ``` +It is also possible to print a list of subgraph details in an ascii table. + +```bash +hive schema:fetch --type subgraphs feb8aa9ec8932eb +``` + For more information please refer to the [CLI readme](https://github.com/graphql-hive/platform/blob/main/packages/libraries/cli/README.md#commands).