Skip to content

Commit

Permalink
chore: initial mockup of command inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
alharris-at committed Sep 21, 2023
1 parent 5ae148c commit e219691
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { beforeEach, describe, it, mock } from 'node:test';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import {
GenerateGraphqlClientCodeCommand,
configFileName,
formatChoices,
} from './generate_graphql_client_code_command.js';
import { GenerateGraphqlClientCodeCommand } from './generate_graphql_client_code_command.js';
import yargs, { CommandModule } from 'yargs';
import {
TestCommandError,
Expand Down Expand Up @@ -42,12 +38,10 @@ describe('generate graphql-client-code command', () => {
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',
});
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(
generateClientConfigMock.mock.calls[0].arguments[1],
path.join(process.cwd(), `${configFileName}.${formatChoices[0]}`)
);
});

it('generates and writes graphql client code for branch', async () => {
Expand All @@ -56,15 +50,10 @@ describe('generate graphql-client-code command', () => {
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
appName: 'testAppName',
branchName: 'branch_name',
out: process.cwd(),
format: 'graphql-codegen',
statementTarget: 'javascript',
});
// I can't find any open node:test or yargs issues that would explain why this is necessary
// but for some reason the mock call count does not update without this 0ms wait
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments[1],
path.join(process.cwd(), `${configFileName}.${formatChoices[0]}`)
);
});

it('generates and writes graphql client code for appID and branch', async () => {
Expand All @@ -75,12 +64,10 @@ describe('generate graphql-client-code command', () => {
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
backendId: 'app_id',
branchName: 'branch_name',
out: process.cwd(),
format: 'graphql-codegen',
statementTarget: 'javascript',
});
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments[1],
path.join(process.cwd(), `${configFileName}.${formatChoices[0]}`)
);
});

it('can generate to custom absolute path', async () => {
Expand All @@ -90,18 +77,10 @@ describe('generate graphql-client-code command', () => {
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], {
stackName: 'stack_name',
out: path.join('/', 'foo', 'bar'),
format: 'graphql-codegen',
statementTarget: 'javascript',
});
assert.equal(generateClientConfigMock.mock.callCount(), 1);
const actualPath = generateClientConfigMock.mock.calls[0].arguments[1];
// normalize the path root across unix and windows platforms
const normalizedPath = actualPath?.replace(
path.parse(actualPath).root,
path.sep
);
assert.equal(
normalizedPath,
path.join('/', 'foo', 'bar', `${configFileName}.${formatChoices[2]}`)
);
});

it('can generate to custom relative path', async () => {
Expand All @@ -111,12 +90,10 @@ describe('generate graphql-client-code command', () => {
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'),
});
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.equal(
generateClientConfigMock.mock.calls[0].arguments[1],
path.join(process.cwd(), 'foo', 'bar', 'amplifyconfiguration.js')
);
});

it('shows available options in help output', async () => {
Expand All @@ -125,8 +102,9 @@ describe('generate graphql-client-code command', () => {
assert.match(output, /--appId/);
assert.match(output, /--branch/);
assert.match(output, /--format/);
assert.match(output, /--platform/);
assert.match(output, /--target/);
assert.match(output, /--statementTarget/);
assert.match(output, /--typeTarget/);
assert.match(output, /--modelTarget/);
assert.match(output, /--out/);
});

Expand All @@ -144,4 +122,36 @@ describe('generate graphql-client-code command', () => {
}
);
});

// 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',
// statementType: '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',
// modelType: '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(),
// });
// });
});
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
import path from 'path';
import { BackendIdentifier } from '@aws-amplify/client-config';
import { AppNameResolver } from '../../../local_app_name_resolver.js';
import { GraphqlClientCodeGeneratorAdapter } from './generate_graphql_client_code_generator_adapter.js';
import { isAbsolute, resolve } from 'path';

export const formatChoices = ['amplify-codegen', 'introspection', 'modelgen'];
export const platformChoices = ['js', 'ts', 'dart', 'android', 'swift'];
export const configFileName = 'amplifyconfiguration';

export const modelgenTargetChoices = ['java', 'swift', 'javascript', 'typescript', 'dart', 'introspection'];
export const statementsTargetChoices = ['javascript', 'graphql', 'flow', 'typescript', 'angular']
export const typesTargetChoice = ['json', 'swift', 'ts', 'typescript', 'flow', 'scala', 'flow-modern', 'angular']
export const targetChoices = ['javascript', 'java', 'swift', 'typescript', 'dart', 'introspection', 'graphql', 'flow', 'angular', 'json', 'ts', 'scala', 'flow-modern'];
export const modelgenTargetChoices = [
'java',
'swift',
'javascript',
'typescript',
'dart',
];
export const statementsTargetChoices = [
'javascript',
'graphql',
'flow',
'typescript',
'angular',
];
export const typesTargetChoice = [
'json',
'swift',
'ts',
'typescript',
'flow',
'scala',
'flow-modern',
'angular',
];

export type GenerateGraphqlClientCodeCommandOptions = {
stack: string | undefined;
appId: string | undefined;
branch: string | undefined;
format: (typeof formatChoices)[number] | undefined;
platform: (typeof platformChoices)[number] | undefined;
target: (typeof targetChoices)[number] | undefined;
modelTarget: (typeof modelgenTargetChoices)[number] | undefined;
statementTarget: (typeof statementsTargetChoices)[number] | undefined;
typeTarget: (typeof typesTargetChoice)[number] | undefined;
out: string | undefined;
};

Expand Down Expand Up @@ -54,37 +72,55 @@ export class GenerateGraphqlClientCodeCommand
this.describe = 'Generates graphql API code';
}

private getTargetParts = (
format: string,
args: ArgumentsCamelCase<GenerateGraphqlClientCodeCommandOptions>
): Record<string, string | undefined> => {
switch (format) {
case 'graphql-codegen':
return {
statementTarget: args.statementTarget ?? 'javascript',
...(args.typeTarget ? { typeTarget: args.typeTarget } : {}),
};
case 'modelgen':
return {
modelTarget: args.modelTarget ?? 'javascript',
};
case 'introspection':
return {};
default:
throw new Error(`Unexpected format ${format} received`);
}
};

private getOutDir = (
args: ArgumentsCamelCase<GenerateGraphqlClientCodeCommandOptions>
) => {
const cwd = process.cwd();
if (!args.out) {
return cwd;
}
return isAbsolute(args.out) ? args.out : resolve(cwd, args.out);
};

/**
* @inheritDoc
*/
handler = async (
args: ArgumentsCamelCase<GenerateGraphqlClientCodeCommandOptions>
): Promise<void> => {
const defaultArgs = {
out: process.cwd(),
format: 'amplify-codegen',
platform: 'js',
target: 'javascript',
};
const backendIdentifier = await this.getBackendIdentifier(args);

let targetPath: string;
if (args.out) {
targetPath = path.isAbsolute(args.out)
? args.out
: path.resolve(process.cwd(), args.out);
} else {
targetPath = defaultArgs.out;
}

targetPath = path.resolve(
targetPath,
`${configFileName}.${args.format || defaultArgs.format}`
);
const backendIdentifierParts = await this.getBackendIdentifier(args);
const out = this.getOutDir(args);
const format = args.format ?? ('graphql-codegen' as unknown as any);
const targetParts = this.getTargetParts(format, args);

await this.graphqlClientCodeGeneratorAdapter.generateGraphqlClientCodeToFile(
backendIdentifier,
targetPath
{
...backendIdentifierParts,
out,
format,
...targetParts,
}
);
};

Expand Down Expand Up @@ -135,28 +171,36 @@ export class GenerateGraphqlClientCodeCommand
array: false,
group: 'Project identifier',
})
.option('out', {
describe:
'A path to directory where config is written. If not provided defaults to current process working directory.',
type: 'string',
array: false,
})
.option('format', {
describe: 'The format that the GraphQL client code should be generated in.',
describe:
'The format that the GraphQL client code should be generated in.',
type: 'string',
array: false,
choices: formatChoices,
})
.option('platform', {
describe: 'The platform for which the configuration should be exported for.',
.option('modelTarget', {
describe: 'TK',
type: 'string',
array: false,
choices: platformChoices,
choices: modelgenTargetChoices,
})
.option('target', {
describe: 'The platform for which the configuration should be exported for.',
.option('statementTarget', {
describe: 'TK',
type: 'string',
array: false,
choices: targetChoices,
choices: statementsTargetChoices,
})
.option('out', {
describe: 'A path to directory where config is written. If not provided defaults to current process working directory.',
.option('typeTarget', {
describe: 'TK',
type: 'string',
array: false,
choices: typesTargetChoice,
})
.check((argv) => {
if (!argv.stack && !argv.branch) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import {
BackendIdentifier,
ClientConfig,
generateClientConfig,
generateClientConfigToFile,
} from '@aws-amplify/client-config';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import {
GenerateAPICodeProps,
GeneratedOutput,
generateAPICode,
} from './mock_code_generator.js';

export type GenerateGraphqlClientCodeProps = GenerateAPICodeProps;
export type GenerateGraphqlClientCodeToFileProps =
GenerateGraphqlClientCodeProps & {
out: string;
};

/**
* Adapts static generateClientConfigToFile from @aws-amplify/client-config call to make it injectable and testable.
* Adapts wraps calls to the code generator, and generates credentials providers, etc.
*/
export class GraphqlClientCodeGeneratorAdapter {
/**
Expand All @@ -20,23 +27,29 @@ export class GraphqlClientCodeGeneratorAdapter {
/**
* Generates the platform-specific graphql client code for a given backend
*/
generateGraphqlClientCode = async (
backendIdentifier: BackendIdentifier
): Promise<ClientConfig> => {
return generateClientConfig(this.awsCredentialProvider, backendIdentifier);
};
generateGraphqlClientCode = (
props: GenerateGraphqlClientCodeProps
): Promise<GeneratedOutput> =>
generateAPICode({
...props,
// credentialProvider: this.awsCredentialProvider,
});

/**
* Generates the platform-specific graphql client code for a given backend, and write the outputs to the specified target.
*/
generateGraphqlClientCodeToFile = async (
backendIdentifier: BackendIdentifier,
targetPath: string
props: GenerateGraphqlClientCodeToFileProps
): Promise<void> => {
await generateClientConfigToFile(
this.awsCredentialProvider,
backendIdentifier,
targetPath
);
const { out, ...rest } = props;

const generatedCode = await generateAPICode({
...rest,
// credentialProvider: this.awsCredentialProvider,
});

Object.entries(generatedCode).forEach(([filePath, fileContents]) => {
writeFileSync(join(out, filePath), fileContents);
});
};
}
Loading

0 comments on commit e219691

Please sign in to comment.