Skip to content

Commit

Permalink
feat: print out file written for amplify generate commands (#939)
Browse files Browse the repository at this point in the history
* feat: print out file written for amplify generate commands

* update API.md

* optional chaining

Co-authored-by: Edward Foyle <foyleef@amazon.com>

* optional chaining + prettier

* use splice instead

* pass in printer.log instead of defining print at top-level

* update API.md

* use printer singleton defined in cli

* removing unused import

* removing unused import

* fix platform specific path

* update assertions

* removing unused import

* win path fix

* win path fix

* try strictEqual instead of match

---------

Co-authored-by: Edward Foyle <foyleef@amazon.com>
  • Loading branch information
bombguy and edwardfoyle committed Jan 24, 2024
1 parent 40298a4 commit 4c1485a
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 63 deletions.
8 changes: 8 additions & 0 deletions .changeset/big-dryers-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@aws-amplify/model-generator': minor
'@aws-amplify/form-generator': minor
'@aws-amplify/client-config': minor
'@aws-amplify/backend-cli': minor
---

print out file written for amplify generate commands
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@aws-amplify/client-config';
import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { printer } from '../printer.js';

/**
* Adapts static generateClientConfigToFile from @aws-amplify/client-config call to make it injectable and testable.
Expand Down Expand Up @@ -39,7 +40,8 @@ export class ClientConfigGeneratorAdapter {
this.awsCredentialProvider,
backendIdentifier,
outDir,
format
format,
(message) => printer.log(message)
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,71 +107,86 @@ void describe('generate config command', () => {
'config --branch branch_name --app-id app_id --out-dir /foo/bar --format mjs'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(generateClientConfigMock.mock.calls[0].arguments, [
{
name: 'branch_name',
namespace: 'app_id',
type: 'branch',
},
'/foo/bar',
ClientConfigFormat.MJS,
]);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments.splice(0, 3),
[
{
name: 'branch_name',
namespace: 'app_id',
type: 'branch',
},
'/foo/bar',
ClientConfigFormat.MJS,
]
);
});

void it('can generate to custom absolute path', async () => {
await commandRunner.runCommand(
'config --stack stack_name --out-dir /foo/bar --format ts'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(generateClientConfigMock.mock.calls[0].arguments, [
{
stackName: 'stack_name',
},
'/foo/bar',
ClientConfigFormat.TS,
]);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments.splice(0, 3),
[
{
stackName: 'stack_name',
},
'/foo/bar',
ClientConfigFormat.TS,
]
);
});

void it('can generate to custom relative path', async () => {
await commandRunner.runCommand(
'config --stack stack_name --out-dir foo/bar --format mjs'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(generateClientConfigMock.mock.calls[0].arguments, [
{
stackName: 'stack_name',
},
'foo/bar',
ClientConfigFormat.MJS,
]);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments.splice(0, 3),
[
{
stackName: 'stack_name',
},
'foo/bar',
ClientConfigFormat.MJS,
]
);
});

void it('can generate config in dart format', async () => {
await commandRunner.runCommand(
'config --stack stack_name --out-dir foo/bar --format dart'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(generateClientConfigMock.mock.calls[0].arguments, [
{
stackName: 'stack_name',
},
'foo/bar',
ClientConfigFormat.DART,
]);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments.splice(0, 3),
[
{
stackName: 'stack_name',
},
'foo/bar',
ClientConfigFormat.DART,
]
);
});

void it('can generate config in json mobile format', async () => {
await commandRunner.runCommand(
'config --stack stack_name --out-dir foo/bar --format json-mobile'
);
assert.equal(generateClientConfigMock.mock.callCount(), 1);
assert.deepStrictEqual(generateClientConfigMock.mock.calls[0].arguments, [
{
stackName: 'stack_name',
},
'foo/bar',
ClientConfigFormat.JSON_MOBILE,
]);
assert.deepStrictEqual(
generateClientConfigMock.mock.calls[0].arguments.splice(0, 3),
[
{
stackName: 'stack_name',
},
'foo/bar',
ClientConfigFormat.JSON_MOBILE,
]
);
});

void it('shows available options in help output', async () => {
Expand Down
7 changes: 5 additions & 2 deletions packages/cli/src/form-generation/form_generation_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createLocalGraphqlFormGenerator } from '@aws-amplify/form-generator';
import { createGraphqlDocumentGenerator } from '@aws-amplify/model-generator';
import { DeployedBackendIdentifier } from '@aws-amplify/deployed-backend-client';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { printer } from '../printer.js';

type FormGenerationParams = {
modelsOutDir: string;
Expand Down Expand Up @@ -32,14 +33,16 @@ export class FormGenerationHandler {
const modelsResult = await graphqlClientGenerator.generateModels({
language: 'typescript',
});
await modelsResult.writeToDirectory(modelsOutDir);
await modelsResult.writeToDirectory(modelsOutDir, (message) =>
printer.log(message)
);
const localFormGenerator = createLocalGraphqlFormGenerator({
introspectionSchemaUrl: apiUrl,
graphqlModelDirectoryPath: './graphql',
});
const result = await localFormGenerator.generateForms({
models: modelsFilter,
});
await result.writeToDirectory(uiOutDir);
await result.writeToDirectory(uiOutDir, (message) => printer.log(message));
};
}
2 changes: 1 addition & 1 deletion packages/client-config/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export enum ClientConfigFormat {
export const generateClientConfig: (credentialProvider: AwsCredentialIdentityProvider, backendIdentifier: DeployedBackendIdentifier) => Promise<ClientConfig>;

// @public
export const generateClientConfigToFile: (credentialProvider: AwsCredentialIdentityProvider, backendIdentifier: DeployedBackendIdentifier, outDir?: string, format?: ClientConfigFormat) => Promise<void>;
export const generateClientConfigToFile: (credentialProvider: AwsCredentialIdentityProvider, backendIdentifier: DeployedBackendIdentifier, outDir?: string, format?: ClientConfigFormat, log?: ((message: string) => void) | undefined) => Promise<void>;

// @public
export const getClientConfigPath: (outDir?: string, format?: ClientConfigFormat) => Promise<string>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _fsp from 'fs/promises';
import path from 'path';
import {
ClientConfig,
ClientConfigFormat,
Expand Down Expand Up @@ -28,10 +29,13 @@ export class ClientConfigWriter {
writeClientConfig = async (
clientConfig: ClientConfig,
outDir?: string,
format: ClientConfigFormat = ClientConfigFormat.JSON
format: ClientConfigFormat = ClientConfigFormat.JSON,
// TODO: update this type when Printer interface gets defined in platform-core.
log?: (message: string) => void
): Promise<void> => {
const targetPath = await this.pathResolver(outDir, format);
const fileContent = this.formatter.format(clientConfig, format);
await this.fsp.writeFile(targetPath, fileContent);
log?.(`File written: ${path.relative(process.cwd(), targetPath)}`);
};
}
6 changes: 4 additions & 2 deletions packages/client-config/src/generate_client_config_to_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const generateClientConfigToFile = async (
credentialProvider: AwsCredentialIdentityProvider,
backendIdentifier: DeployedBackendIdentifier,
outDir?: string,
format?: ClientConfigFormat
format?: ClientConfigFormat,
// TODO: update this type when Printer interface gets defined in platform-core.
log?: (message: string) => void
): Promise<void> => {
const packageJson = await readPackageJson();

Expand All @@ -31,7 +33,7 @@ export const generateClientConfigToFile = async (
credentialProvider,
backendIdentifier
);
await clientConfigWriter.writeClientConfig(clientConfig, outDir, format);
await clientConfigWriter.writeClientConfig(clientConfig, outDir, format, log);
};

const readPackageJson = async (): Promise<{
Expand Down
2 changes: 1 addition & 1 deletion packages/form-generator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type GraphqlFormGenerator = {

// @public (undocumented)
export type GraphqlGenerationResult = {
writeToDirectory: (directoryPath: string) => Promise<void>;
writeToDirectory: (directoryPath: string, log?: (message: string) => void) => Promise<void>;
};

// @public (undocumented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export class CodegenGraphqlFormGeneratorResult
/**
* writes the components to a given directory
*/
writeToDirectory = async (directoryPath: string) => {
writeToDirectory = async (
directoryPath: string,
// TODO: update this type when Printer interface gets defined in platform-core.
log?: (message: string) => void
) => {
try {
await fs.stat(directoryPath);
} catch (e) {
Expand All @@ -25,9 +29,11 @@ export class CodegenGraphqlFormGeneratorResult
this.fileNameComponentMap
)) {
if (content) {
const fd = await fs.open(path.join(directoryPath, fileName), 'w+');
const filePath = path.join(directoryPath, fileName);
const fd = await fs.open(filePath, 'w+');
try {
await fd.writeFile(content);
log?.(`File written: ${path.relative(process.cwd(), filePath)}`);
} finally {
await fd.close();
}
Expand Down
6 changes: 5 additions & 1 deletion packages/form-generator/src/graphql_form_generator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export type GraphqlGenerationResult = {
writeToDirectory: (directoryPath: string) => Promise<void>;
writeToDirectory: (
directoryPath: string,
// TODO: update this type when Printer interface gets defined in platform-core.
log?: (message: string) => void
) => Promise<void>;
};
export type FormGenerationOptions = {
models?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,22 @@ void describe('LocalCodegenGraphqlFormGenerator', () => {
stat: async () => ({}),
close: async () => undefined,
}));
await output.writeToDirectory('./');
const mockLog = mock.fn();
await output.writeToDirectory('./', (message) => mockLog(message));
const writeArgs = fsMock.mock.calls.flatMap((c) => c.arguments[0]);
assert(writeArgs.some((e) => /utils\.[jt]s[x]?/.test(e.toString())));
const utilFSWriteArgs = writeArgs.filter((e) =>
/utils\.[jt]s[x]?/.test(e.toString())
);

assert(utilFSWriteArgs.length > 0);

utilFSWriteArgs.forEach((fileName) => {
assert(
mockLog.mock.calls.some(({ arguments: [logMessage] }) =>
new RegExp(`^File written: ${fileName as string}$`).test(logMessage)
)
);
});
});
void it('generates index file', async () => {
const models = ['Post', 'Author', 'Foo'];
Expand All @@ -88,9 +101,19 @@ void describe('LocalCodegenGraphqlFormGenerator', () => {
stat: async () => ({}),
close: async () => undefined,
}));
await output.writeToDirectory('./');
const mockLog = mock.fn();
await output.writeToDirectory('./', (message) => mockLog(message));
const writeArgs = fsMock.mock.calls.flatMap((c) => c.arguments[0]);
assert(writeArgs.includes('index.js'));

const logArgs: string[] = mockLog.mock.calls.flatMap(
(c) => c.arguments[0]
);
assert(
logArgs.some((logMessage) =>
new RegExp('^File written: index.js$').test(logMessage)
)
);
});
});
void describe('filtering', () => {
Expand Down Expand Up @@ -126,13 +149,21 @@ void describe('LocalCodegenGraphqlFormGenerator', () => {
stat: async () => ({}),
close: async () => undefined,
}));
await output.writeToDirectory('./');
const mockLog = mock.fn();
await output.writeToDirectory('./', (message) => mockLog(message));
const writeArgs = fsMock.mock.calls.flatMap((c) => c.arguments[0]);
const logMessages = mockLog.mock.calls.flatMap((c) => c.arguments[0]);
assert(
models.every((m) => {
return writeArgs.some((arg) =>
const didWriteFile = writeArgs.some((arg) =>
new RegExp(`${m}(Update|Create)Form.d.ts`).test(arg.toString())
);
const didLogMessage = logMessages.some((logMessage) =>
new RegExp(`^File written: ${m}(Update|Create)Form.d.ts$`).test(
logMessage.toString()
)
);
return didWriteFile && didLogMessage;
})
);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/model-generator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export type GenerateOptions = GenerateGraphqlCodegenOptions | GenerateModelsOpti

// @public (undocumented)
export type GenerationResult = {
writeToDirectory: (directoryPath: string) => Promise<void>;
writeToDirectory: (directoryPath: string, log?: (message: string) => void) => Promise<void>;
getResults: () => Promise<Record<string, string>>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import assert from 'assert';

void describe('AppsyncGraphqlDocumentGenerationResult', () => {
void it('writes a map of files to disk', async () => {
const writeMock = mock.method(fs, 'writeFile');
const writeFileMock = mock.method(fs, 'writeFile');
const logMock = mock.fn();
mock.method(fs, 'mkdir').mock.mockImplementation(async () => null);
writeMock.mock.mockImplementation(async () => null);
writeFileMock.mock.mockImplementation(async () => null);
const filePathWithDir = path.join('a-third', 'fake-file', '.type');
const files: Record<string, string> = {
'fake-file': 'my \n fake file \n contents',
Expand All @@ -19,14 +20,24 @@ void describe('AppsyncGraphqlDocumentGenerationResult', () => {
const result = new AppsyncGraphqlGenerationResult(files);
const directory = './fake-dir';

await result.writeToDirectory(directory);
await result.writeToDirectory(directory, (message) => logMock(message));

Object.entries(files).forEach(([fileName, content]) => {
const resolvedName = path.resolve(path.join(directory, fileName));
const writeCall = writeMock.mock.calls.find(
const writeFileCall = writeFileMock.mock.calls.find(
({ arguments: [f] }) => resolvedName === f
);
assert.deepEqual(writeCall?.arguments, [resolvedName, content]);
const logCall = logMock.mock.calls.find(({ arguments: [message] }) =>
message.includes(path.join(directory, fileName))
);
assert.deepEqual(writeFileCall?.arguments, [resolvedName, content]);
assert.strictEqual(
logCall?.arguments[0],
`File written: ${path.relative(
'.',
path.resolve(path.join(directory, fileName))
)}`
);
});
});
});
Loading

0 comments on commit 4c1485a

Please sign in to comment.