diff --git a/.changeset/red-needles-know.md b/.changeset/red-needles-know.md new file mode 100644 index 0000000000..9adf8469bc --- /dev/null +++ b/.changeset/red-needles-know.md @@ -0,0 +1,6 @@ +--- +'@graphql-hive/cli': patch +'hive': patch +--- + +Adds optional url argument to schema checks diff --git a/integration-tests/tests/cli/__snapshots__/schema.spec.ts.snap b/integration-tests/tests/cli/__snapshots__/schema.spec.ts.snap index 94f7a591ca..e54376b50e 100644 --- a/integration-tests/tests/cli/__snapshots__/schema.spec.ts.snap +++ b/integration-tests/tests/cli/__snapshots__/schema.spec.ts.snap @@ -105,6 +105,15 @@ stdout--------------------------------------------: __NONE__ `; +exports[`FEDERATION > schema:check accepts a \`--url\` argument in distributed projects 1`] = ` +:::::::::::::::: CLI SUCCESS OUTPUT ::::::::::::::::: + +stdout--------------------------------------------: +✔ Schema registry is empty, nothing to compare your schema with. +View full report: +http://__URL__ +`; + exports[`FEDERATION > schema:check should notify user when registry is empty > schemaCheck 1`] = ` :::::::::::::::: CLI SUCCESS OUTPUT ::::::::::::::::: @@ -261,6 +270,20 @@ stdout--------------------------------------------: __NONE__ `; +exports[`SINGLE > schema:check rejects a \`--url\` argument in single projects 1`] = ` +:::::::::::::::: CLI FAILURE OUTPUT ::::::::::::::: +exitCode------------------------------------------: +1 +stderr--------------------------------------------: +__NONE__ +stdout--------------------------------------------: +✖ Detected 1 error + + - url is only supported by distributed projects + + +`; + exports[`SINGLE > schema:check should notify user when registry is empty > schemaCheck 1`] = ` :::::::::::::::: CLI SUCCESS OUTPUT ::::::::::::::::: @@ -433,6 +456,15 @@ stdout--------------------------------------------: __NONE__ `; +exports[`STITCHING > schema:check accepts a \`--url\` argument in distributed projects 1`] = ` +:::::::::::::::: CLI SUCCESS OUTPUT ::::::::::::::::: + +stdout--------------------------------------------: +✔ Schema registry is empty, nothing to compare your schema with. +View full report: +http://__URL__ +`; + exports[`STITCHING > schema:check should notify user when registry is empty > schemaCheck 1`] = ` :::::::::::::::: CLI SUCCESS OUTPUT ::::::::::::::::: diff --git a/integration-tests/tests/cli/schema.spec.ts b/integration-tests/tests/cli/schema.spec.ts index 6313dcadc2..10d45dafd7 100644 --- a/integration-tests/tests/cli/schema.spec.ts +++ b/integration-tests/tests/cli/schema.spec.ts @@ -354,6 +354,76 @@ describe.each([ProjectType.Stitching, ProjectType.Federation, ProjectType.Single await expect(fetchCmd).resolves.toMatchSnapshot('latest sdl'); }, ); + + test.skipIf(projectType !== ProjectType.Single)( + 'schema:check rejects a `--url` argument in single projects', + async ({ expect }) => { + const { createOrg } = await initSeed().createOwner(); + const { inviteAndJoinMember, createProject } = await createOrg(); + await inviteAndJoinMember(); + const { createTargetAccessToken } = await createProject(projectType); + const { secret } = await createTargetAccessToken({}); + + await expect( + schemaCheck( + [ + '--registry.accessToken', + secret, + '--service', + 'example', + '--url', + 'https://example.graphql-hive.com/graphql', + '--author', + 'Kamil', + 'fixtures/init-schema.graphql', + ], + { + // set these environment variables to "emulate" a GitHub actions environment + // We set GITHUB_EVENT_PATH to "" because on our CI it can be present and we want + // consistent snapshot output behaviour. + GITHUB_ACTIONS: '1', + GITHUB_REPOSITORY: 'foo/foo', + GITHUB_EVENT_PATH: '', + }, + ), + ).rejects.toMatchSnapshot(); + }, + ); + + test.skipIf(projectType === ProjectType.Single)( + 'schema:check accepts a `--url` argument in distributed projects', + async ({ expect }) => { + const { createOrg } = await initSeed().createOwner(); + const { inviteAndJoinMember, createProject } = await createOrg(); + await inviteAndJoinMember(); + const { createTargetAccessToken } = await createProject(projectType); + const { secret } = await createTargetAccessToken({}); + + await expect( + schemaCheck( + [ + '--registry.accessToken', + secret, + '--service', + 'example', + '--url', + 'https://example.graphql-hive.com/graphql', + '--author', + 'Kamil', + 'fixtures/init-schema.graphql', + ], + { + // set these environment variables to "emulate" a GitHub actions environment + // We set GITHUB_EVENT_PATH to "" because on our CI it can be present and we want + // consistent snapshot output behaviour. + GITHUB_ACTIONS: '1', + GITHUB_REPOSITORY: 'foo/foo', + GITHUB_EVENT_PATH: '', + }, + ), + ).resolves.toMatchSnapshot(); + }, + ); }, ); diff --git a/packages/libraries/cli/src/commands/schema/check.ts b/packages/libraries/cli/src/commands/schema/check.ts index 0adca1eb1b..621c76122f 100644 --- a/packages/libraries/cli/src/commands/schema/check.ts +++ b/packages/libraries/cli/src/commands/schema/check.ts @@ -161,6 +161,10 @@ export default class SchemaCheck extends Command { ' This can either be a slug following the format "$organizationSlug/$projectSlug/$targetSlug" (e.g "the-guild/graphql-hive/staging")' + ' or an UUID (e.g. "a0f4c605-6541-4350-8cfe-b31f21a4bf80").', }), + url: Flags.string({ + description: + 'If checking a service, then you can optionally provide the service URL to see the difference in the supergraph during the check.', + }), }; static args = { @@ -273,6 +277,7 @@ export default class SchemaCheck extends Command { : null, contextId: flags.contextId ?? undefined, target, + url: flags.url, }, }, }); diff --git a/packages/services/api/src/modules/schema/module.graphql.ts b/packages/services/api/src/modules/schema/module.graphql.ts index 57d080b80c..c3ac36158e 100644 --- a/packages/services/api/src/modules/schema/module.graphql.ts +++ b/packages/services/api/src/modules/schema/module.graphql.ts @@ -588,6 +588,10 @@ export default gql` Manually approved breaking changes will be memorized for schema checks with the same context id. """ contextId: String + """ + Optional url if wanting to show subgraph url changes inside checks. + """ + url: String } input SchemaDeleteInput { diff --git a/packages/services/api/src/modules/schema/providers/models/composite.ts b/packages/services/api/src/modules/schema/providers/models/composite.ts index 4dd2656fdb..9df572b2b7 100644 --- a/packages/services/api/src/modules/schema/providers/models/composite.ts +++ b/packages/services/api/src/modules/schema/providers/models/composite.ts @@ -108,6 +108,7 @@ export class CompositeModel { input: { sdl: string; serviceName: string; + url: string | null; }; selector: { organizationId: string; @@ -146,7 +147,9 @@ export class CompositeModel { sdl: input.sdl, service_name: input.serviceName, service_url: - latest?.schemas?.find(s => s.service_name === input.serviceName)?.service_url ?? 'temp', + input.url ?? + latest?.schemas?.find(s => s.service_name === input.serviceName)?.service_url ?? + 'temp', action: 'PUSH', metadata: null, }; diff --git a/packages/services/api/src/modules/schema/providers/schema-publisher.ts b/packages/services/api/src/modules/schema/providers/schema-publisher.ts index 7352a90acf..02bd6194f2 100644 --- a/packages/services/api/src/modules/schema/providers/schema-publisher.ts +++ b/packages/services/api/src/modules/schema/providers/schema-publisher.ts @@ -341,6 +341,27 @@ export class SchemaPublisher { }); } + // if url is provided but this is not a distributed project + if ( + input.url != null && + !(project.type === ProjectType.FEDERATION || project.type === ProjectType.STITCHING) + ) { + this.logger.debug('url is only supported by distributed projects (type=%s)', project.type); + increaseSchemaCheckCountMetric('rejected'); + + return { + __typename: 'SchemaCheckError', + valid: false, + changes: [], + warnings: [], + errors: [ + { + message: 'url is only supported by distributed projects', + }, + ], + } as const; + } + if ( (project.type === ProjectType.FEDERATION || project.type === ProjectType.STITCHING) && input.service == null @@ -548,6 +569,7 @@ export class SchemaPublisher { input: { sdl, serviceName: input.service, + url: input.url ?? null, }, selector, latest: latestVersion