forked from projen/projen
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathawscdk-app-ts.ts
255 lines (206 loc) · 6.82 KB
/
awscdk-app-ts.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import * as path from 'path';
import * as fs from 'fs-extra';
import { Component } from './component';
import { JsonFile } from './json';
import { TaskCategory } from './tasks';
import { TypeScriptAppProject, TypeScriptProjectOptions } from './typescript';
export enum CdkApprovalLevel {
/**
* Approval is never required
*/
NEVER = 'never',
/**
* Requires approval on any IAM or security-group-related change
*/
ANY_CHANGE = 'any-change',
/**
* Requires approval when IAM statements or traffic rules are added; removals don't require approval
*/
BROADENING = 'broadening',
}
export interface AwsCdkTypeScriptAppOptions extends TypeScriptProjectOptions {
/**
* AWS CDK version to use.
*
* @default "1.73.0"
*/
readonly cdkVersion: string;
/**
* Use pinned version instead of caret version for CDK.
*
* You can use this to prevent yarn to mix versions for your CDK dependencies and to prevent auto-updates.
* If you use experimental features this will let you define the moment you include breaking changes.
*
* @default false
*/
readonly cdkVersionPinning?: boolean;
/**
* Which AWS CDK modules (those that start with "@aws-cdk/") this app uses.
*/
readonly cdkDependencies?: string[];
/**
* Additional context to include in `cdk.json`.
*/
readonly context?: { [key: string]: string };
/**
* The CDK app's entrypoint (relative to the source directory, which is
* "src" by default).
*
* @default "main.ts"
*/
readonly appEntrypoint?: string;
/**
* To protect you against unintended changes that affect your security posture,
* the AWS CDK Toolkit prompts you to approve security-related changes before deploying them.
*
* @default CdkApprovalLevel.BROADENING
*/
readonly requireApproval?: CdkApprovalLevel;
}
/**
* AWS CDK app in TypeScript
*
* @pjid awscdk-app-ts
*/
export class AwsCdkTypeScriptApp extends TypeScriptAppProject {
/**
* The CDK version this app is using.
*/
public readonly cdkVersion: string;
/**
* Contents of `cdk.json`.
*/
public readonly cdkConfig: any;
/**
* The CDK app entrypoint
*/
public readonly appEntrypoint: string;
constructor(options: AwsCdkTypeScriptAppOptions) {
super({
...options,
sampleCode: false,
});
// encode a hidden assumption further down the chain
if (this.srcdir !== 'src') {
throw new Error('sources are expected under the "src" directory');
}
// encode a hidden assumption further down the chain
if (this.testdir !== 'test') {
throw new Error('test sources are expected under the "test" directory');
}
this.appEntrypoint = options.appEntrypoint ?? 'main.ts';
this.cdkVersion = options.cdkVersionPinning ? options.cdkVersion : `^${options.cdkVersion}`;
// CLI
this.addDevDeps(this.formatModuleSpec('aws-cdk'));
this.addCdkDependency('@aws-cdk/assert');
this.addCdkDependency('@aws-cdk/core');
this.addCdkDependency(...options.cdkDependencies ?? []);
const synth = this.addTask('synth', {
description: 'Synthesizes your cdk app into cdk.out (part of "yarn build")',
category: TaskCategory.BUILD,
exec: 'cdk synth',
});
this.addTask('deploy', {
description: 'Deploys your CDK app to the AWS cloud',
category: TaskCategory.RELEASE,
exec: 'cdk deploy',
});
this.addTask('destroy', {
description: 'Destroys your cdk app in the AWS cloud',
category: TaskCategory.RELEASE,
exec: 'cdk destroy',
});
this.addTask('diff', {
description: 'Diffs the currently deployed app against your code',
category: TaskCategory.MISC,
exec: 'cdk diff',
});
// no compile step because we do all of it in typescript directly
this.compileTask.reset();
this.removeScript('watch'); // because we use ts-node
// add synth to the build
this.buildTask.spawn(synth);
this.cdkConfig = {
app: `npx ts-node --prefer-ts-exts ${path.join(this.srcdir, this.appEntrypoint)}`,
};
if (options.context) {
this.cdkConfig.context = { ...options.context };
}
if (options.requireApproval) {
this.cdkConfig.requireApproval = options.requireApproval;
}
this.gitignore.exclude('cdk.out/');
this.gitignore.exclude('.cdk.staging/');
this.gitignore.exclude('.parcel-cache/');
this.npmignore?.exclude('cdk.out/');
this.npmignore?.exclude('.cdk.staging/');
if (this.tsconfig) {
this.tsconfig.exclude.push('cdk.out');
}
this.addDevDeps('ts-node');
new JsonFile(this, 'cdk.json', {
obj: this.cdkConfig,
});
if (options.sampleCode ?? true) {
new SampleCode(this);
}
}
/**
* Adds an AWS CDK module dependencies
* @param modules The list of modules to depend on
*/
public addCdkDependency(...modules: string[]) {
this.addDeps(...modules.map(m => this.formatModuleSpec(m)));
}
private formatModuleSpec(module: string): string {
return `${module}@${this.cdkVersion}`;
}
}
class SampleCode extends Component {
private readonly appProject: AwsCdkTypeScriptApp;
constructor(project: AwsCdkTypeScriptApp) {
super(project);
this.appProject = project;
}
public synthesize() {
const outdir = this.project.outdir;
const srcdir = path.join(outdir, this.appProject.srcdir);
if (fs.pathExistsSync(srcdir) && fs.readdirSync(srcdir).filter(x => x.endsWith('.ts'))) {
return;
}
const srcCode = `import { App, Construct, Stack, StackProps } from '@aws-cdk/core';
export class MyStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);
// define resources here...
}
}
// for development, use account/region from cdk cli
const devEnv = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
const app = new App();
new MyStack(app, 'my-stack-dev', { env: devEnv });
// new MyStack(app, 'my-stack-prod', { env: prodEnv });
app.synth();`;
fs.mkdirpSync(srcdir);
fs.writeFileSync(path.join(srcdir, this.appProject.appEntrypoint), srcCode);
const testdir = path.join(outdir, this.appProject.testdir);
if (fs.pathExistsSync(testdir) && fs.readdirSync(testdir).filter(x => x.endsWith('.ts'))) {
return;
}
const appEntrypointName = path.basename(this.appProject.appEntrypoint, '.ts');
const testCode = `import '@aws-cdk/assert/jest';
import { App } from '@aws-cdk/core';
import { MyStack } from '../src/${appEntrypointName}';
test('Snapshot', () => {
const app = new App();
const stack = new MyStack(app, 'test');
expect(stack).not.toHaveResource('AWS::S3::Bucket');
expect(app.synth().getStackArtifact(stack.artifactId).template).toMatchSnapshot();
});`;
fs.mkdirpSync(testdir);
fs.writeFileSync(path.join(testdir, `${appEntrypointName}.test.ts`), testCode);
}
}