Skip to content

Commit

Permalink
feat: sandbox deployment events (#254)
Browse files Browse the repository at this point in the history
feat: add deployment events to sandbox
  • Loading branch information
sdstolworthy authored Sep 27, 2023
1 parent 0b2d50d commit ee3d55f
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 289 deletions.
6 changes: 6 additions & 0 deletions .changeset/moody-rats-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@aws-amplify/sandbox': minor
'@aws-amplify/backend-cli': minor
---

Add event handlers for Sandbox
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
TestCommandRunner,
} from '../../../test-utils/command_runner.js';
import assert from 'node:assert';
import { ClientConfigGeneratorAdapter } from './client_config_generator_adapter.js';
import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js';
import { ClientConfigGeneratorAdapter } from '../../../client-config/client_config_generator_adapter.js';

void describe('generate config command', () => {
const clientConfigGeneratorAdapter = new ClientConfigGeneratorAdapter(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
import { ClientConfigFormat } from '@aws-amplify/client-config';
import { ClientConfigGeneratorAdapter } from './client_config_generator_adapter.js';
import { BackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js';
import { ClientConfigGeneratorAdapter } from '../../../client-config/client_config_generator_adapter.js';

export type GenerateConfigCommandOptions = {
stack: string | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { CommandModule } from 'yargs';
import { GenerateCommand } from './generate_command.js';
import { GenerateConfigCommand } from './config/generate_config_command.js';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { ClientConfigGeneratorAdapter } from './config/client_config_generator_adapter.js';
import { GenerateFormsCommand } from './forms/generate_forms_command.js';
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 { ClientConfigGeneratorAdapter } from '../../client-config/client_config_generator_adapter.js';
import { FormGenerationHandler } from './forms/form_generation_handler.js';
import { BackendOutputClient } from '@aws-amplify/deployed-backend-client';
import { GenerateApiCodeAdapter } from './graphql-client-code/generate_api_code_adapter.js';
Expand Down
41 changes: 21 additions & 20 deletions packages/cli/src/commands/sandbox/sandbox_command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
} from '../../test-utils/command_runner.js';
import assert from 'node:assert';
import fs from 'fs';
import { SandboxCommand } from './sandbox_command.js';
import { EventHandler, SandboxCommand } from './sandbox_command.js';
import { createSandboxCommand } from './sandbox_command_factory.js';
import { SandboxDeleteCommand } from './sandbox-delete/sandbox_delete_command.js';
import { Sandbox, SandboxSingletonFactory } from '@aws-amplify/sandbox';
import { ClientConfigGeneratorAdapter } from '../../client-config/client_config_generator_adapter.js';
import { createSandboxSecretCommand } from './sandbox-secret/sandbox_secret_command_factory.js';

void describe('sandbox command factory', () => {
Expand All @@ -23,6 +24,9 @@ void describe('sandbox command', () => {
let commandRunner: TestCommandRunner;
let sandbox: Sandbox;
let sandboxStartMock = mock.fn<typeof sandbox.start>();
const mockGenerate =
mock.fn<ClientConfigGeneratorAdapter['generateClientConfigToFile']>();
const generationMock = mock.fn<EventHandler>();

beforeEach(async () => {
const sandboxFactory = new SandboxSingletonFactory(() =>
Expand All @@ -32,13 +36,25 @@ void describe('sandbox command', () => {

sandboxStartMock = mock.method(sandbox, 'start', () => Promise.resolve());
const sandboxDeleteCommand = new SandboxDeleteCommand(sandboxFactory);
const sandboxCommand = new SandboxCommand(sandboxFactory, [
sandboxDeleteCommand,
createSandboxSecretCommand(),
]);

const sandboxCommand = new SandboxCommand(
sandboxFactory,
[sandboxDeleteCommand, createSandboxSecretCommand()],
() => ({
successfulDeployment: [generationMock],
})
);
const parser = yargs().command(sandboxCommand as unknown as CommandModule);
commandRunner = new TestCommandRunner(parser);
sandboxStartMock.mock.resetCalls();
mockGenerate.mock.resetCalls();
});

void it('registers a callback on the "successfulDeployment" event', async () => {
const mockOn = mock.method(sandbox, 'on');
await commandRunner.runCommand('sandbox');
assert.equal(mockOn.mock.calls[0].arguments[0], 'successfulDeployment');
assert.equal(mockOn.mock.calls[0].arguments[1], generationMock);
});

void it('starts sandbox without any additional flags', async () => {
Expand All @@ -56,21 +72,6 @@ void describe('sandbox command', () => {
);
});

void it('starts sandbox with user provided output directory for client config', async () => {
await commandRunner.runCommand(
'sandbox --outDir test/location --format js'
);
assert.equal(sandboxStartMock.mock.callCount(), 1);
assert.deepStrictEqual(
sandboxStartMock.mock.calls[0].arguments[0].clientConfigFilePath,
'test/location'
);
assert.deepStrictEqual(
sandboxStartMock.mock.calls[0].arguments[0].format,
'js'
);
});

void it('shows available options in help output', async () => {
const output = await commandRunner.runCommand('sandbox --help');
assert.match(output, /--name/);
Expand Down
49 changes: 41 additions & 8 deletions packages/cli/src/commands/sandbox/sandbox_command.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
import { ClientConfigFormat } from '@aws-amplify/client-config';
import fs from 'fs';
import { AmplifyPrompter } from '../prompter/amplify_prompts.js';
import { SandboxSingletonFactory } from '@aws-amplify/sandbox';
import {
ClientConfigFormat,
getClientConfigPath,
} from '@aws-amplify/client-config';

export type SandboxCommandOptions = {
dirToWatch: string | undefined;
Expand All @@ -13,6 +16,22 @@ export type SandboxCommandOptions = {
profile: string | undefined;
};

export type EventHandler = () => void;

export type SandboxEventHandlers = {
successfulDeployment: EventHandler[];
};

export type SandboxEventHandlerParams = {
appName?: string;
outDir?: string;
format?: ClientConfigFormat;
};

export type SandboxEventHandlerCreator = (
params: SandboxEventHandlerParams
) => SandboxEventHandlers;

/**
* Command that starts sandbox.
*/
Expand All @@ -36,7 +55,8 @@ export class SandboxCommand
*/
constructor(
private readonly sandboxFactory: SandboxSingletonFactory,
private readonly sandboxSubCommands: CommandModule[]
private readonly sandboxSubCommands: CommandModule[],
private readonly sandboxEventHandlerCreator?: SandboxEventHandlerCreator
) {
this.command = 'sandbox';
this.describe = 'Starts sandbox, watch mode for amplify deployments';
Expand All @@ -48,15 +68,28 @@ export class SandboxCommand
handler = async (
args: ArgumentsCamelCase<SandboxCommandOptions>
): Promise<void> => {
const sandbox = await this.sandboxFactory.getInstance();
this.appName = args.name;
await (
await this.sandboxFactory.getInstance()
).start({
const eventHandlers = this.sandboxEventHandlerCreator?.({
appName: args.name,
format: args.format,
outDir: args.outDir,
});
if (eventHandlers) {
Object.entries(eventHandlers).forEach(([event, handlers]) => {
handlers.forEach((handler) => sandbox.on(event, handler));
});
}
const watchExclusions = args.exclude ?? [];
const clientConfigWritePath = await getClientConfigPath(
args.outDir,
args.format
);
watchExclusions.push(clientConfigWritePath);
await sandbox.start({
dir: args.dirToWatch,
exclude: args.exclude,
exclude: watchExclusions,
name: args.name,
format: args.format,
clientConfigFilePath: args.outDir,
profile: args.profile,
});
process.once('SIGINT', () => void this.sigIntHandler());
Expand Down
45 changes: 38 additions & 7 deletions packages/cli/src/commands/sandbox/sandbox_command_factory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { CommandModule } from 'yargs';

import { SandboxCommand, SandboxCommandOptions } from './sandbox_command.js';
import {
SandboxCommand,
SandboxCommandOptions,
SandboxEventHandlerCreator,
} from './sandbox_command.js';
import { SandboxSingletonFactory } from '@aws-amplify/sandbox';
import { SandboxDeleteCommand } from './sandbox-delete/sandbox_delete_command.js';
import { SandboxIdResolver } from './sandbox_id_resolver.js';
import { CwdPackageJsonLoader } from '../../cwd_package_json_loader.js';
import { ClientConfigGeneratorAdapter } from '../../client-config/client_config_generator_adapter.js';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { LocalAppNameResolver } from '../../backend-identifier/local_app_name_resolver.js';
import { createSandboxSecretCommand } from './sandbox-secret/sandbox_secret_command_factory.js';

Expand All @@ -15,13 +20,39 @@ export const createSandboxCommand = (): CommandModule<
object,
SandboxCommandOptions
> => {
const credentialProvider = fromNodeProviderChain();
const sandboxIdResolver = new SandboxIdResolver(
new LocalAppNameResolver(new CwdPackageJsonLoader())
);
const sandboxFactory = new SandboxSingletonFactory(sandboxIdResolver.resolve);

return new SandboxCommand(sandboxFactory, [
new SandboxDeleteCommand(sandboxFactory),
createSandboxSecretCommand(),
]);
const clientConfigGeneratorAdapter = new ClientConfigGeneratorAdapter(
credentialProvider
);
const getBackendIdentifier = async (appName?: string) => {
const sandboxId = appName ?? (await sandboxIdResolver.resolve());
return { backendId: sandboxId, branchName: 'sandbox' };
};
const sandboxEventHandlerCreator: SandboxEventHandlerCreator = ({
appName,
outDir,
format,
}) => {
return {
successfulDeployment: [
async () => {
const id = await getBackendIdentifier(appName);
await clientConfigGeneratorAdapter.generateClientConfigToFile(
id,
outDir,
format
);
},
],
};
};
return new SandboxCommand(
sandboxFactory,
[new SandboxDeleteCommand(sandboxFactory), createSandboxSecretCommand()],
sandboxEventHandlerCreator
);
};
1 change: 1 addition & 0 deletions packages/client-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './client-config-types/client_config.js';
export * from './client-config-types/auth_client_config.js';
export * from './client-config-types/graphql_client_config.js';
export * from './client-config-types/storage_client_config.js';
export * from './paths/get_client_config_path.js';
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export class ProcessController {
if (typeof currentInteraction.payload === 'string') {
if (currentInteraction.payload === CONTROL_C) {
if (process.platform.startsWith('win')) {
// Wait X milliseconds before sending kill in hopes of draining the node event queue
await new Promise((resolve) => setTimeout(resolve, 5000));
// turns out killing child process on Windows is a huge PITA
// https://stackoverflow.com/questions/23706055/why-can-i-not-kill-my-child-process-in-nodejs-on-windows
// https://github.com/sindresorhus/execa#killsignal-options
Expand Down
9 changes: 7 additions & 2 deletions packages/sandbox/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,33 @@
```ts

/// <reference types="node" />

import { ClientConfigFormat } from '@aws-amplify/client-config';
import EventEmitter from 'events';

// @public
export type Sandbox = {
start: (options: SandboxOptions) => Promise<void>;
stop: () => Promise<void>;
delete: (options: SandboxDeleteOptions) => Promise<void>;
};
} & EventEmitter;

// @public (undocumented)
export type SandboxDeleteOptions = {
name?: string;
};

// @public (undocumented)
export type SandboxEvents = 'successfulDeployment';

// @public (undocumented)
export type SandboxOptions = {
dir?: string;
exclude?: string[];
name?: string;
format?: ClientConfigFormat;
profile?: string;
clientConfigFilePath?: string;
};

// @public
Expand Down
34 changes: 0 additions & 34 deletions packages/sandbox/src/config/client_config_generator_adapter.ts

This file was deleted.

Loading

0 comments on commit ee3d55f

Please sign in to comment.