Skip to content

Commit

Permalink
do not repeat dev interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Dec 19, 2024
1 parent 5489204 commit 8b23d0e
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 150 deletions.
3 changes: 2 additions & 1 deletion integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
"@aws-sdk/client-s3": "3.693.0",
"@esm2cjs/execa": "6.1.1-cjs.1",
"@graphql-hive/apollo": "workspace:*",
"@graphql-hive/cli": "workspace:*",
"@graphql-hive/core": "workspace:*",
"@graphql-typed-document-node/core": "3.2.0",
"@hive/rate-limit": "workspace:*",
"@hive/cli": "workspace:*",
"@hive/schema": "workspace:*",
"@hive/server": "workspace:*",
"@hive/storage": "workspace:*",
"@sinclair/typebox": "^0.34.12",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"@types/async-retry": "1.4.8",
Expand Down
56 changes: 11 additions & 45 deletions integration-tests/testkit/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { execaCommand } from '@esm2cjs/execa';
import { HiveCLI } from '@graphql-hive/cli';
import { Static } from '@sinclair/typebox';
import { fetchLatestSchema, fetchLatestValidSchema } from './flow';
import { getServiceHost } from './utils';

Expand Down Expand Up @@ -237,58 +238,23 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) {
return cmd;
}

async function devCmd(input: {
services: Array<{
name: string;
url: string;
sdl: string;
}>;
remote: boolean;
write?: string;
useLatestVersion?: boolean;
}) {
const servicesFlags = await Promise.all(
input.services.map(async ({ name, url, sdl }) => {
return {
service: name,
url,
schema: await generateTmpFile(sdl, 'graphql'),
};
}),
).then(argsGroups => {
return argsGroups.reduce(
(argsAcc, args) => {
return {
service: [...argsAcc.service, args.service],
url: [...argsAcc.url, args.url],
schema: [...argsAcc.schema, args.schema],
};
},
{ service: [], url: [], schema: [] } as {
service: string[];
url: string[];
schema: string[];
},
);
});
const remoteFlags = input.remote
? {
remote: true,
'registry.accessToken': tokens.readonly,
unstable__forceLatest: input.useLatestVersion,
}
: {};
async function devCommand(args: Static<typeof HiveCLI.Commands.Dev.parameters.named>) {
const schema = await Promise.all(
args.schema?.map(async schema => generateTmpFile(schema, 'graphql')) ?? [],
);
const accessToken = args.remote ? tokens.readonly : undefined;

return hiveCLI.dev({
write: input.write,
...remoteFlags,
...servicesFlags,
...args,
'registry.accessToken': accessToken,
schema,
});
}

return {
publish,
check,
delete: deleteCommand,
dev: devCmd,
dev: devCommand,
};
}
139 changes: 58 additions & 81 deletions integration-tests/tests/cli/dev.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,41 @@ describe('dev', () => {
});

const supergraph = tmpFile('graphql');
const cmd = cli.dev({
const running = cli.dev({
remote: false,
services: [
{
name: 'bar',
url: 'http://localhost/bar',
sdl: 'type Query { bar: String }',
},
],
service: ['bar'],
url: ['http://localhost/bar'],
schema: ['type Query { bar: String }'],
write: supergraph.filepath,
});

await expect(cmd).resolves.toMatch(supergraph.filepath);
await expect(running).resolves.toMatch(supergraph.filepath);
await expect(supergraph.read()).resolves.toMatch('http://localhost/bar');
await expect(supergraph.read()).resolves.not.toMatch('http://localhost/foo');
});
});

describe('dev --remote', () => {
test('not available for SINGLE project', async ({ cliSingle: cli }) => {
const cmd = cli.dev({
const running = cli.dev({
remote: true,
services: [
{
name: 'foo',
url: 'http://localhost/foo',
sdl: 'type Query { foo: String }',
},
],
service: ['foo'],
url: ['http://localhost/foo'],
schema: ['type Query { foo: String }'],
});

await expect(cmd).rejects.toThrowError(/Only Federation projects are supported/);
await expect(running).rejects.toThrowError(/Only Federation projects are supported/);
});

test('not available for STITCHING project', async ({ cliStitching: cli }) => {
const cmd = cli.dev({
const running = cli.dev({
remote: true,
services: [
{
name: 'foo',
url: 'http://localhost/foo',
sdl: 'type Query { foo: String }',
},
],
service: ['foo'],
url: ['http://localhost/foo'],
schema: ['type Query { foo: String }'],
});

await expect(cmd).rejects.toThrowError(/Only Federation projects are supported/);
await expect(running).rejects.toThrowError(/Only Federation projects are supported/);
});

test('adds a service', async ({ cliFederation: cli }) => {
Expand All @@ -72,13 +60,9 @@ describe('dev --remote', () => {
const supergraph = tmpFile('graphql');
const cmd = cli.dev({
remote: true,
services: [
{
name: 'bar',
url: 'http://localhost/bar',
sdl: 'type Query { bar: String }',
},
],
service: ['bar'],
url: ['http://localhost/bar'],
schema: ['type Query { bar: String }'],
write: supergraph.filepath,
});

Expand All @@ -102,19 +86,15 @@ describe('dev --remote', () => {
});

const supergraph = tmpFile('graphql');
const cmd = cli.dev({
const running = cli.dev({
remote: true,
services: [
{
name: 'bar',
url: 'http://localhost/bar',
sdl: 'type Query { bar: String }',
},
],
service: ['bar'],
url: ['http://localhost/bar'],
schema: ['type Query { bar: String }'],
write: supergraph.filepath,
});

await expect(cmd).resolves.toMatch(supergraph.filepath);
await expect(running).resolves.toMatch(supergraph.filepath);
await expect(supergraph.read()).resolves.toMatch('http://localhost/bar');
});

Expand Down Expand Up @@ -167,23 +147,21 @@ describe('dev --remote', () => {
const supergraph = tmpFile('graphql');
const cmd = cli.dev({
remote: true,
services: [
{
name: 'baz',
url: 'http://localhost/baz',
sdl: /* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
baz: String
}
type User @key(fields: "id") {
id: ID!
baz: String!
}
`,
},
service: ['baz'],
url: ['http://localhost/baz'],
schema: [
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
baz: String
}
type User @key(fields: "id") {
id: ID!
baz: String!
}
`,
],
write: supergraph.filepath,
});
Expand Down Expand Up @@ -241,31 +219,30 @@ describe('dev --remote', () => {
});

const supergraph = tmpFile('graphql');
const cmd = cli.dev({
const running = cli.dev({
remote: true,
useLatestVersion: true,
services: [
{
name: 'baz',
url: 'http://localhost/baz',
sdl: /* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
baz: String
}
type User @key(fields: "id") {
id: ID!
baz: String!
}
`,
},
unstable__forceLatest: true,
service: ['baz'],
url: ['http://localhost/baz'],
schema: [
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
baz: String
}
type User @key(fields: "id") {
id: ID!
baz: String!
}
`,
],
write: supergraph.filepath,
});

// The command should fail because the latest version contains a non-shareable field and we don't override the corrupted subgraph
await expect(cmd).rejects.toThrowError('Non-shareable field');
// The command should fail because the latest version contains a
// non-shareable field and we don't override the corrupted subgraph
await expect(running).rejects.toThrowError('Non-shareable field');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import Whoami from './commands/whoami';
// todo raise issue with respective ESLint lib author about type imports used in JSDoc being marked as "unused"
// eslint-disable-next-line
import type { Infer } from './library/infer';
import { CommandRegistry } from './library/infer';
import { CommandIndexGeneric } from './library/infer';

export const commandRegistry = {
dev: Dev,
export const commandIndex = {
Dev,
whoami: Whoami,
introspect: Introspect,
// app:
Expand All @@ -37,4 +37,6 @@ export const commandRegistry = {
artifactFetch: ArtifactsFetch,
// operations:
operationsCheck: OperationsCheck,
} satisfies CommandRegistry;
} satisfies CommandIndexGeneric;

export type CommandIndex = typeof commandIndex;
4 changes: 4 additions & 0 deletions packages/libraries/cli/src/helpers/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ export const toSnakeCase = (str: string): string => {
.toLowerCase()
);
};

export const uncapitalize = <$String extends string>(str: $String): Uncapitalize<$String> => {
return (str.charAt(0).toLowerCase() + str.slice(1)) as Uncapitalize<$String>;
};
14 changes: 9 additions & 5 deletions packages/libraries/cli/src/library/_.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Command } from '@oclif/core';
import { commandRegistry } from '../command-registry';
import { toSnakeCase } from '../helpers/general';
import { commandIndex } from '../command-index';
import { toSnakeCase, uncapitalize } from '../helpers/general';
import { Args, Infer, renderSubcommandExecution } from './infer';

export { commandIndex as Commands };

export const create = (config: {
/**
* Optional middleware to manipulate various aspects of the CLI.
Expand All @@ -18,11 +20,13 @@ export const create = (config: {
* that invokes the CLI like a real user would.
*/
execute: (command: string) => Promise<string>;
}): Infer<typeof commandRegistry> => {
}): Infer<typeof commandIndex> => {
return Object.fromEntries(
Object.entries(commandRegistry).map(([handle, commandClass]) => {
Object.entries(commandIndex).map(([commandClassName, commandClass]) => {
return [
handle,
// The property name on the CLI object.
uncapitalize(commandClassName),
// The method that runs the command.
async (args: Args) => {
const args_ = (await config.middleware?.args?.(args)) ?? args;
const subCommandPath = inferCommandPath(commandClass);
Expand Down
6 changes: 3 additions & 3 deletions packages/libraries/cli/src/library/infer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import BaseCommand from '../base-command';
import { tb } from '../helpers/typebox/__';

export type Infer<$Commands extends CommandRegistry> = {
[_ in keyof $Commands]: InferFunction<$Commands[_]>;
export type Infer<$Commands extends CommandIndexGeneric> = {
[K in keyof $Commands as K extends string ? Uncapitalize<K> : K]: InferFunction<$Commands[K]>;
};

export type InferFunction<$Command extends typeof BaseCommand<any>> = <
Expand Down Expand Up @@ -58,7 +58,7 @@ export const renderSubcommandExecution = (
return execArgs;
};

export type CommandRegistry = Record<string, typeof BaseCommand<any>>;
export type CommandIndexGeneric = Record<string, typeof BaseCommand<any>>;

export interface Args {
[name: string]: unknown;
Expand Down
Loading

0 comments on commit 8b23d0e

Please sign in to comment.