Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate client code command #265

Merged
merged 11 commits into from
Sep 25, 2023
Merged
5 changes: 5 additions & 0 deletions .changeset/old-pumpkins-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/backend-cli': minor
---

Add generate graphql-client-code command with mocked implementation
3 changes: 3 additions & 0 deletions .eslint_dictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ export default [
'inheritdoc',
'invokable',
'invoker',
'javascript',
'jsdoc',
'lang',
'lsof',
'lstat',
'mfas',
'mkdtemp',
'modelgen',
'multifactor',
'nodejs',
'npmrc',
Expand All @@ -65,6 +67,7 @@ export default [
'repo',
'resolvers',
'saml',
'scala',
'schema',
'schemas',
'searchable',
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/src/commands/generate/generate_command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Argv, CommandModule } from 'yargs';
import { GenerateConfigCommand } from './config/generate_config_command.js';
import { GenerateGraphqlClientCodeCommand } from './graphql-client-code/generate_graphql_client_code_command.js';

/**
* An entry point for generate command.
Expand All @@ -18,7 +19,10 @@ export class GenerateCommand implements CommandModule {
/**
* Creates top level entry point for generate command.
*/
constructor(private readonly generateConfigCommand: GenerateConfigCommand) {
constructor(
private readonly generateConfigCommand: GenerateConfigCommand,
private readonly generateGraphqlClientCodeCommand: GenerateGraphqlClientCodeCommand
) {
this.command = 'generate';
this.describe = 'Generates post deployment artifacts';
}
Expand All @@ -38,6 +42,9 @@ export class GenerateCommand implements CommandModule {
yargs
// Cast to erase options types used in internal sub command implementation. Otherwise, compiler fails here.
.command(this.generateConfigCommand as unknown as CommandModule)
.command(
this.generateGraphqlClientCodeCommand as unknown as CommandModule
)
.demandCommand()
.strictCommands()
.recommendCommands()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ describe('top level generate command', () => {
const parser = yargs().command(generateCommand);
const commandRunner = new TestCommandRunner(parser);

it('includes generate config in help output', async () => {
it('includes generate config and graphql-client-code in help output', async () => {
alharris-at marked this conversation as resolved.
Show resolved Hide resolved
const output = await commandRunner.runCommand('generate --help');
assert.match(output, /Commands:/);
assert.match(output, /generate config {2}Generates client config/);
assert.match(output, /generate config\W*Generates client config/);
assert.match(
output,
/generate graphql-client-code\W*Generates graphql API code/
);
});

it('fails if subcommand is not provided', async () => {
Expand Down
15 changes: 14 additions & 1 deletion packages/cli/src/commands/generate/generate_command_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { ClientConfigGeneratorAdapter } from './config/client_config_generator_adapter.js';
import { LocalAppNameResolver } from '../../local_app_name_resolver.js';
import { CwdPackageJsonLoader } from '../../cwd_package_json_loader.js';
import { GenerateGraphqlClientCodeCommand } from './graphql-client-code/generate_graphql_client_code_command.js';
import { GraphqlClientCodeGeneratorAdapter } from './graphql-client-code/generate_graphql_client_code_generator_adapter.js';

/**
* Creates wired generate command.
Expand All @@ -23,5 +25,16 @@ export const createGenerateCommand = (): CommandModule => {
localAppNameResolver
);

return new GenerateCommand(generateConfigCommand);
const graphqlClientCodeGeneratorAdapter =
new GraphqlClientCodeGeneratorAdapter(credentialProvider);

const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand(
graphqlClientCodeGeneratorAdapter,
localAppNameResolver
);

return new GenerateCommand(
generateConfigCommand,
generateGraphqlClientCodeCommand
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { beforeEach, describe, it, mock } from 'node:test';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { GenerateGraphqlClientCodeCommand } from './generate_graphql_client_code_command.js';
import yargs, { CommandModule } from 'yargs';
import {
TestCommandError,
TestCommandRunner,
} from '../../../test-utils/command_runner.js';
import assert from 'node:assert';
import path from 'path';
import { GraphqlClientCodeGeneratorAdapter } from './generate_graphql_client_code_generator_adapter.js';

describe('generate graphql-client-code command', () => {
const graphqlClientCodeGeneratorAdapter =
new GraphqlClientCodeGeneratorAdapter(fromNodeProviderChain());

const generateClientConfigMock = mock.method(
graphqlClientCodeGeneratorAdapter,
'generateGraphqlClientCodeToFile',
() => Promise.resolve()
);

const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand(
graphqlClientCodeGeneratorAdapter,
{ resolve: () => Promise.resolve('testAppName') }
);
const parser = yargs().command(
generateGraphqlClientCodeCommand as unknown as CommandModule
);
const commandRunner = new TestCommandRunner(parser);

beforeEach(() => {
generateClientConfigMock.mock.resetCalls();
});

it('generates and writes graphql client code for stack', async () => {
await commandRunner.runCommand('graphql-client-code --stack stack_name');
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
stackName: 'stack_name',
format: 'graphql-codegen',
out: process.cwd(),
statementTarget: 'javascript',
});
});

it('generates and writes graphql client code for branch', async () => {
await commandRunner.runCommand('graphql-client-code --branch branch_name');
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
appName: 'testAppName',
branchName: 'branch_name',
out: process.cwd(),
format: 'graphql-codegen',
statementTarget: 'javascript',
});
});

it('generates and writes graphql client code for appID and branch', async () => {
await commandRunner.runCommand(
'graphql-client-code --branch branch_name --appId app_id'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
backendId: 'app_id',
branchName: 'branch_name',
out: process.cwd(),
format: 'graphql-codegen',
statementTarget: 'javascript',
});
});

it('can generate to custom relative path', async () => {
await commandRunner.runCommand(
'graphql-client-code --stack stack_name --out foo/bar'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
stackName: 'stack_name',
format: 'graphql-codegen',
statementTarget: 'javascript',
out: path.join(process.cwd(), 'foo', 'bar'),
});
});

it('shows available options in help output', async () => {
const output = await commandRunner.runCommand('graphql-client-code --help');
assert.match(output, /--stack/);
assert.match(output, /--appId/);
assert.match(output, /--branch/);
assert.match(output, /--format/);
assert.match(output, /--statementTarget/);
assert.match(output, /--typeTarget/);
assert.match(output, /--modelTarget/);
assert.match(output, /--out/);
});

it('can be invoked explicitly with graphql-codegen format', async () => {
await commandRunner.runCommand(
'graphql-client-code --stack stack_name --format graphql-codegen'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
stackName: 'stack_name',
format: 'graphql-codegen',
statementTarget: 'javascript',
out: process.cwd(),
});
});

it('can be invoked explicitly with modelgen format', async () => {
await commandRunner.runCommand(
'graphql-client-code --stack stack_name --format modelgen'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
stackName: 'stack_name',
format: 'modelgen',
modelTarget: 'javascript',
out: process.cwd(),
});
});

it('can be invoked explicitly with introspection format', async () => {
await commandRunner.runCommand(
'graphql-client-code --stack stack_name --format introspection'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
stackName: 'stack_name',
format: 'introspection',
out: process.cwd(),
});
});

// Note: after this test, future tests seem to be in a weird state, leaving this at the
it('fails if both stack and branch are present', async () => {
await assert.rejects(
() =>
commandRunner.runCommand(
'graphql-client-code --stack foo --branch baz'
),
(err: TestCommandError) => {
assert.equal(err.error.name, 'YError');
assert.match(err.error.message, /Arguments .* mutually exclusive/);
assert.match(err.output, /Arguments .* are mutually exclusive/);
return true;
}
);
});
});
Loading