Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(amplify-alpha): support custom response headers in monorepo structures #31771

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
62 changes: 62 additions & 0 deletions packages/@aws-cdk/aws-amplify-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,68 @@ const amplifyApp = new amplify.App(this, 'App', {
});
```

If the app uses a monorepo structure, define which appRoot from the build spec the custom response headers should apply to by using the `appRoot` property:

```ts
import * as codebuild from 'aws-cdk-lib/aws-codebuild';

const amplifyApp = new amplify.App(this, 'App', {
sourceCodeProvider: new amplify.GitHubSourceCodeProvider({
owner: '<user>',
repository: '<repo>',
oauthToken: SecretValue.secretsManager('my-github-token'),
}),
buildSpec: codebuild.BuildSpec.fromObjectToYaml({
version: '1.0',
applications: [
{
appRoot: 'frontend',
frontend: {
phases: {
preBuild: {
commands: ['npm install'],
},
build: {
commands: ['npm run build'],
},
},
},
},
{
appRoot: 'backend',
backend: {
phases: {
preBuild: {
commands: ['npm install'],
},
build: {
commands: ['npm run build'],
},
},
},
},
],
}),
customResponseHeaders: [
{
appRoot: 'frontend',
pattern: '*.json',
headers: {
'custom-header-name-1': 'custom-header-value-1',
'custom-header-name-2': 'custom-header-value-2',
},
},
{
appRoot: 'backend',
pattern: '/path/*',
headers: {
'custom-header-name-1': 'custom-header-value-2',
},
},
],
});
```

## Configure server side rendering when hosting app

Setting the `platform` field on the Amplify `App` construct can be used to control whether the app will host only static assets or server side rendered assets in addition to static. By default, the value is set to `WEB` (static only), however, server side rendering can be turned on by setting to `WEB_COMPUTE` as follows:
Expand Down
28 changes: 21 additions & 7 deletions packages/@aws-cdk/aws-amplify-alpha/lib/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@ export class CustomRule {
* Custom response header of an Amplify App.
*/
export interface CustomResponseHeader {
/**
* If the app uses a monorepo structure, the appRoot from the build spec to apply the custom headers to.
* @default - The appRoot is omitted in the custom headers output.
*/
readonly appRoot?: string;

/**
* These custom headers will be applied to all URL file paths that match this pattern.
*/
Expand All @@ -539,16 +545,24 @@ export interface CustomResponseHeader {
}

function renderCustomResponseHeaders(customHeaders: CustomResponseHeader[]): string {
const yaml = [
'customHeaders:',
];
const hasAppRoot = customHeaders[0].appRoot !== undefined;
const yaml = [hasAppRoot ? 'applications:' : 'customHeaders:'];

for (const customHeader of customHeaders) {
yaml.push(` - pattern: "${customHeader.pattern}"`);
yaml.push(' headers:');
if ((customHeader.appRoot !== undefined) !== hasAppRoot) {
throw new Error('appRoot must be either be present or absent across all custom response headers');
}

const baseIndentation = ' '.repeat(hasAppRoot ? 6 : 2);
if (hasAppRoot) {
yaml.push(` - appRoot: ${customHeader.appRoot}`);
yaml.push(' customHeaders:');
}
yaml.push(`${baseIndentation}- pattern: "${customHeader.pattern}"`);
yaml.push(`${baseIndentation} headers:`);
for (const [key, value] of Object.entries(customHeader.headers)) {
yaml.push(` - key: "${key}"`);
yaml.push(` value: "${value}"`);
yaml.push(`${baseIndentation} - key: "${key}"`);
yaml.push(`${baseIndentation} value: "${value}"`);
}
}

Expand Down
105 changes: 105 additions & 0 deletions packages/@aws-cdk/aws-amplify-alpha/test/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,111 @@ test('with custom headers', () => {
});
});

test('with custom headers in a monorepo structure', () => {
// WHEN
new amplify.App(stack, 'App', {
sourceCodeProvider: new amplify.GitHubSourceCodeProvider({
owner: 'aws',
repository: 'aws-cdk',
oauthToken: SecretValue.unsafePlainText('secret'),
}),
buildSpec: codebuild.BuildSpec.fromObjectToYaml({
version: '1.0',
applications: [
{
appRoot: 'frontend',
frontend: {
phases: {
preBuild: {
commands: ['npm install'],
},
build: {
commands: ['npm run build'],
},
},
},
},
{
appRoot: 'backend',
backend: {
phases: {
preBuild: {
commands: ['npm install'],
},
build: {
commands: ['npm run build'],
},
},
},
},
],
}),
customResponseHeaders: [
{
appRoot: 'frontend',
pattern: '*.json',
headers: {
'custom-header-name-1': 'custom-header-value-1',
'custom-header-name-2': 'custom-header-value-2',
},
},
{
appRoot: 'backend',
pattern: '/path/*',
headers: {
'custom-header-name-1': 'custom-header-value-2',
'x-aws-url-suffix': `this-is-the-suffix-${stack.urlSuffix}`,
},
},
],
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Amplify::App', {
CustomHeaders: {
'Fn::Join': [
'',
[
'applications:\n - appRoot: frontend\n customHeaders:\n - pattern: "*.json"\n headers:\n - key: "custom-header-name-1"\n value: "custom-header-value-1"\n - key: "custom-header-name-2"\n value: "custom-header-value-2"\n - appRoot: backend\n customHeaders:\n - pattern: "/path/*"\n headers:\n - key: "custom-header-name-1"\n value: "custom-header-value-2"\n - key: "x-aws-url-suffix"\n value: "this-is-the-suffix-',
{
Ref: 'AWS::URLSuffix',
},
'"\n',
],
],
},
});
});

test('error with inconsistent appRoot in custom headers', () => {
// WHEN
expect(() => {
new amplify.App(stack, 'App', {
sourceCodeProvider: new amplify.GitHubSourceCodeProvider({
owner: 'aws',
repository: 'aws-cdk',
oauthToken: SecretValue.unsafePlainText('secret'),
}),
customResponseHeaders: [
{
pattern: '*.json',
headers: {
'custom-header-name-1': 'custom-header-value-1',
'custom-header-name-2': 'custom-header-value-2',
},
},
{
appRoot: 'backend',
pattern: '/path/*',
headers: {
'custom-header-name-1': 'custom-header-value-2',
},
},
],
});
}).toThrow('appRoot must be either be present or absent across all custom response headers');
});

test('create a statically hosted app by default', () => {
// WHEN
new amplify.App(stack, 'App', {});
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading