From dbadc3125cece6524f37c2273ce2e2ebf02ae74c Mon Sep 17 00:00:00 2001 From: Al Harris Date: Fri, 22 Sep 2023 08:57:47 -0700 Subject: [PATCH 1/9] feat: add mocked generate graphql-client-code to cli --- .changeset/old-pumpkins-peel.md | 5 + .eslint_dictionary.js | 3 + .../src/commands/generate/generate_command.ts | 9 +- .../generate/generate_command_factory.test.ts | 8 +- .../generate/generate_command_factory.ts | 15 +- ...nerate_graphql_client_code_command.test.ts | 151 +++++++++++++ .../generate_graphql_client_code_command.ts | 212 ++++++++++++++++++ ...e_graphql_client_code_generator_adapter.ts | 57 +++++ .../mock_code_generator.ts | 74 ++++++ 9 files changed, 530 insertions(+), 4 deletions(-) create mode 100644 .changeset/old-pumpkins-peel.md create mode 100644 packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts create mode 100644 packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts create mode 100644 packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts create mode 100644 packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts diff --git a/.changeset/old-pumpkins-peel.md b/.changeset/old-pumpkins-peel.md new file mode 100644 index 0000000000..2995d29876 --- /dev/null +++ b/.changeset/old-pumpkins-peel.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-cli': minor +--- + +Add generate graphql-client-code command with mocked implementation diff --git a/.eslint_dictionary.js b/.eslint_dictionary.js index 6d0469deb1..ca66dbccb3 100644 --- a/.eslint_dictionary.js +++ b/.eslint_dictionary.js @@ -39,12 +39,14 @@ export default [ 'inheritdoc', 'invokable', 'invoker', + 'javascript', 'jsdoc', 'lang', 'lsof', 'lstat', 'mfas', 'mkdtemp', + 'modelgen', 'multifactor', 'nodejs', 'npmrc', @@ -65,6 +67,7 @@ export default [ 'repo', 'resolvers', 'saml', + 'scala', 'schema', 'schemas', 'searchable', diff --git a/packages/cli/src/commands/generate/generate_command.ts b/packages/cli/src/commands/generate/generate_command.ts index 5b5578acfb..3b42236f9f 100644 --- a/packages/cli/src/commands/generate/generate_command.ts +++ b/packages/cli/src/commands/generate/generate_command.ts @@ -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. @@ -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'; } @@ -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() diff --git a/packages/cli/src/commands/generate/generate_command_factory.test.ts b/packages/cli/src/commands/generate/generate_command_factory.test.ts index 03f54bf83c..f4dd1f710c 100644 --- a/packages/cli/src/commands/generate/generate_command_factory.test.ts +++ b/packages/cli/src/commands/generate/generate_command_factory.test.ts @@ -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 () => { 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 () => { diff --git a/packages/cli/src/commands/generate/generate_command_factory.ts b/packages/cli/src/commands/generate/generate_command_factory.ts index 93cefba263..382609f322 100644 --- a/packages/cli/src/commands/generate/generate_command_factory.ts +++ b/packages/cli/src/commands/generate/generate_command_factory.ts @@ -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. @@ -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 + ); }; diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts new file mode 100644 index 0000000000..66ba2c8590 --- /dev/null +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts @@ -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; + } + ); + }); +}); diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts new file mode 100644 index 0000000000..38530ad294 --- /dev/null +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -0,0 +1,212 @@ +import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; +import { BackendIdentifier } from '@aws-amplify/deployed-backend-client'; +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 = ['graphql-codegen', 'introspection', 'modelgen']; +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; + modelTarget: (typeof modelgenTargetChoices)[number] | undefined; + statementTarget: (typeof statementsTargetChoices)[number] | undefined; + typeTarget: (typeof typesTargetChoice)[number] | undefined; + out: string | undefined; +}; + +/** + * Command that generates client config. + */ +export class GenerateGraphqlClientCodeCommand + implements CommandModule +{ + /** + * @inheritDoc + */ + readonly command: string; + + /** + * @inheritDoc + */ + readonly describe: string; + + private readonly missingArgsError = new Error( + 'Either --stack or --branch must be provided' + ); + + /** + * Creates graphql client code generation command. + */ + constructor( + private readonly graphqlClientCodeGeneratorAdapter: GraphqlClientCodeGeneratorAdapter, + private readonly appNameResolver: AppNameResolver + ) { + this.command = 'graphql-client-code'; + this.describe = 'Generates graphql API code'; + } + + private getTargetParts = ( + format: string, + args: ArgumentsCamelCase + ): Record => { + 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 + ) => { + const cwd = process.cwd(); + if (!args.out) { + return cwd; + } + return isAbsolute(args.out) ? args.out : resolve(cwd, args.out); + }; + + /** + * @inheritDoc + */ + handler = async ( + args: ArgumentsCamelCase + ): Promise => { + 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( + { + ...backendIdentifierParts, + out, + format, + ...targetParts, + } + ); + }; + + /** + * Translates args to BackendIdentifier. + * Throws if translation can't be made (this should never happen if command validation works correctly). + */ + private getBackendIdentifier = async ( + args: ArgumentsCamelCase + ): Promise => { + if (args.stack) { + return { stackName: args.stack }; + } else if (args.appId && args.branch) { + return { backendId: args.appId, branchName: args.branch }; + } else if (args.branch) { + return { + appName: await this.appNameResolver.resolve(), + branchName: args.branch, + }; + } + throw this.missingArgsError; + }; + + /** + * @inheritDoc + */ + builder = (yargs: Argv): Argv => { + return yargs + .option('stack', { + conflicts: ['appId', 'branch'], + describe: 'A stack name that contains an Amplify backend', + type: 'string', + array: false, + group: 'Stack identifier', + }) + .option('appId', { + conflicts: ['stack'], + describe: 'The Amplify App ID of the project', + type: 'string', + array: false, + implies: 'branch', + group: 'Project identifier', + }) + .option('branch', { + conflicts: ['stack'], + describe: 'A git branch of the Amplify project', + type: 'string', + 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.', + type: 'string', + array: false, + choices: formatChoices, + }) + .option('modelTarget', { + describe: 'TK', + type: 'string', + array: false, + choices: modelgenTargetChoices, + }) + .option('statementTarget', { + describe: 'TK', + type: 'string', + array: false, + choices: statementsTargetChoices, + }) + .option('typeTarget', { + describe: 'TK', + type: 'string', + array: false, + choices: typesTargetChoice, + }) + .check((argv) => { + if (!argv.stack && !argv.branch) { + throw this.missingArgsError; + } + return true; + }); + }; +} diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts new file mode 100644 index 0000000000..3ab293be71 --- /dev/null +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts @@ -0,0 +1,57 @@ +import fs from 'fs'; +import path 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 wraps calls to the code generator, and generates credentials providers, etc. + */ +export class GraphqlClientCodeGeneratorAdapter { + /** + * Creates new adapter for generateClientConfigToFile from @aws-amplify/client-config. + */ + constructor( + private readonly awsCredentialProvider: AwsCredentialIdentityProvider + ) {} + + /** + * Generates the platform-specific graphql client code for a given backend + */ + generateGraphqlClientCode = ( + props: GenerateGraphqlClientCodeProps + ): Promise => + 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 ( + props: GenerateGraphqlClientCodeToFileProps + ): Promise => { + const { out, ...rest } = props; + + const generatedCode = await generateAPICode({ + ...rest, + // credentialProvider: this.awsCredentialProvider, + }); + + Object.entries(generatedCode).forEach(([filePathSuffix, fileContents]) => { + const filePath = path.join(out, filePathSuffix); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, fileContents); + }); + }; +} diff --git a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts new file mode 100644 index 0000000000..7e045435a5 --- /dev/null +++ b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts @@ -0,0 +1,74 @@ +import { BackendIdentifier } from '@aws-amplify/deployed-backend-client'; +import path from 'path'; + +export type GeneratedOutput = { [filename: string]: string }; + +export type GenerateModelsOptions = { + format: 'modelgen'; + modelTarget: 'java' | 'swift' | 'javascript' | 'typescript' | 'dart'; + generateIndexRules?: boolean; + emitAuthProvider?: boolean; + useExperimentalPipelinedTransformer?: boolean; + transformerVersion?: boolean; + respectPrimaryKeyAttributesOnConnectionField?: boolean; + generateModelsForLazyLoadAndCustomSelectionSet?: boolean; + addTimestampFields?: boolean; + handleListNullabilityTransparently?: boolean; +}; + +export type GenerateGraphqlCodegenOptions = { + format: 'graphql-codegen'; + statementTarget: 'javascript' | 'graphql' | 'flow' | 'typescript' | 'angular'; + maxDepth?: number; + typenameIntrospection?: boolean; + typeTarget?: + | 'json' + | 'swift' + | 'typescript' + | 'flow' + | 'scala' + | 'flow-modern' + | 'angular'; + multipleSwiftFiles?: boolean; +}; + +export type GenerateIntrospectionOptions = { + format: 'introspection'; +}; + +export type GenerateOptions = + | GenerateGraphqlCodegenOptions + | GenerateModelsOptions + | GenerateIntrospectionOptions; + +export type GenerateAPICodeProps = BackendIdentifier & GenerateOptions; + +/** + * Mock generateApiCode command. + */ +export const generateAPICode = async ( + props: GenerateAPICodeProps +): Promise => { + switch (props.format) { + case 'graphql-codegen': + return { + [path.join('src', 'graphql', 'mutations.js')]: 'type Mutations {}', + [path.join('src', 'graphql', 'queries.js')]: 'type Queries {}', + [path.join('src', 'graphql', 'subscriptions.js')]: + 'type Subscriptions {}', + }; + case 'modelgen': + return { + [path.join('src', 'models', 'index.js')]: 'export me', + [path.join('src', 'models', 'models.js')]: 'im a models', + }; + case 'introspection': + return { + 'model-introspection-schema.json': JSON.stringify( + { version: 1, models: [], nonModels: [] }, + null, + 4 + ), + }; + } +}; From 4a696486a01a290ee4ef5cbd92263766fec5ee79 Mon Sep 17 00:00:00 2001 From: Al Harris <91494052+alharris-at@users.noreply.github.com> Date: Fri, 22 Sep 2023 09:58:37 -0700 Subject: [PATCH 2/9] Update packages/cli/src/commands/generate/generate_command_factory.test.ts fix: update test name Co-authored-by: Kamil Sobol --- .../cli/src/commands/generate/generate_command_factory.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/generate/generate_command_factory.test.ts b/packages/cli/src/commands/generate/generate_command_factory.test.ts index f4dd1f710c..64d73ab6c4 100644 --- a/packages/cli/src/commands/generate/generate_command_factory.test.ts +++ b/packages/cli/src/commands/generate/generate_command_factory.test.ts @@ -16,7 +16,7 @@ describe('top level generate command', () => { const parser = yargs().command(generateCommand); const commandRunner = new TestCommandRunner(parser); - it('includes generate config and graphql-client-code in help output', async () => { + it('includes generate subcommands in help output', async () => { const output = await commandRunner.runCommand('generate --help'); assert.match(output, /Commands:/); assert.match(output, /generate config\W*Generates client config/); From eaf2837fc1e01aeb1c22df9294d4f3f58c804da3 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Fri, 22 Sep 2023 13:12:16 -0700 Subject: [PATCH 3/9] chore: add missing descriptions for targets, use new shared backendIdentifierResolver --- package-lock.json | 6 +-- .../generate/generate_command_factory.ts | 2 +- ...nerate_graphql_client_code_command.test.ts | 38 +++++++++++++----- .../generate_graphql_client_code_command.ts | 40 ++++++------------- .../mock_code_generator.ts | 4 +- 5 files changed, 47 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49a00bf1f5..49b5154865 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18500,10 +18500,12 @@ }, "packages/cli": { "name": "@aws-amplify/backend-cli", - "version": "1.0.0-alpha.5", + "version": "0.2.0-alpha.3", "license": "Apache-2.0", "dependencies": { + "@aws-amplify/client-config": "^0.2.0-alpha.5", "@aws-amplify/deployed-backend-client": "^0.1.0", + "@aws-amplify/sandbox": "^0.1.1-alpha.5", "@aws-sdk/credential-providers": "^3.360.0", "@inquirer/prompts": "^3.0.0", "execa": "^7.2.0", @@ -18521,8 +18523,6 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-amplify/client-config": "^0.2.0-alpha.5", - "@aws-amplify/sandbox": "^0.1.1-alpha.5", "@aws-sdk/types": "^3.347.0" } }, diff --git a/packages/cli/src/commands/generate/generate_command_factory.ts b/packages/cli/src/commands/generate/generate_command_factory.ts index e077d0151c..87476367be 100644 --- a/packages/cli/src/commands/generate/generate_command_factory.ts +++ b/packages/cli/src/commands/generate/generate_command_factory.ts @@ -35,7 +35,7 @@ export const createGenerateCommand = (): CommandModule => { const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand( graphqlClientCodeGeneratorAdapter, - localAppNameResolver + backendIdentifierResolver ); return new GenerateCommand( diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts index 66ba2c8590..6da030708a 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts @@ -9,6 +9,7 @@ import { import assert from 'node:assert'; import path from 'path'; import { GraphqlClientCodeGeneratorAdapter } from './generate_graphql_client_code_generator_adapter.js'; +import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; describe('generate graphql-client-code command', () => { const graphqlClientCodeGeneratorAdapter = @@ -20,9 +21,12 @@ describe('generate graphql-client-code command', () => { () => Promise.resolve() ); + const backendIdentifierResolver = new BackendIdentifierResolver({ + resolve: () => Promise.resolve('testAppName'), + }); const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand( graphqlClientCodeGeneratorAdapter, - { resolve: () => Promise.resolve('testAppName') } + backendIdentifierResolver ); const parser = yargs().command( generateGraphqlClientCodeCommand as unknown as CommandModule @@ -37,7 +41,9 @@ describe('generate graphql-client-code command', () => { 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', + backendIdentifier: { + stackName: 'stack_name', + }, format: 'graphql-codegen', out: process.cwd(), statementTarget: 'javascript', @@ -48,8 +54,10 @@ describe('generate graphql-client-code command', () => { 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', + backendIdentifier: { + appName: 'testAppName', + branchName: 'branch_name', + }, out: process.cwd(), format: 'graphql-codegen', statementTarget: 'javascript', @@ -62,8 +70,10 @@ describe('generate graphql-client-code command', () => { ); assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], { - backendId: 'app_id', - branchName: 'branch_name', + backendIdentifier: { + backendId: 'app_id', + branchName: 'branch_name', + }, out: process.cwd(), format: 'graphql-codegen', statementTarget: 'javascript', @@ -76,7 +86,9 @@ describe('generate graphql-client-code command', () => { ); assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], { - stackName: 'stack_name', + backendIdentifier: { + stackName: 'stack_name', + }, format: 'graphql-codegen', statementTarget: 'javascript', out: path.join(process.cwd(), 'foo', 'bar'), @@ -101,7 +113,9 @@ describe('generate graphql-client-code command', () => { ); assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], { - stackName: 'stack_name', + backendIdentifier: { + stackName: 'stack_name', + }, format: 'graphql-codegen', statementTarget: 'javascript', out: process.cwd(), @@ -114,7 +128,9 @@ describe('generate graphql-client-code command', () => { ); assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], { - stackName: 'stack_name', + backendIdentifier: { + stackName: 'stack_name', + }, format: 'modelgen', modelTarget: 'javascript', out: process.cwd(), @@ -127,7 +143,9 @@ describe('generate graphql-client-code command', () => { ); assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual(generateClientConfigMock.mock.calls[0].arguments[0], { - stackName: 'stack_name', + backendIdentifier: { + stackName: 'stack_name', + }, format: 'introspection', out: process.cwd(), }); diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index 38530ad294..91206b2f07 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -1,6 +1,5 @@ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; -import { BackendIdentifier } from '@aws-amplify/deployed-backend-client'; -import { AppNameResolver } from '../../../local_app_name_resolver.js'; +import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; import { GraphqlClientCodeGeneratorAdapter } from './generate_graphql_client_code_generator_adapter.js'; import { isAbsolute, resolve } from 'path'; @@ -66,7 +65,7 @@ export class GenerateGraphqlClientCodeCommand */ constructor( private readonly graphqlClientCodeGeneratorAdapter: GraphqlClientCodeGeneratorAdapter, - private readonly appNameResolver: AppNameResolver + private readonly backendIdentifierResolver: BackendIdentifierResolver ) { this.command = 'graphql-client-code'; this.describe = 'Generates graphql API code'; @@ -109,14 +108,16 @@ export class GenerateGraphqlClientCodeCommand handler = async ( args: ArgumentsCamelCase ): Promise => { - const backendIdentifierParts = await this.getBackendIdentifier(args); + const backendIdentifier = await this.backendIdentifierResolver.resolve( + 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( { - ...backendIdentifierParts, + backendIdentifier, out, format, ...targetParts, @@ -124,26 +125,6 @@ export class GenerateGraphqlClientCodeCommand ); }; - /** - * Translates args to BackendIdentifier. - * Throws if translation can't be made (this should never happen if command validation works correctly). - */ - private getBackendIdentifier = async ( - args: ArgumentsCamelCase - ): Promise => { - if (args.stack) { - return { stackName: args.stack }; - } else if (args.appId && args.branch) { - return { backendId: args.appId, branchName: args.branch }; - } else if (args.branch) { - return { - appName: await this.appNameResolver.resolve(), - branchName: args.branch, - }; - } - throw this.missingArgsError; - }; - /** * @inheritDoc */ @@ -185,19 +166,22 @@ export class GenerateGraphqlClientCodeCommand choices: formatChoices, }) .option('modelTarget', { - describe: 'TK', + describe: + 'The modelgen export target. Only applies when the `--format` parameter is set to `modelgen`', type: 'string', array: false, choices: modelgenTargetChoices, }) .option('statementTarget', { - describe: 'TK', + describe: + 'The graphql-codegen statement export target. Only applies when the `--format` parameter is set to `graphql-codegen`', type: 'string', array: false, choices: statementsTargetChoices, }) .option('typeTarget', { - describe: 'TK', + describe: + 'The optional graphql-codegen type export target. Only applies when the `--format` parameter is set to `graphql-codegen`', type: 'string', array: false, choices: typesTargetChoice, diff --git a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts index 7e045435a5..503348172f 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts @@ -41,7 +41,9 @@ export type GenerateOptions = | GenerateModelsOptions | GenerateIntrospectionOptions; -export type GenerateAPICodeProps = BackendIdentifier & GenerateOptions; +export type GenerateAPICodeProps = GenerateOptions & { + backendIdentifier: BackendIdentifier; +}; /** * Mock generateApiCode command. From 35ed3b82a0ce7094eb623c24eab2b583ebccc7e8 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Fri, 22 Sep 2023 13:42:35 -0700 Subject: [PATCH 4/9] chore: remove adapter, we may need to reintroduce in the future, but simplifying until we have an impl in place --- .../generate/generate_command_factory.ts | 7 +- ...nerate_graphql_client_code_command.test.ts | 43 +++++---- .../generate_graphql_client_code_command.ts | 18 ++-- ...e_graphql_client_code_generator_adapter.ts | 57 ------------ .../mock_code_generator.ts | 90 +++++++++++++------ 5 files changed, 95 insertions(+), 120 deletions(-) delete mode 100644 packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts diff --git a/packages/cli/src/commands/generate/generate_command_factory.ts b/packages/cli/src/commands/generate/generate_command_factory.ts index 87476367be..ad8da52346 100644 --- a/packages/cli/src/commands/generate/generate_command_factory.ts +++ b/packages/cli/src/commands/generate/generate_command_factory.ts @@ -5,9 +5,9 @@ import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { ClientConfigGeneratorAdapter } from './config/client_config_generator_adapter.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'; import { LocalAppNameResolver } from '../../backend-identifier/local_app_name_resolver.js'; import { BackendIdentifierResolver } from '../../backend-identifier/backend_identifier_resolver.js'; +import { ApiCodeGenerator } from './graphql-client-code/mock_code_generator.js'; /** * Creates wired generate command. @@ -30,11 +30,10 @@ export const createGenerateCommand = (): CommandModule => { backendIdentifierResolver ); - const graphqlClientCodeGeneratorAdapter = - new GraphqlClientCodeGeneratorAdapter(credentialProvider); + const apiCodeGenerator = new ApiCodeGenerator(credentialProvider); const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand( - graphqlClientCodeGeneratorAdapter, + apiCodeGenerator, backendIdentifierResolver ); diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts index 6da030708a..02d22fe25b 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts @@ -8,16 +8,15 @@ import { } 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'; import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; +import { ApiCodeGenerator } from './mock_code_generator.js'; describe('generate graphql-client-code command', () => { - const graphqlClientCodeGeneratorAdapter = - new GraphqlClientCodeGeneratorAdapter(fromNodeProviderChain()); + const apiCodeGenerator = new ApiCodeGenerator(fromNodeProviderChain()); - const generateClientConfigMock = mock.method( - graphqlClientCodeGeneratorAdapter, - 'generateGraphqlClientCodeToFile', + const codeGeneratorMock = mock.method( + apiCodeGenerator, + 'generateAPICodeToFile', () => Promise.resolve() ); @@ -25,7 +24,7 @@ describe('generate graphql-client-code command', () => { resolve: () => Promise.resolve('testAppName'), }); const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand( - graphqlClientCodeGeneratorAdapter, + apiCodeGenerator, backendIdentifierResolver ); const parser = yargs().command( @@ -34,13 +33,13 @@ describe('generate graphql-client-code command', () => { const commandRunner = new TestCommandRunner(parser); beforeEach(() => { - generateClientConfigMock.mock.resetCalls(); + codeGeneratorMock.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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { stackName: 'stack_name', }, @@ -52,8 +51,8 @@ describe('generate graphql-client-code command', () => { 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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { appName: 'testAppName', branchName: 'branch_name', @@ -68,8 +67,8 @@ describe('generate graphql-client-code command', () => { 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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { backendId: 'app_id', branchName: 'branch_name', @@ -84,8 +83,8 @@ describe('generate graphql-client-code command', () => { 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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { stackName: 'stack_name', }, @@ -111,8 +110,8 @@ describe('generate graphql-client-code command', () => { 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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { stackName: 'stack_name', }, @@ -126,8 +125,8 @@ describe('generate graphql-client-code command', () => { 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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { stackName: 'stack_name', }, @@ -141,8 +140,8 @@ describe('generate graphql-client-code command', () => { 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], { + assert.equal(codeGeneratorMock.mock.callCount(), 1); + assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { backendIdentifier: { stackName: 'stack_name', }, diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index 91206b2f07..b2a3b71a49 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -1,6 +1,6 @@ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; -import { GraphqlClientCodeGeneratorAdapter } from './generate_graphql_client_code_generator_adapter.js'; +import { ApiCodeGenerator } from './mock_code_generator.js'; import { isAbsolute, resolve } from 'path'; export const formatChoices = ['graphql-codegen', 'introspection', 'modelgen']; @@ -64,7 +64,7 @@ export class GenerateGraphqlClientCodeCommand * Creates graphql client code generation command. */ constructor( - private readonly graphqlClientCodeGeneratorAdapter: GraphqlClientCodeGeneratorAdapter, + private readonly apiCodeGenerator: ApiCodeGenerator, private readonly backendIdentifierResolver: BackendIdentifierResolver ) { this.command = 'graphql-client-code'; @@ -115,14 +115,12 @@ export class GenerateGraphqlClientCodeCommand const format = args.format ?? ('graphql-codegen' as unknown as any); const targetParts = this.getTargetParts(format, args); - await this.graphqlClientCodeGeneratorAdapter.generateGraphqlClientCodeToFile( - { - backendIdentifier, - out, - format, - ...targetParts, - } - ); + await this.apiCodeGenerator.generateAPICodeToFile({ + backendIdentifier, + out, + format, + ...targetParts, + }); }; /** diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts deleted file mode 100644 index 3ab293be71..0000000000 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_generator_adapter.ts +++ /dev/null @@ -1,57 +0,0 @@ -import fs from 'fs'; -import path 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 wraps calls to the code generator, and generates credentials providers, etc. - */ -export class GraphqlClientCodeGeneratorAdapter { - /** - * Creates new adapter for generateClientConfigToFile from @aws-amplify/client-config. - */ - constructor( - private readonly awsCredentialProvider: AwsCredentialIdentityProvider - ) {} - - /** - * Generates the platform-specific graphql client code for a given backend - */ - generateGraphqlClientCode = ( - props: GenerateGraphqlClientCodeProps - ): Promise => - 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 ( - props: GenerateGraphqlClientCodeToFileProps - ): Promise => { - const { out, ...rest } = props; - - const generatedCode = await generateAPICode({ - ...rest, - // credentialProvider: this.awsCredentialProvider, - }); - - Object.entries(generatedCode).forEach(([filePathSuffix, fileContents]) => { - const filePath = path.join(out, filePathSuffix); - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, fileContents); - }); - }; -} diff --git a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts index 503348172f..7b94d87bcc 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts @@ -1,5 +1,7 @@ import { BackendIdentifier } from '@aws-amplify/deployed-backend-client'; import path from 'path'; +import fs from 'fs'; +import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; export type GeneratedOutput = { [filename: string]: string }; @@ -45,32 +47,66 @@ export type GenerateAPICodeProps = GenerateOptions & { backendIdentifier: BackendIdentifier; }; +export type GenerateAPICodeToFileProps = GenerateAPICodeProps & { + out: string; +}; + /** - * Mock generateApiCode command. + * API Code Generator mock. */ -export const generateAPICode = async ( - props: GenerateAPICodeProps -): Promise => { - switch (props.format) { - case 'graphql-codegen': - return { - [path.join('src', 'graphql', 'mutations.js')]: 'type Mutations {}', - [path.join('src', 'graphql', 'queries.js')]: 'type Queries {}', - [path.join('src', 'graphql', 'subscriptions.js')]: - 'type Subscriptions {}', - }; - case 'modelgen': - return { - [path.join('src', 'models', 'index.js')]: 'export me', - [path.join('src', 'models', 'models.js')]: 'im a models', - }; - case 'introspection': - return { - 'model-introspection-schema.json': JSON.stringify( - { version: 1, models: [], nonModels: [] }, - null, - 4 - ), - }; - } -}; +export class ApiCodeGenerator { + /** + * Constructor + * @param awsCredentialProvider credential provider + */ + constructor( + private readonly awsCredentialProvider: AwsCredentialIdentityProvider + ) {} + + /** + * Mock generateApiCode command. + */ + generateAPICode = async ( + props: GenerateAPICodeProps + ): Promise => { + switch (props.format) { + case 'graphql-codegen': + return { + [path.join('src', 'graphql', 'mutations.js')]: 'type Mutations {}', + [path.join('src', 'graphql', 'queries.js')]: 'type Queries {}', + [path.join('src', 'graphql', 'subscriptions.js')]: + 'type Subscriptions {}', + }; + case 'modelgen': + return { + [path.join('src', 'models', 'index.js')]: 'export me', + [path.join('src', 'models', 'models.js')]: 'im a models', + }; + case 'introspection': + return { + 'model-introspection-schema.json': JSON.stringify( + { version: 1, models: [], nonModels: [] }, + null, + 4 + ), + }; + } + }; + + /** + * Generates the platform-specific graphql client code for a given backend, and write the outputs to the specified target. + */ + generateAPICodeToFile = async ( + props: GenerateAPICodeToFileProps + ): Promise => { + const { out, ...rest } = props; + + const generatedCode = await this.generateAPICode({ ...rest }); + + Object.entries(generatedCode).forEach(([filePathSuffix, fileContents]) => { + const filePath = path.join(out, filePathSuffix); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, fileContents); + }); + }; +} From 79cb5e8589d6f08aed202c4be3ad8646bff1bb54 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Mon, 25 Sep 2023 09:03:47 -0700 Subject: [PATCH 5/9] chore: add feature flags --- .../generate_graphql_client_code_command.ts | 101 +++++++++++++++++- .../mock_code_generator.ts | 2 - 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index b2a3b71a49..ec90834069 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -38,6 +38,15 @@ export type GenerateGraphqlClientCodeCommandOptions = { statementTarget: (typeof statementsTargetChoices)[number] | undefined; typeTarget: (typeof typesTargetChoice)[number] | undefined; out: string | undefined; + modelGenerateIndexRules: boolean | undefined; + modelEmitAuthProvider: boolean | undefined; + modelRespectPrimaryKeyAttributesOnConnectionField: boolean | undefined; + modelGenerateModelsForLazyLoadAndCustomSelectionSet: boolean | undefined; + modelAddTimestampFields: boolean | undefined; + modelHandleListNullabilityTransparently: boolean | undefined; + statementMaxDepth: number | undefined; + statementTypenameIntrospection: boolean | undefined; + typeMultipleSwiftFiles: boolean | undefined; }; /** @@ -71,19 +80,55 @@ export class GenerateGraphqlClientCodeCommand this.describe = 'Generates graphql API code'; } - private getTargetParts = ( + private getFormatParams = ( format: string, args: ArgumentsCamelCase - ): Record => { + ): Record => { switch (format) { case 'graphql-codegen': return { statementTarget: args.statementTarget ?? 'javascript', ...(args.typeTarget ? { typeTarget: args.typeTarget } : {}), + ...(args.statementMaxDepth + ? { maxDepth: args.statementMaxDepth } + : {}), + ...(args.statementTypenameIntrospection + ? { typenameIntrospection: args.statementTypenameIntrospection } + : {}), + ...(args.typeMultipleSwiftFiles + ? { multipleSwiftFiles: args.typeMultipleSwiftFiles } + : {}), }; case 'modelgen': return { modelTarget: args.modelTarget ?? 'javascript', + ...(args.modelGenerateIndexRules + ? { generateIndexRules: args.modelGenerateIndexRules } + : {}), + ...(args.modelEmitAuthProvider + ? { emitAuthProvider: args.modelEmitAuthProvider } + : {}), + ...(args.modelRespectPrimaryKeyAttributesOnConnectionField + ? { + respectPrimaryKeyAttributesOnConnectionField: + args.modelRespectPrimaryKeyAttributesOnConnectionField, + } + : {}), + ...(args.modelGenerateModelsForLazyLoadAndCustomSelectionSet + ? { + generateModelsForLazyLoadAndCustomSelectionSet: + args.modelGenerateModelsForLazyLoadAndCustomSelectionSet, + } + : {}), + ...(args.modelAddTimestampFields + ? { addTimestampFields: args.modelAddTimestampFields } + : {}), + ...(args.modelHandleListNullabilityTransparently + ? { + handleListNullabilityTransparently: + args.modelHandleListNullabilityTransparently, + } + : {}), }; case 'introspection': return {}; @@ -113,13 +158,13 @@ export class GenerateGraphqlClientCodeCommand ); const out = this.getOutDir(args); const format = args.format ?? ('graphql-codegen' as unknown as any); - const targetParts = this.getTargetParts(format, args); + const formatParams = this.getFormatParams(format, args); await this.apiCodeGenerator.generateAPICodeToFile({ backendIdentifier, out, format, - ...targetParts, + ...formatParams, }); }; @@ -184,6 +229,54 @@ export class GenerateGraphqlClientCodeCommand array: false, choices: typesTargetChoice, }) + .option('modelGenerateIndexRules', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('modelEmitAuthProvider', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('modelRespectPrimaryKeyAttributesOnConnectionField', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('modelGenerateModelsForLazyLoadAndCustomSelectionSet', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('modelAddTimestampFields', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('modelHandleListNullabilityTransparently', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('statementMaxDepth', { + description: + 'Determines how deeply nested to generate graphql statements.', + type: 'number', + array: false, + hidden: true, + }) + .option('statementTypenameIntrospection', { + type: 'boolean', + array: false, + hidden: true, + }) + .option('typeMultipleSwiftFiles', { + type: 'boolean', + array: false, + hidden: true, + }) + .showHidden('all') .check((argv) => { if (!argv.stack && !argv.branch) { throw this.missingArgsError; diff --git a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts index 7b94d87bcc..f7747b5ee7 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts @@ -10,8 +10,6 @@ export type GenerateModelsOptions = { modelTarget: 'java' | 'swift' | 'javascript' | 'typescript' | 'dart'; generateIndexRules?: boolean; emitAuthProvider?: boolean; - useExperimentalPipelinedTransformer?: boolean; - transformerVersion?: boolean; respectPrimaryKeyAttributesOnConnectionField?: boolean; generateModelsForLazyLoadAndCustomSelectionSet?: boolean; addTimestampFields?: boolean; From 7adb34bafe5dcf2043486ee20ec69d2a16e5bb15 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Mon, 25 Sep 2023 10:01:37 -0700 Subject: [PATCH 6/9] chore: flip to use real generator, not mocked api --- package-lock.json | 1 + packages/cli/package.json | 1 + .../generate/generate_command_factory.ts | 6 +- .../generate_api_code_adapter.ts | 33 ++++ ...nerate_graphql_client_code_command.test.ts | 186 +++++++++++++----- .../generate_graphql_client_code_command.ts | 44 +++-- .../mock_code_generator.ts | 110 ----------- packages/cli/tsconfig.json | 1 + 8 files changed, 202 insertions(+), 180 deletions(-) create mode 100644 packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts delete mode 100644 packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts diff --git a/package-lock.json b/package-lock.json index 54048ebda3..1ed6952ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18505,6 +18505,7 @@ "dependencies": { "@aws-amplify/client-config": "^0.2.0-alpha.5", "@aws-amplify/deployed-backend-client": "^0.1.0", + "@aws-amplify/model-generator": "^0.2.0-alpha.1", "@aws-amplify/sandbox": "^0.1.1-alpha.5", "@aws-sdk/credential-providers": "^3.360.0", "@inquirer/prompts": "^3.0.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 3841f6d1c2..cfa379969b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,6 +29,7 @@ "dependencies": { "@aws-amplify/client-config": "^0.2.0-alpha.5", "@aws-amplify/deployed-backend-client": "^0.1.0", + "@aws-amplify/model-generator": "^0.2.0-alpha.1", "@aws-amplify/sandbox": "^0.1.1-alpha.5", "@aws-sdk/credential-providers": "^3.360.0", "@inquirer/prompts": "^3.0.0", diff --git a/packages/cli/src/commands/generate/generate_command_factory.ts b/packages/cli/src/commands/generate/generate_command_factory.ts index ad8da52346..2e439936d0 100644 --- a/packages/cli/src/commands/generate/generate_command_factory.ts +++ b/packages/cli/src/commands/generate/generate_command_factory.ts @@ -7,7 +7,7 @@ import { CwdPackageJsonLoader } from '../../cwd_package_json_loader.js'; import { GenerateGraphqlClientCodeCommand } from './graphql-client-code/generate_graphql_client_code_command.js'; import { LocalAppNameResolver } from '../../backend-identifier/local_app_name_resolver.js'; import { BackendIdentifierResolver } from '../../backend-identifier/backend_identifier_resolver.js'; -import { ApiCodeGenerator } from './graphql-client-code/mock_code_generator.js'; +import { GenerateApiCodeAdapter } from './graphql-client-code/generate_api_code_adapter.js'; /** * Creates wired generate command. @@ -30,10 +30,10 @@ export const createGenerateCommand = (): CommandModule => { backendIdentifierResolver ); - const apiCodeGenerator = new ApiCodeGenerator(credentialProvider); + const generateApiCodeAdapter = new GenerateApiCodeAdapter(credentialProvider); const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand( - apiCodeGenerator, + generateApiCodeAdapter, backendIdentifierResolver ); diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts new file mode 100644 index 0000000000..5a7a4a23c5 --- /dev/null +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts @@ -0,0 +1,33 @@ +import { BackendIdentifier } from '@aws-amplify/deployed-backend-client'; +import { + GenerateOptions, + GenerationResult, + generateApiCode, +} from '@aws-amplify/model-generator'; +import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; + +// For some reason using `omit` is causing type errors, so reconstructing without the credentialProvider. +export type InvokeGenerateApiCodeProps = GenerateOptions & BackendIdentifier; + +/** + * Class to wrap static generateApiCode method for testability. + */ +export class GenerateApiCodeAdapter { + /** + * Creates graphql api code adapter. + */ + constructor( + private readonly credentialProvider: AwsCredentialIdentityProvider + ) {} + + /** + * Invoke the generateApiCode method, using the constructor injected credentialProvider, and remaining props. + */ + invokeGenerateApiCode = ( + props: InvokeGenerateApiCodeProps + ): Promise => + generateApiCode({ + ...props, + credentialProvider: this.credentialProvider, + }); +} diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts index 02d22fe25b..b4be9dc344 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts @@ -9,22 +9,28 @@ import { import assert from 'node:assert'; import path from 'path'; import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; -import { ApiCodeGenerator } from './mock_code_generator.js'; +import { GenerateApiCodeAdapter } from './generate_api_code_adapter.js'; describe('generate graphql-client-code command', () => { - const apiCodeGenerator = new ApiCodeGenerator(fromNodeProviderChain()); + const generateApiCodeAdapter = new GenerateApiCodeAdapter( + fromNodeProviderChain() + ); - const codeGeneratorMock = mock.method( - apiCodeGenerator, - 'generateAPICodeToFile', - () => Promise.resolve() + const writeToDirectoryMock = mock.fn(); + const invokeGenerateApiCodeMock = mock.method( + generateApiCodeAdapter, + 'invokeGenerateApiCode', + () => + Promise.resolve({ + writeToDirectory: writeToDirectoryMock, + }) ); const backendIdentifierResolver = new BackendIdentifierResolver({ resolve: () => Promise.resolve('testAppName'), }); const generateGraphqlClientCodeCommand = new GenerateGraphqlClientCodeCommand( - apiCodeGenerator, + generateApiCodeAdapter, backendIdentifierResolver ); const parser = yargs().command( @@ -33,65 +39,74 @@ describe('generate graphql-client-code command', () => { const commandRunner = new TestCommandRunner(parser); beforeEach(() => { - codeGeneratorMock.mock.resetCalls(); + invokeGenerateApiCodeMock.mock.resetCalls(); + writeToDirectoryMock.mock.resetCalls(); }); it('generates and writes graphql client code for stack', async () => { await commandRunner.runCommand('graphql-client-code --stack stack_name'); - assert.equal(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - stackName: 'stack_name', - }, + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', format: 'graphql-codegen', - out: process.cwd(), statementTarget: 'javascript', }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); }); it('generates and writes graphql client code for branch', async () => { await commandRunner.runCommand('graphql-client-code --branch branch_name'); - assert.equal(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - appName: 'testAppName', - branchName: 'branch_name', - }, - out: process.cwd(), + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + appName: 'testAppName', + branchName: 'branch_name', format: 'graphql-codegen', statementTarget: 'javascript', }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); }); 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(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - backendId: 'app_id', - branchName: 'branch_name', - }, - out: process.cwd(), + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + backendId: 'app_id', + branchName: 'branch_name', format: 'graphql-codegen', statementTarget: 'javascript', }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); }); it('can generate to custom relative path', async () => { await commandRunner.runCommand( 'graphql-client-code --stack stack_name --out foo/bar' ); - assert.equal(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - stackName: 'stack_name', - }, + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', format: 'graphql-codegen', statementTarget: 'javascript', - out: path.join(process.cwd(), 'foo', 'bar'), }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + path.join(process.cwd(), 'foo', 'bar') + ); }); it('shows available options in help output', async () => { @@ -104,50 +119,117 @@ describe('generate graphql-client-code command', () => { assert.match(output, /--typeTarget/); assert.match(output, /--modelTarget/); assert.match(output, /--out/); + assert.match(output, /--all/); + }); + + it('shows all available options in help output', async () => { + const output = await commandRunner.runCommand( + 'graphql-client-code --help --all' + ); + 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/); + assert.match(output, /--all/); + assert.match(output, /--modelGenerateIndexRules/); + assert.match(output, /--modelEmitAuthProvider/); + assert.match(output, /--modelAddTimestampFields/); + assert.match(output, /--statementMaxDepth/); + assert.match(output, /--statementTypenameIntrospection/); + assert.match(output, /--typeMultipleSwiftFiles/); }); it('can be invoked explicitly with graphql-codegen format', async () => { await commandRunner.runCommand( 'graphql-client-code --stack stack_name --format graphql-codegen' ); - assert.equal(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - stackName: 'stack_name', - }, + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', format: 'graphql-codegen', statementTarget: 'javascript', - out: process.cwd(), }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); }); it('can be invoked explicitly with modelgen format', async () => { await commandRunner.runCommand( 'graphql-client-code --stack stack_name --format modelgen' ); - assert.equal(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - stackName: 'stack_name', - }, + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', format: 'modelgen', modelTarget: 'javascript', - out: process.cwd(), }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); }); it('can be invoked explicitly with introspection format', async () => { await commandRunner.runCommand( 'graphql-client-code --stack stack_name --format introspection' ); - assert.equal(codeGeneratorMock.mock.callCount(), 1); - assert.deepEqual(codeGeneratorMock.mock.calls[0].arguments[0], { - backendIdentifier: { - stackName: 'stack_name', - }, + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', format: 'introspection', - out: process.cwd(), }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); + }); + + it('passes in feature flags on modelgen', async () => { + await commandRunner.runCommand( + 'graphql-client-code --stack stack_name --format modelgen --modelGenerateIndexRules true --modelEmitAuthProvider true --modelGenerateModelsForLazyLoadAndCustomSelectionSet false' + ); + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', + format: 'modelgen', + modelTarget: 'javascript', + generateIndexRules: true, + emitAuthProvider: true, + generateModelsForLazyLoadAndCustomSelectionSet: false, + }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); + }); + + it('passes in feature flags on graphql-codegen', async () => { + await commandRunner.runCommand( + 'graphql-client-code --stack stack_name --format graphql-codegen --statementTarget typescript --statementMaxDepth 3 --statementTypenameIntrospection true' + ); + assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); + assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { + stackName: 'stack_name', + format: 'graphql-codegen', + statementTarget: 'typescript', + maxDepth: 3, + typenameIntrospection: true, + }); + assert.equal(writeToDirectoryMock.mock.callCount(), 1); + assert.equal( + writeToDirectoryMock.mock.calls[0].arguments[0], + process.cwd() + ); }); // Note: after this test, future tests seem to be in a weird state, leaving this at the diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index ec90834069..164bb3ee15 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -1,7 +1,7 @@ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; -import { ApiCodeGenerator } from './mock_code_generator.js'; import { isAbsolute, resolve } from 'path'; +import { GenerateApiCodeAdapter } from './generate_api_code_adapter.js'; export const formatChoices = ['graphql-codegen', 'introspection', 'modelgen']; export const modelgenTargetChoices = [ @@ -73,7 +73,7 @@ export class GenerateGraphqlClientCodeCommand * Creates graphql client code generation command. */ constructor( - private readonly apiCodeGenerator: ApiCodeGenerator, + private readonly generateApiCodeAdapter: GenerateApiCodeAdapter, private readonly backendIdentifierResolver: BackendIdentifierResolver ) { this.command = 'graphql-client-code'; @@ -88,42 +88,42 @@ export class GenerateGraphqlClientCodeCommand case 'graphql-codegen': return { statementTarget: args.statementTarget ?? 'javascript', - ...(args.typeTarget ? { typeTarget: args.typeTarget } : {}), - ...(args.statementMaxDepth + ...('typeTarget' in args ? { typeTarget: args.typeTarget } : {}), + ...('statementMaxDepth' in args ? { maxDepth: args.statementMaxDepth } : {}), - ...(args.statementTypenameIntrospection + ...('statementTypenameIntrospection' in args ? { typenameIntrospection: args.statementTypenameIntrospection } : {}), - ...(args.typeMultipleSwiftFiles + ...('typeMultipleSwiftFiles' in args ? { multipleSwiftFiles: args.typeMultipleSwiftFiles } : {}), }; case 'modelgen': return { modelTarget: args.modelTarget ?? 'javascript', - ...(args.modelGenerateIndexRules + ...('modelGenerateIndexRules' in args ? { generateIndexRules: args.modelGenerateIndexRules } : {}), - ...(args.modelEmitAuthProvider + ...('modelEmitAuthProvider' in args ? { emitAuthProvider: args.modelEmitAuthProvider } : {}), - ...(args.modelRespectPrimaryKeyAttributesOnConnectionField + ...('modelRespectPrimaryKeyAttributesOnConnectionField' in args ? { respectPrimaryKeyAttributesOnConnectionField: args.modelRespectPrimaryKeyAttributesOnConnectionField, } : {}), - ...(args.modelGenerateModelsForLazyLoadAndCustomSelectionSet + ...('modelGenerateModelsForLazyLoadAndCustomSelectionSet' in args ? { generateModelsForLazyLoadAndCustomSelectionSet: args.modelGenerateModelsForLazyLoadAndCustomSelectionSet, } : {}), - ...(args.modelAddTimestampFields + ...('modelAddTimestampFields' in args ? { addTimestampFields: args.modelAddTimestampFields } : {}), - ...(args.modelHandleListNullabilityTransparently + ...('modelHandleListNullabilityTransparently' in args ? { handleListNullabilityTransparently: args.modelHandleListNullabilityTransparently, @@ -160,12 +160,13 @@ export class GenerateGraphqlClientCodeCommand const format = args.format ?? ('graphql-codegen' as unknown as any); const formatParams = this.getFormatParams(format, args); - await this.apiCodeGenerator.generateAPICodeToFile({ - backendIdentifier, - out, + const result = await this.generateApiCodeAdapter.invokeGenerateApiCode({ + ...backendIdentifier, format, ...formatParams, }); + + await result.writeToDirectory(out); }; /** @@ -230,31 +231,40 @@ export class GenerateGraphqlClientCodeCommand choices: typesTargetChoice, }) .option('modelGenerateIndexRules', { + description: 'Adds key/index details to iOS models', type: 'boolean', array: false, hidden: true, }) .option('modelEmitAuthProvider', { + description: 'Adds auth provider details to iOS models', type: 'boolean', array: false, hidden: true, }) .option('modelRespectPrimaryKeyAttributesOnConnectionField', { + description: + 'If enabled, datastore queries will respect the primary + sort key fields, rather than a defaut id field', type: 'boolean', array: false, hidden: true, }) .option('modelGenerateModelsForLazyLoadAndCustomSelectionSet', { + description: + 'Generates lazy model type definitions, required or amplify-js v5+', type: 'boolean', array: false, hidden: true, }) .option('modelAddTimestampFields', { + description: 'Add read-only timestamp fields in modelgen.', type: 'boolean', array: false, hidden: true, }) .option('modelHandleListNullabilityTransparently', { + description: + 'Configure the nullability of the List and List components in Datastore Models generation', type: 'boolean', array: false, hidden: true, @@ -267,11 +277,15 @@ export class GenerateGraphqlClientCodeCommand hidden: true, }) .option('statementTypenameIntrospection', { + description: + 'Determines whether to include default __typename for all generated statements', type: 'boolean', array: false, hidden: true, }) .option('typeMultipleSwiftFiles', { + description: + 'Determines whether or not to generate a single API.swift, or multiple per-model swift files.', type: 'boolean', array: false, hidden: true, diff --git a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts b/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts deleted file mode 100644 index f7747b5ee7..0000000000 --- a/packages/cli/src/commands/generate/graphql-client-code/mock_code_generator.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { BackendIdentifier } from '@aws-amplify/deployed-backend-client'; -import path from 'path'; -import fs from 'fs'; -import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; - -export type GeneratedOutput = { [filename: string]: string }; - -export type GenerateModelsOptions = { - format: 'modelgen'; - modelTarget: 'java' | 'swift' | 'javascript' | 'typescript' | 'dart'; - generateIndexRules?: boolean; - emitAuthProvider?: boolean; - respectPrimaryKeyAttributesOnConnectionField?: boolean; - generateModelsForLazyLoadAndCustomSelectionSet?: boolean; - addTimestampFields?: boolean; - handleListNullabilityTransparently?: boolean; -}; - -export type GenerateGraphqlCodegenOptions = { - format: 'graphql-codegen'; - statementTarget: 'javascript' | 'graphql' | 'flow' | 'typescript' | 'angular'; - maxDepth?: number; - typenameIntrospection?: boolean; - typeTarget?: - | 'json' - | 'swift' - | 'typescript' - | 'flow' - | 'scala' - | 'flow-modern' - | 'angular'; - multipleSwiftFiles?: boolean; -}; - -export type GenerateIntrospectionOptions = { - format: 'introspection'; -}; - -export type GenerateOptions = - | GenerateGraphqlCodegenOptions - | GenerateModelsOptions - | GenerateIntrospectionOptions; - -export type GenerateAPICodeProps = GenerateOptions & { - backendIdentifier: BackendIdentifier; -}; - -export type GenerateAPICodeToFileProps = GenerateAPICodeProps & { - out: string; -}; - -/** - * API Code Generator mock. - */ -export class ApiCodeGenerator { - /** - * Constructor - * @param awsCredentialProvider credential provider - */ - constructor( - private readonly awsCredentialProvider: AwsCredentialIdentityProvider - ) {} - - /** - * Mock generateApiCode command. - */ - generateAPICode = async ( - props: GenerateAPICodeProps - ): Promise => { - switch (props.format) { - case 'graphql-codegen': - return { - [path.join('src', 'graphql', 'mutations.js')]: 'type Mutations {}', - [path.join('src', 'graphql', 'queries.js')]: 'type Queries {}', - [path.join('src', 'graphql', 'subscriptions.js')]: - 'type Subscriptions {}', - }; - case 'modelgen': - return { - [path.join('src', 'models', 'index.js')]: 'export me', - [path.join('src', 'models', 'models.js')]: 'im a models', - }; - case 'introspection': - return { - 'model-introspection-schema.json': JSON.stringify( - { version: 1, models: [], nonModels: [] }, - null, - 4 - ), - }; - } - }; - - /** - * Generates the platform-specific graphql client code for a given backend, and write the outputs to the specified target. - */ - generateAPICodeToFile = async ( - props: GenerateAPICodeToFileProps - ): Promise => { - const { out, ...rest } = props; - - const generatedCode = await this.generateAPICode({ ...rest }); - - Object.entries(generatedCode).forEach(([filePathSuffix, fileContents]) => { - const filePath = path.join(out, filePathSuffix); - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, fileContents); - }); - }; -} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index ca7ebacb55..ea291bde69 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -4,6 +4,7 @@ "references": [ { "path": "../client-config" }, { "path": "../deployed-backend-client" }, + { "path": "../model-generator" }, { "path": "../sandbox" } ] } From fa2a5b0de25a2ddec4e04f0709773334a177fb47 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Mon, 25 Sep 2023 10:36:32 -0700 Subject: [PATCH 7/9] fix: lint-fix --- .eslint_dictionary.js | 1 + .../generate/graphql-client-code/generate_api_code_adapter.ts | 2 +- .../graphql-client-code/generate_graphql_client_code_command.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslint_dictionary.js b/.eslint_dictionary.js index ca66dbccb3..200e688185 100644 --- a/.eslint_dictionary.js +++ b/.eslint_dictionary.js @@ -14,6 +14,7 @@ export default [ 'codegen', 'cognito', 'ctor', + 'datastore', 'debounce', 'declarator', 'deployer', diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts index 5a7a4a23c5..c241a0a6c1 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_api_code_adapter.ts @@ -10,7 +10,7 @@ import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; export type InvokeGenerateApiCodeProps = GenerateOptions & BackendIdentifier; /** - * Class to wrap static generateApiCode method for testability. + * Class to wrap static generateApiCode method to facilitate testing. */ export class GenerateApiCodeAdapter { /** diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index 164bb3ee15..0d10e984c7 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -244,7 +244,7 @@ export class GenerateGraphqlClientCodeCommand }) .option('modelRespectPrimaryKeyAttributesOnConnectionField', { description: - 'If enabled, datastore queries will respect the primary + sort key fields, rather than a defaut id field', + 'If enabled, Datastore queries will respect the primary + sort key fields, rather than a default id field', type: 'boolean', array: false, hidden: true, From b2f0940561fa727a15d32272e1e16e5af11e54b9 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Mon, 25 Sep 2023 11:38:02 -0700 Subject: [PATCH 8/9] fix: use exported types and values from generateApiCode method --- .../generate_graphql_client_code_command.ts | 48 ++++++------------- packages/model-generator/API.md | 18 +++++-- .../model-generator/src/generate_api_code.ts | 42 ++++++++++++---- 3 files changed, 61 insertions(+), 47 deletions(-) diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index 0d10e984c7..c0a86ed547 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -2,41 +2,21 @@ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; import { isAbsolute, resolve } from 'path'; import { GenerateApiCodeAdapter } from './generate_api_code_adapter.js'; - -export const formatChoices = ['graphql-codegen', 'introspection', 'modelgen']; -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', -]; +import { + generateApiCodeFormats, + generateApiCodeModelTargets, + generateApiCodeStatementTargets, + generateApiCodeTypeTargets, +} from '@aws-amplify/model-generator'; export type GenerateGraphqlClientCodeCommandOptions = { stack: string | undefined; appId: string | undefined; branch: string | undefined; - format: (typeof formatChoices)[number] | undefined; - modelTarget: (typeof modelgenTargetChoices)[number] | undefined; - statementTarget: (typeof statementsTargetChoices)[number] | undefined; - typeTarget: (typeof typesTargetChoice)[number] | undefined; + format: (typeof generateApiCodeFormats)[number] | undefined; + modelTarget: (typeof generateApiCodeModelTargets)[number] | undefined; + statementTarget: (typeof generateApiCodeStatementTargets)[number] | undefined; + typeTarget: (typeof generateApiCodeTypeTargets)[number] | undefined; out: string | undefined; modelGenerateIndexRules: boolean | undefined; modelEmitAuthProvider: boolean | undefined; @@ -207,28 +187,28 @@ export class GenerateGraphqlClientCodeCommand 'The format that the GraphQL client code should be generated in.', type: 'string', array: false, - choices: formatChoices, + choices: generateApiCodeFormats, }) .option('modelTarget', { describe: 'The modelgen export target. Only applies when the `--format` parameter is set to `modelgen`', type: 'string', array: false, - choices: modelgenTargetChoices, + choices: generateApiCodeModelTargets, }) .option('statementTarget', { describe: 'The graphql-codegen statement export target. Only applies when the `--format` parameter is set to `graphql-codegen`', type: 'string', array: false, - choices: statementsTargetChoices, + choices: generateApiCodeStatementTargets, }) .option('typeTarget', { describe: 'The optional graphql-codegen type export target. Only applies when the `--format` parameter is set to `graphql-codegen`', type: 'string', array: false, - choices: typesTargetChoice, + choices: generateApiCodeTypeTargets, }) .option('modelGenerateIndexRules', { description: 'Adds key/index details to iOS models', diff --git a/packages/model-generator/API.md b/packages/model-generator/API.md index 97087da629..128f207aaf 100644 --- a/packages/model-generator/API.md +++ b/packages/model-generator/API.md @@ -23,18 +23,30 @@ export type DocumentGenerationParameters = { // @public export const generateApiCode: (props: GenerateApiCodeProps) => Promise; +// @public (undocumented) +export const generateApiCodeFormats: readonly ["modelgen", "graphql-codegen", "introspection"]; + +// @public (undocumented) +export const generateApiCodeModelTargets: readonly ["java", "swift", "javascript", "typescript", "dart"]; + // @public (undocumented) export type GenerateApiCodeProps = GenerateOptions & BackendIdentifier & { credentialProvider: AwsCredentialIdentityProvider; }; +// @public (undocumented) +export const generateApiCodeStatementTargets: readonly ["javascript", "graphql", "flow", "typescript", "angular"]; + +// @public (undocumented) +export const generateApiCodeTypeTargets: readonly ["json", "swift", "typescript", "flow", "scala", "flow-modern", "angular"]; + // @public (undocumented) export type GenerateGraphqlCodegenOptions = { format: 'graphql-codegen'; - statementTarget: 'javascript' | 'graphql' | 'flow' | 'typescript' | 'angular'; + statementTarget: typeof generateApiCodeStatementTargets[number]; maxDepth?: number; typeNameIntrospection?: boolean; - typeTarget?: 'json' | 'swift' | 'typescript' | 'flow' | 'scala' | 'flow-modern' | 'angular'; + typeTarget?: typeof generateApiCodeTypeTargets[number]; multipleSwiftFiles?: boolean; }; @@ -46,7 +58,7 @@ export type GenerateIntrospectionOptions = { // @public (undocumented) export type GenerateModelsOptions = { format: 'modelgen'; - modelTarget: 'java' | 'swift' | 'javascript' | 'typescript' | 'dart'; + modelTarget: typeof generateApiCodeModelTargets[number]; generateIndexRules?: boolean; emitAuthProvider?: boolean; useExperimentalPipelinedTransformer?: boolean; diff --git a/packages/model-generator/src/generate_api_code.ts b/packages/model-generator/src/generate_api_code.ts index 6841ef8bbf..01d5caa90f 100644 --- a/packages/model-generator/src/generate_api_code.ts +++ b/packages/model-generator/src/generate_api_code.ts @@ -7,9 +7,38 @@ import { createGraphqlModelsGenerator } from './create_graphql_models_generator. import { createGraphqlTypesGenerator } from './create_graphql_types_generator.js'; import { createGraphqlDocumentGenerator } from './create_graphql_document_generator.js'; +export const generateApiCodeFormats = [ + 'modelgen', + 'graphql-codegen', + 'introspection', +] as const; +export const generateApiCodeModelTargets = [ + 'java', + 'swift', + 'javascript', + 'typescript', + 'dart', +] as const; +export const generateApiCodeStatementTargets = [ + 'javascript', + 'graphql', + 'flow', + 'typescript', + 'angular', +] as const; +export const generateApiCodeTypeTargets = [ + 'json', + 'swift', + 'typescript', + 'flow', + 'scala', + 'flow-modern', + 'angular', +] as const; + export type GenerateModelsOptions = { format: 'modelgen'; - modelTarget: 'java' | 'swift' | 'javascript' | 'typescript' | 'dart'; + modelTarget: (typeof generateApiCodeModelTargets)[number]; generateIndexRules?: boolean; emitAuthProvider?: boolean; useExperimentalPipelinedTransformer?: boolean; @@ -22,17 +51,10 @@ export type GenerateModelsOptions = { export type GenerateGraphqlCodegenOptions = { format: 'graphql-codegen'; - statementTarget: 'javascript' | 'graphql' | 'flow' | 'typescript' | 'angular'; + statementTarget: (typeof generateApiCodeStatementTargets)[number]; maxDepth?: number; typeNameIntrospection?: boolean; - typeTarget?: - | 'json' - | 'swift' - | 'typescript' - | 'flow' - | 'scala' - | 'flow-modern' - | 'angular'; + typeTarget?: (typeof generateApiCodeTypeTargets)[number]; multipleSwiftFiles?: boolean; }; From 57152fcbbce12a04d815eebc0983f3c07022d105 Mon Sep 17 00:00:00 2001 From: Al Harris Date: Mon, 25 Sep 2023 12:00:30 -0700 Subject: [PATCH 9/9] fix: use enums instead of string lists --- ...nerate_graphql_client_code_command.test.ts | 39 +++++----- .../generate_graphql_client_code_command.ts | 34 +++++---- packages/model-generator/API.md | 72 +++++++++++++++---- .../model-generator/src/generate_api_code.ts | 71 +++++++++--------- 4 files changed, 138 insertions(+), 78 deletions(-) diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts index b4be9dc344..ba94956da3 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts @@ -10,6 +10,11 @@ import assert from 'node:assert'; import path from 'path'; import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; import { GenerateApiCodeAdapter } from './generate_api_code_adapter.js'; +import { + GenerateApiCodeFormat, + GenerateApiCodeModelTarget, + GenerateApiCodeStatementTarget, +} from '@aws-amplify/model-generator'; describe('generate graphql-client-code command', () => { const generateApiCodeAdapter = new GenerateApiCodeAdapter( @@ -48,8 +53,8 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'graphql-codegen', - statementTarget: 'javascript', + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN, + statementTarget: GenerateApiCodeStatementTarget.JAVASCRIPT, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -64,8 +69,8 @@ describe('generate graphql-client-code command', () => { assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { appName: 'testAppName', branchName: 'branch_name', - format: 'graphql-codegen', - statementTarget: 'javascript', + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN, + statementTarget: GenerateApiCodeStatementTarget.JAVASCRIPT, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -82,8 +87,8 @@ describe('generate graphql-client-code command', () => { assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { backendId: 'app_id', branchName: 'branch_name', - format: 'graphql-codegen', - statementTarget: 'javascript', + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN, + statementTarget: GenerateApiCodeStatementTarget.JAVASCRIPT, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -99,8 +104,8 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'graphql-codegen', - statementTarget: 'javascript', + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN, + statementTarget: GenerateApiCodeStatementTarget.JAVASCRIPT, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -150,8 +155,8 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'graphql-codegen', - statementTarget: 'javascript', + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN, + statementTarget: GenerateApiCodeStatementTarget.JAVASCRIPT, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -167,8 +172,8 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'modelgen', - modelTarget: 'javascript', + format: GenerateApiCodeFormat.MODELGEN, + modelTarget: GenerateApiCodeModelTarget.JAVASCRIPT, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -184,7 +189,7 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'introspection', + format: GenerateApiCodeFormat.INTROSPECTION, }); assert.equal(writeToDirectoryMock.mock.callCount(), 1); assert.equal( @@ -200,8 +205,8 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'modelgen', - modelTarget: 'javascript', + format: GenerateApiCodeFormat.MODELGEN, + modelTarget: GenerateApiCodeModelTarget.JAVASCRIPT, generateIndexRules: true, emitAuthProvider: true, generateModelsForLazyLoadAndCustomSelectionSet: false, @@ -220,8 +225,8 @@ describe('generate graphql-client-code command', () => { assert.equal(invokeGenerateApiCodeMock.mock.callCount(), 1); assert.deepEqual(invokeGenerateApiCodeMock.mock.calls[0].arguments[0], { stackName: 'stack_name', - format: 'graphql-codegen', - statementTarget: 'typescript', + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN, + statementTarget: GenerateApiCodeStatementTarget.TYPESCRIPT, maxDepth: 3, typenameIntrospection: true, }); diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index c0a86ed547..1911b7b583 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -3,20 +3,22 @@ import { BackendIdentifierResolver } from '../../../backend-identifier/backend_i import { isAbsolute, resolve } from 'path'; import { GenerateApiCodeAdapter } from './generate_api_code_adapter.js'; import { - generateApiCodeFormats, - generateApiCodeModelTargets, - generateApiCodeStatementTargets, - generateApiCodeTypeTargets, + GenerateApiCodeFormat, + GenerateApiCodeModelTarget, + GenerateApiCodeStatementTarget, + GenerateApiCodeTypeTarget, + GenerateGraphqlCodegenOptions, + GenerateModelsOptions, } from '@aws-amplify/model-generator'; export type GenerateGraphqlClientCodeCommandOptions = { stack: string | undefined; appId: string | undefined; branch: string | undefined; - format: (typeof generateApiCodeFormats)[number] | undefined; - modelTarget: (typeof generateApiCodeModelTargets)[number] | undefined; - statementTarget: (typeof generateApiCodeStatementTargets)[number] | undefined; - typeTarget: (typeof generateApiCodeTypeTargets)[number] | undefined; + format: GenerateApiCodeFormat | undefined; + modelTarget: GenerateApiCodeModelTarget | undefined; + statementTarget: GenerateApiCodeStatementTarget | undefined; + typeTarget: GenerateApiCodeTypeTarget | undefined; out: string | undefined; modelGenerateIndexRules: boolean | undefined; modelEmitAuthProvider: boolean | undefined; @@ -63,7 +65,13 @@ export class GenerateGraphqlClientCodeCommand private getFormatParams = ( format: string, args: ArgumentsCamelCase - ): Record => { + ): + | {} + | Pick< + GenerateGraphqlCodegenOptions, + 'statementTarget' | 'typeTarget' | 'maxDepth' | 'multipleSwiftFiles' + > + | Pick => { switch (format) { case 'graphql-codegen': return { @@ -187,28 +195,28 @@ export class GenerateGraphqlClientCodeCommand 'The format that the GraphQL client code should be generated in.', type: 'string', array: false, - choices: generateApiCodeFormats, + choices: Object.values(GenerateApiCodeFormat), }) .option('modelTarget', { describe: 'The modelgen export target. Only applies when the `--format` parameter is set to `modelgen`', type: 'string', array: false, - choices: generateApiCodeModelTargets, + choices: Object.values(GenerateApiCodeModelTarget), }) .option('statementTarget', { describe: 'The graphql-codegen statement export target. Only applies when the `--format` parameter is set to `graphql-codegen`', type: 'string', array: false, - choices: generateApiCodeStatementTargets, + choices: Object.values(GenerateApiCodeStatementTarget), }) .option('typeTarget', { describe: 'The optional graphql-codegen type export target. Only applies when the `--format` parameter is set to `graphql-codegen`', type: 'string', array: false, - choices: generateApiCodeTypeTargets, + choices: Object.values(GenerateApiCodeTypeTarget), }) .option('modelGenerateIndexRules', { description: 'Adds key/index details to iOS models', diff --git a/packages/model-generator/API.md b/packages/model-generator/API.md index 128f207aaf..61b7b552a0 100644 --- a/packages/model-generator/API.md +++ b/packages/model-generator/API.md @@ -24,10 +24,28 @@ export type DocumentGenerationParameters = { export const generateApiCode: (props: GenerateApiCodeProps) => Promise; // @public (undocumented) -export const generateApiCodeFormats: readonly ["modelgen", "graphql-codegen", "introspection"]; - -// @public (undocumented) -export const generateApiCodeModelTargets: readonly ["java", "swift", "javascript", "typescript", "dart"]; +export enum GenerateApiCodeFormat { + // (undocumented) + GRAPHQL_CODEGEN = "graphql-codegen", + // (undocumented) + INTROSPECTION = "introspection", + // (undocumented) + MODELGEN = "modelgen" +} + +// @public (undocumented) +export enum GenerateApiCodeModelTarget { + // (undocumented) + DART = "dart", + // (undocumented) + JAVA = "java", + // (undocumented) + JAVASCRIPT = "javascript", + // (undocumented) + SWIFT = "swift", + // (undocumented) + TYPESCRIPT = "typescript" +} // @public (undocumented) export type GenerateApiCodeProps = GenerateOptions & BackendIdentifier & { @@ -35,30 +53,56 @@ export type GenerateApiCodeProps = GenerateOptions & BackendIdentifier & { }; // @public (undocumented) -export const generateApiCodeStatementTargets: readonly ["javascript", "graphql", "flow", "typescript", "angular"]; - -// @public (undocumented) -export const generateApiCodeTypeTargets: readonly ["json", "swift", "typescript", "flow", "scala", "flow-modern", "angular"]; +export enum GenerateApiCodeStatementTarget { + // (undocumented) + ANGULAR = "angular", + // (undocumented) + FLOW = "flow", + // (undocumented) + GRAPHQL = "graphql", + // (undocumented) + JAVASCRIPT = "javascript", + // (undocumented) + TYPESCRIPT = "typescript" +} + +// @public (undocumented) +export enum GenerateApiCodeTypeTarget { + // (undocumented) + ANGULAR = "angular", + // (undocumented) + FLOW = "flow", + // (undocumented) + FLOW_MODERN = "flow-modern", + // (undocumented) + JSON = "json", + // (undocumented) + SCALA = "scala", + // (undocumented) + SWIFT = "swift", + // (undocumented) + TYPESCRIPT = "typescript" +} // @public (undocumented) export type GenerateGraphqlCodegenOptions = { - format: 'graphql-codegen'; - statementTarget: typeof generateApiCodeStatementTargets[number]; + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN; + statementTarget: GenerateApiCodeStatementTarget; maxDepth?: number; typeNameIntrospection?: boolean; - typeTarget?: typeof generateApiCodeTypeTargets[number]; + typeTarget?: GenerateApiCodeTypeTarget; multipleSwiftFiles?: boolean; }; // @public (undocumented) export type GenerateIntrospectionOptions = { - format: 'introspection'; + format: GenerateApiCodeFormat.INTROSPECTION; }; // @public (undocumented) export type GenerateModelsOptions = { - format: 'modelgen'; - modelTarget: typeof generateApiCodeModelTargets[number]; + format: GenerateApiCodeFormat.MODELGEN; + modelTarget: GenerateApiCodeModelTarget; generateIndexRules?: boolean; emitAuthProvider?: boolean; useExperimentalPipelinedTransformer?: boolean; diff --git a/packages/model-generator/src/generate_api_code.ts b/packages/model-generator/src/generate_api_code.ts index 01d5caa90f..313fc464fb 100644 --- a/packages/model-generator/src/generate_api_code.ts +++ b/packages/model-generator/src/generate_api_code.ts @@ -7,38 +7,41 @@ import { createGraphqlModelsGenerator } from './create_graphql_models_generator. import { createGraphqlTypesGenerator } from './create_graphql_types_generator.js'; import { createGraphqlDocumentGenerator } from './create_graphql_document_generator.js'; -export const generateApiCodeFormats = [ - 'modelgen', - 'graphql-codegen', - 'introspection', -] as const; -export const generateApiCodeModelTargets = [ - 'java', - 'swift', - 'javascript', - 'typescript', - 'dart', -] as const; -export const generateApiCodeStatementTargets = [ - 'javascript', - 'graphql', - 'flow', - 'typescript', - 'angular', -] as const; -export const generateApiCodeTypeTargets = [ - 'json', - 'swift', - 'typescript', - 'flow', - 'scala', - 'flow-modern', - 'angular', -] as const; +export enum GenerateApiCodeFormat { + MODELGEN = 'modelgen', + GRAPHQL_CODEGEN = 'graphql-codegen', + INTROSPECTION = 'introspection', +} + +export enum GenerateApiCodeModelTarget { + JAVA = 'java', + SWIFT = 'swift', + JAVASCRIPT = 'javascript', + TYPESCRIPT = 'typescript', + DART = 'dart', +} + +export enum GenerateApiCodeStatementTarget { + JAVASCRIPT = 'javascript', + GRAPHQL = 'graphql', + FLOW = 'flow', + TYPESCRIPT = 'typescript', + ANGULAR = 'angular', +} + +export enum GenerateApiCodeTypeTarget { + JSON = 'json', + SWIFT = 'swift', + TYPESCRIPT = 'typescript', + FLOW = 'flow', + SCALA = 'scala', + FLOW_MODERN = 'flow-modern', + ANGULAR = 'angular', +} export type GenerateModelsOptions = { - format: 'modelgen'; - modelTarget: (typeof generateApiCodeModelTargets)[number]; + format: GenerateApiCodeFormat.MODELGEN; + modelTarget: GenerateApiCodeModelTarget; generateIndexRules?: boolean; emitAuthProvider?: boolean; useExperimentalPipelinedTransformer?: boolean; @@ -50,16 +53,16 @@ export type GenerateModelsOptions = { }; export type GenerateGraphqlCodegenOptions = { - format: 'graphql-codegen'; - statementTarget: (typeof generateApiCodeStatementTargets)[number]; + format: GenerateApiCodeFormat.GRAPHQL_CODEGEN; + statementTarget: GenerateApiCodeStatementTarget; maxDepth?: number; typeNameIntrospection?: boolean; - typeTarget?: (typeof generateApiCodeTypeTargets)[number]; + typeTarget?: GenerateApiCodeTypeTarget; multipleSwiftFiles?: boolean; }; export type GenerateIntrospectionOptions = { - format: 'introspection'; + format: GenerateApiCodeFormat.INTROSPECTION; }; export type GenerateOptions =