Skip to content

Commit

Permalink
Merge branch 'main' into linkable-spec-lib
Browse files Browse the repository at this point in the history
  • Loading branch information
jdolle authored Feb 4, 2025
2 parents b751f8b + fa9dcf0 commit 142969f
Show file tree
Hide file tree
Showing 55 changed files with 971 additions and 393 deletions.
6 changes: 6 additions & 0 deletions .changeset/hungry-files-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'hive': minor
---

Modify GraphQL fields used by CLI to accept an optional specified target that is used for
identifying the affected target instead of resolving the target from the access token.
5 changes: 5 additions & 0 deletions .changeset/tall-lies-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'hive': patch
---

A minor defect in Laboratory has been fixed that previously caused the application to crash when local storage was in a particular state.
30 changes: 30 additions & 0 deletions .changeset/wet-eggs-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'@graphql-hive/cli': minor
---

Add `--target` flag for commands `app:create`, `app:publish`, `operations:check`, `schema:check`,
`schema:delete`, `schema:fetch`, `schema:publish` and `dev`.

The `--target` flag can be used to specify the target on which the operation should be performed.
Either a slug or ID of the target can be provided.

A provided slug must follow the format `$organizationSlug/$projectSlug/$targetSlug` (e.g.
`the-guild/graphql-hive/staging`).

**Example using target slug**

```bash
hive schema:publish --target the-guild/graphql-hive/production ./my-schema.graphql
```

A target id, must be a valid target UUID.

**Example using target id**

```bash
hive schema:publish --target a0f4c605-6541-4350-8cfe-b31f21a4bf80 ./my-schema.graphql
```

**Note:** We encourage starting to use the `--target` flag today. In the future the flag will become
mandatory as we are moving to a more flexible approach of access tokens that can be granted access
to multiple targets.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ experience of its final users**.
GraphQL Hive is completely open-source under the MIT license, meaning that you are free to host on
your own infrastructure.

- [Changelog](./deployments/CHANGELOG.md)
- [Changelog](./deployment/CHANGELOG.md)

GraphQL Hive helps you get a global overview of the usage of your GraphQL API with:

Expand Down
8 changes: 8 additions & 0 deletions integration-tests/testkit/cli-snapshot-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,18 @@ const variableReplacements = [
pattern: /(Reference: )[^ ]+/gi,
mask: '$1__ID__',
},
{
pattern: /(Request ID:)[^)]+"/gi,
mask: '$1 __REQUEST_ID__',
},
{
pattern: /(https?:\/\/)[^\n ]+/gi,
mask: '$1__URL__',
},
{
pattern: /[ ]+\n/gi,
mask: '\n',
},
];

/**
Expand Down
6 changes: 3 additions & 3 deletions integration-tests/tests/cli/__snapshots__/schema.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ exports[`FEDERATION > schema:publish should see Invalid Token error when token i
exitCode------------------------------------------:
2
stderr--------------------------------------------:
› Error: A valid registry token is required to perform the action. The
› Error: A valid registry token is required to perform the action. The
› registry token used does not exist or has been revoked. [106]
› > See https://__URL__ for
› a complete list of error codes and recommended fixes.
Expand Down Expand Up @@ -319,7 +319,7 @@ exports[`SINGLE > schema:publish should see Invalid Token error when token is in
exitCode------------------------------------------:
2
stderr--------------------------------------------:
› Error: A valid registry token is required to perform the action. The
› Error: A valid registry token is required to perform the action. The
› registry token used does not exist or has been revoked. [106]
› > See https://__URL__ for
› a complete list of error codes and recommended fixes.
Expand Down Expand Up @@ -493,7 +493,7 @@ exports[`STITCHING > schema:publish should see Invalid Token error when token is
exitCode------------------------------------------:
2
stderr--------------------------------------------:
› Error: A valid registry token is required to perform the action. The
› Error: A valid registry token is required to perform the action. The
› registry token used does not exist or has been revoked. [106]
› > See https://__URL__ for
› a complete list of error codes and recommended fixes.
Expand Down
100 changes: 99 additions & 1 deletion integration-tests/tests/cli/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ describe.each([ProjectType.Stitching, ProjectType.Federation, ProjectType.Single
const { inviteAndJoinMember, createProject } = await createOrg();
await inviteAndJoinMember();
const { createTargetAccessToken } = await createProject(projectType);
const { secret, latestSchema } = await createTargetAccessToken({});
const { secret } = await createTargetAccessToken({});

const cli = createCLI({
readonly: secret,
Expand All @@ -355,3 +355,101 @@ describe.each([ProjectType.Stitching, ProjectType.Federation, ProjectType.Single
);
},
);

test.concurrent(
'schema:publish with --target parameter matching the access token (slug)',
async ({ expect }) => {
const { createOrg } = await initSeed().createOwner();
const { inviteAndJoinMember, createProject, organization } = await createOrg();
await inviteAndJoinMember();
const { createTargetAccessToken, project, target } = await createProject();
const { secret } = await createTargetAccessToken({});

const targetSlug = [organization.slug, project.slug, target.slug].join('/');

await expect(
schemaPublish([
'--registry.accessToken',
secret,
'--author',
'Kamil',
'--target',
targetSlug,
'fixtures/init-schema.graphql',
]),
).resolves.toMatchInlineSnapshot(`
:::::::::::::::: CLI SUCCESS OUTPUT :::::::::::::::::
stdout--------------------------------------------:
✔ Published initial schema.
ℹ Available at http://__URL__
`);
},
);

test.concurrent(
'schema:publish with --target parameter matching the access token (UUID)',
async ({ expect }) => {
const { createOrg } = await initSeed().createOwner();
const { inviteAndJoinMember, createProject } = await createOrg();
await inviteAndJoinMember();
const { createTargetAccessToken, target } = await createProject();
const { secret } = await createTargetAccessToken({});

await expect(
schemaPublish([
'--registry.accessToken',
secret,
'--author',
'Kamil',
'--target',
target.id,
'fixtures/init-schema.graphql',
]),
).resolves.toMatchInlineSnapshot(`
:::::::::::::::: CLI SUCCESS OUTPUT :::::::::::::::::
stdout--------------------------------------------:
✔ Published initial schema.
ℹ Available at http://__URL__
`);
},
);

test.concurrent(
'schema:publish fails with --target parameter not matching the access token (slug)',
async ({ expect }) => {
const { createOrg } = await initSeed().createOwner();
const { inviteAndJoinMember, createProject } = await createOrg();
await inviteAndJoinMember();
const { createTargetAccessToken } = await createProject();
const { secret } = await createTargetAccessToken({});

const targetSlug = 'i/do/not-match';

await expect(
schemaPublish([
'--registry.accessToken',
secret,
'--author',
'Kamil',
'--target',
targetSlug,
'fixtures/init-schema.graphql',
]),
).rejects.toMatchInlineSnapshot(`
:::::::::::::::: CLI FAILURE OUTPUT :::::::::::::::
exitCode------------------------------------------:
2
stderr--------------------------------------------:
› Error: No access (reason: "Missing permission for performing
› 'schemaVersion:publish' on resource") (Request ID: __REQUEST_ID__) [115]
› > See https://__URL__ for
› a complete list of error codes and recommended fixes.
› To disable this message set HIVE_NO_ERROR_TIP=1
› Reference: __ID__
stdout--------------------------------------------:
__NONE__
`);
},
);
20 changes: 20 additions & 0 deletions packages/libraries/cli/src/commands/app/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { Args, Flags } from '@oclif/core';
import Command from '../../base-command';
import { graphql } from '../../gql';
import { AppDeploymentStatus } from '../../gql/graphql';
import * as GraphQLSchema from '../../gql/graphql';
import { graphqlEndpoint } from '../../helpers/config';
import {
APIError,
InvalidTargetError,
MissingEndpointError,
MissingRegistryTokenError,
PersistedOperationsMalformedError,
} from '../../helpers/errors';
import * as TargetInput from '../../helpers/target-input';

export default class AppCreate extends Command<typeof AppCreate> {
static description = 'create an app deployment';
Expand All @@ -28,6 +31,12 @@ export default class AppCreate extends Command<typeof AppCreate> {
description: 'app version',
required: true,
}),
target: Flags.string({
description:
'The target in which the app deployment will be created.' +
' 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").',
}),
};

static args = {
Expand Down Expand Up @@ -66,6 +75,15 @@ export default class AppCreate extends Command<typeof AppCreate> {
throw new MissingRegistryTokenError();
}

let target: GraphQLSchema.TargetReferenceInput | null = null;
if (flags.target) {
const result = TargetInput.parse(flags.target);
if (result.type === 'error') {
throw new InvalidTargetError();
}
target = result.data;
}

const file: string = args.file;
const contents = this.readJSON(file);
const operations: unknown = JSON.parse(contents);
Expand All @@ -81,6 +99,7 @@ export default class AppCreate extends Command<typeof AppCreate> {
input: {
appName: flags['name'],
appVersion: flags['version'],
target,
},
},
});
Expand Down Expand Up @@ -108,6 +127,7 @@ export default class AppCreate extends Command<typeof AppCreate> {
operation: AddDocumentsToAppDeploymentMutation,
variables: {
input: {
target,
appName: flags['name'],
appVersion: flags['version'],
documents: buffer,
Expand Down
25 changes: 24 additions & 1 deletion packages/libraries/cli/src/commands/app/publish.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { Flags } from '@oclif/core';
import Command from '../../base-command';
import { graphql } from '../../gql';
import * as GraphQLSchema from '../../gql/graphql';
import { graphqlEndpoint } from '../../helpers/config';
import { APIError, MissingEndpointError, MissingRegistryTokenError } from '../../helpers/errors';
import {
APIError,
InvalidTargetError,
MissingEndpointError,
MissingRegistryTokenError,
} from '../../helpers/errors';
import * as TargetInput from '../../helpers/target-input';

export default class AppPublish extends Command<typeof AppPublish> {
static description = 'publish an app deployment';
Expand All @@ -21,6 +28,12 @@ export default class AppPublish extends Command<typeof AppPublish> {
description: 'app version',
required: true,
}),
target: Flags.string({
description:
'The target in which the app deployment will be published (slug or ID).' +
' 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").',
}),
};

async run() {
Expand Down Expand Up @@ -50,10 +63,20 @@ export default class AppPublish extends Command<typeof AppPublish> {
throw new MissingRegistryTokenError();
}

let target: GraphQLSchema.TargetReferenceInput | null = null;
if (flags.target) {
const result = TargetInput.parse(flags.target);
if (result.type === 'error') {
throw new InvalidTargetError();
}
target = result.data;
}

const result = await this.registryApi(endpoint, accessToken).request({
operation: ActivateAppDeploymentMutation,
variables: {
input: {
target,
appName: flags['name'],
appVersion: flags['version'],
},
Expand Down
Loading

0 comments on commit 142969f

Please sign in to comment.