@awsless/awsless 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +2339 -1129
- package/dist/bin.js +2336 -1126
- package/dist/index.cjs +19 -22
- package/dist/index.d.ts +906 -917
- package/dist/index.js +15 -20
- package/package.json +4 -1
package/dist/bin.js
CHANGED
|
@@ -6,13 +6,6 @@ import {
|
|
|
6
6
|
// src/cli/program.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
9
|
-
// src/app.ts
|
|
10
|
-
import { App as App4, DefaultStackSynthesizer } from "aws-cdk-lib";
|
|
11
|
-
|
|
12
|
-
// src/stack.ts
|
|
13
|
-
import { Arn, Stack } from "aws-cdk-lib";
|
|
14
|
-
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
15
|
-
|
|
16
9
|
// src/cli/style.ts
|
|
17
10
|
import chalk from "chalk";
|
|
18
11
|
var symbol = {
|
|
@@ -64,7 +57,7 @@ var flushDebug = () => {
|
|
|
64
57
|
// src/util/param.ts
|
|
65
58
|
import { DeleteParameterCommand, GetParameterCommand, GetParametersByPathCommand, ParameterType, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
66
59
|
var configParameterPrefix = (config) => {
|
|
67
|
-
return
|
|
60
|
+
return `/awsless/${config.name}/${config.stage}`;
|
|
68
61
|
};
|
|
69
62
|
var Params = class {
|
|
70
63
|
constructor(config) {
|
|
@@ -145,82 +138,408 @@ var Params = class {
|
|
|
145
138
|
}
|
|
146
139
|
};
|
|
147
140
|
|
|
148
|
-
// src/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
// src/formation/util.ts
|
|
142
|
+
import { paramCase, pascalCase } from "change-case";
|
|
143
|
+
var ref = (logicalId) => {
|
|
144
|
+
return { Ref: logicalId };
|
|
145
|
+
};
|
|
146
|
+
var sub = (value, params) => {
|
|
147
|
+
if (params) {
|
|
148
|
+
return { "Fn::Sub": [value, params] };
|
|
149
|
+
}
|
|
150
|
+
return { "Fn::Sub": value };
|
|
151
|
+
};
|
|
152
|
+
var getAtt = (logicalId, attr) => {
|
|
153
|
+
return { "Fn::GetAtt": [logicalId, attr] };
|
|
154
|
+
};
|
|
155
|
+
var importValue = (name) => {
|
|
156
|
+
return { "Fn::ImportValue": name };
|
|
157
|
+
};
|
|
158
|
+
var formatLogicalId = (id) => {
|
|
159
|
+
return pascalCase(id);
|
|
160
|
+
};
|
|
161
|
+
var formatName = (name) => {
|
|
162
|
+
return paramCase(name);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/formation/resource.ts
|
|
166
|
+
var Resource = class {
|
|
167
|
+
constructor(type, logicalId, children = []) {
|
|
168
|
+
this.type = type;
|
|
169
|
+
this.children = children;
|
|
170
|
+
this.logicalId = formatLogicalId(`${logicalId}-${type.replace(/^AWS::/, "")}`);
|
|
171
|
+
}
|
|
172
|
+
logicalId;
|
|
173
|
+
deps = /* @__PURE__ */ new Set();
|
|
174
|
+
dependsOn(...dependencies) {
|
|
175
|
+
for (const dependency of dependencies) {
|
|
176
|
+
this.deps.add(dependency);
|
|
161
177
|
}
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
attr(name, value) {
|
|
181
|
+
if (typeof value === "undefined") {
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
[name]: value
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
toJSON() {
|
|
189
|
+
return {
|
|
190
|
+
[this.logicalId]: {
|
|
191
|
+
Type: this.type,
|
|
192
|
+
DependsOn: [...this.deps].map((dep) => dep.logicalId),
|
|
193
|
+
Properties: this.properties()
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var Group = class {
|
|
199
|
+
constructor(children) {
|
|
200
|
+
this.children = children;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/formation/resource/iam/inline-policy.ts
|
|
205
|
+
var InlinePolicy = class {
|
|
206
|
+
constructor(name, props = {}) {
|
|
207
|
+
this.name = name;
|
|
208
|
+
this.statements = props.statements || [];
|
|
209
|
+
}
|
|
210
|
+
statements;
|
|
211
|
+
addStatement(...statements) {
|
|
212
|
+
this.statements.push(...statements.flat());
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
toJSON() {
|
|
216
|
+
return {
|
|
217
|
+
PolicyName: this.name,
|
|
218
|
+
PolicyDocument: {
|
|
219
|
+
Version: "2012-10-17",
|
|
220
|
+
Statement: this.statements.map((statement) => ({
|
|
221
|
+
Effect: statement.effect || "Allow",
|
|
222
|
+
Action: statement.actions,
|
|
223
|
+
Resource: statement.resources
|
|
224
|
+
}))
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/formation/resource/iam/managed-policy.ts
|
|
231
|
+
var ManagedPolicy = class {
|
|
232
|
+
constructor(arn) {
|
|
233
|
+
this.arn = arn;
|
|
234
|
+
}
|
|
235
|
+
static fromAwsManagedPolicyName(name) {
|
|
236
|
+
const arn = sub("arn:${AWS::Partition}:iam::aws:policy/service-role/" + name);
|
|
237
|
+
return new ManagedPolicy(arn);
|
|
238
|
+
}
|
|
239
|
+
static fromManagedPolicyArn(arn) {
|
|
240
|
+
return new ManagedPolicy(arn);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/formation/resource/iam/role.ts
|
|
245
|
+
var Role = class extends Resource {
|
|
246
|
+
constructor(logicalId, props = {}) {
|
|
247
|
+
super("AWS::IAM::Role", logicalId);
|
|
248
|
+
this.props = props;
|
|
249
|
+
this.name = formatName(logicalId);
|
|
250
|
+
}
|
|
251
|
+
name;
|
|
252
|
+
inlinePolicies = /* @__PURE__ */ new Set();
|
|
253
|
+
managedPolicies = /* @__PURE__ */ new Set();
|
|
254
|
+
get arn() {
|
|
255
|
+
return getAtt(this.logicalId, "Arn");
|
|
256
|
+
}
|
|
257
|
+
addManagedPolicy(...policies) {
|
|
258
|
+
for (const policy of policies) {
|
|
259
|
+
this.managedPolicies.add(policy);
|
|
260
|
+
}
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
addInlinePolicy(...policies) {
|
|
264
|
+
for (const policy of policies) {
|
|
265
|
+
this.inlinePolicies.add(policy);
|
|
266
|
+
}
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
properties() {
|
|
270
|
+
return {
|
|
271
|
+
...this.props.assumedBy ? {
|
|
272
|
+
AssumeRolePolicyDocument: {
|
|
273
|
+
Version: "2012-10-17",
|
|
274
|
+
Statement: [{
|
|
275
|
+
Action: "sts:AssumeRole",
|
|
276
|
+
Effect: "Allow",
|
|
277
|
+
Principal: {
|
|
278
|
+
Service: this.props.assumedBy
|
|
279
|
+
}
|
|
280
|
+
}]
|
|
281
|
+
}
|
|
282
|
+
} : {},
|
|
283
|
+
ManagedPolicyArns: [...this.managedPolicies].map((policy) => policy.arn),
|
|
284
|
+
Policies: [...this.inlinePolicies].map((policy) => policy.toJSON())
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// src/formation/resource/lambda/function.ts
|
|
290
|
+
var Function = class extends Resource {
|
|
291
|
+
constructor(logicalId, props) {
|
|
292
|
+
const policy = new InlinePolicy(logicalId);
|
|
293
|
+
const role = new Role(logicalId, {
|
|
294
|
+
assumedBy: "lambda.amazonaws.com"
|
|
295
|
+
});
|
|
296
|
+
role.addInlinePolicy(policy);
|
|
297
|
+
role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AWSLambdaBasicExecutionRole"));
|
|
298
|
+
super("AWS::Lambda::Function", logicalId, [
|
|
299
|
+
role,
|
|
300
|
+
props.code
|
|
301
|
+
]);
|
|
302
|
+
this.props = props;
|
|
303
|
+
this.dependsOn(role);
|
|
304
|
+
this.role = role;
|
|
305
|
+
this.policy = policy;
|
|
306
|
+
this.name = formatName(this.props.name || logicalId);
|
|
307
|
+
this.environmentVariables = props.environment ? { ...props.environment } : {};
|
|
308
|
+
}
|
|
309
|
+
name;
|
|
310
|
+
role;
|
|
311
|
+
policy;
|
|
312
|
+
environmentVariables;
|
|
313
|
+
addPermissions(...permissions) {
|
|
314
|
+
this.policy.addStatement(...permissions);
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
addEnvironment(name, value) {
|
|
318
|
+
this.environmentVariables[name] = value;
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
get id() {
|
|
322
|
+
return ref(this.logicalId);
|
|
323
|
+
}
|
|
324
|
+
get arn() {
|
|
325
|
+
return getAtt(this.logicalId, "Arn");
|
|
326
|
+
}
|
|
327
|
+
get permissions() {
|
|
328
|
+
return {
|
|
329
|
+
actions: [
|
|
330
|
+
"lambda:InvokeFunction",
|
|
331
|
+
"lambda:InvokeAsync"
|
|
332
|
+
],
|
|
333
|
+
resources: [this.arn]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
properties() {
|
|
337
|
+
return {
|
|
338
|
+
FunctionName: this.name,
|
|
339
|
+
MemorySize: this.props.memorySize?.toMegaBytes() ?? 128,
|
|
340
|
+
Runtime: this.props.runtime ?? "nodejs18.x",
|
|
341
|
+
Timeout: this.props.timeout?.toSeconds() ?? 10,
|
|
342
|
+
Architectures: [this.props.architecture ?? "arm64"],
|
|
343
|
+
Role: this.role.arn,
|
|
344
|
+
...this.props.code.toCodeJson(),
|
|
345
|
+
EphemeralStorage: {
|
|
346
|
+
Size: this.props.ephemeralStorageSize?.toMegaBytes() ?? 512
|
|
347
|
+
},
|
|
348
|
+
Environment: {
|
|
349
|
+
Variables: this.environmentVariables
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/formation/asset.ts
|
|
356
|
+
import { paramCase as paramCase2 } from "change-case";
|
|
357
|
+
var Asset = class {
|
|
358
|
+
constructor(type, id) {
|
|
359
|
+
this.type = type;
|
|
360
|
+
this.id = paramCase2(id);
|
|
361
|
+
}
|
|
362
|
+
id;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/formation/stack.ts
|
|
366
|
+
var Stack = class {
|
|
367
|
+
constructor(name, region) {
|
|
368
|
+
this.name = name;
|
|
369
|
+
this.region = region;
|
|
370
|
+
}
|
|
371
|
+
exports = /* @__PURE__ */ new Map();
|
|
372
|
+
resources = /* @__PURE__ */ new Set();
|
|
373
|
+
tags = /* @__PURE__ */ new Map();
|
|
374
|
+
assets = /* @__PURE__ */ new Set();
|
|
375
|
+
add(...resources) {
|
|
376
|
+
for (const item of resources) {
|
|
377
|
+
if (item instanceof Asset) {
|
|
378
|
+
this.assets.add(item);
|
|
379
|
+
} else {
|
|
380
|
+
this.add(...item.children);
|
|
381
|
+
if (item instanceof Resource) {
|
|
382
|
+
this.resources.add(item);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
export(name, value) {
|
|
389
|
+
name = formatName(name);
|
|
390
|
+
this.exports.set(name, value);
|
|
391
|
+
return this;
|
|
392
|
+
}
|
|
393
|
+
import(name) {
|
|
394
|
+
name = formatName(name);
|
|
395
|
+
if (!this.exports.has(name)) {
|
|
396
|
+
throw new Error(`Undefined export value: ${name}`);
|
|
397
|
+
}
|
|
398
|
+
return importValue(name);
|
|
399
|
+
}
|
|
400
|
+
tag(name, value) {
|
|
401
|
+
this.tags.set(name, value);
|
|
402
|
+
return this;
|
|
403
|
+
}
|
|
404
|
+
find(resourceType) {
|
|
405
|
+
return [...this.resources].filter((resource) => resource instanceof resourceType);
|
|
406
|
+
}
|
|
407
|
+
[Symbol.iterator]() {
|
|
408
|
+
return this.resources.values();
|
|
409
|
+
}
|
|
410
|
+
// get resources() {
|
|
411
|
+
// return [ ...this.list.values() ]
|
|
412
|
+
// }
|
|
413
|
+
get size() {
|
|
414
|
+
return this.resources.size;
|
|
415
|
+
}
|
|
416
|
+
toJSON() {
|
|
417
|
+
const resources = {};
|
|
418
|
+
const outputs = {};
|
|
419
|
+
for (const resource of this) {
|
|
420
|
+
Object.assign(resources, resource.toJSON());
|
|
421
|
+
}
|
|
422
|
+
for (const [name, value] of this.exports.entries()) {
|
|
423
|
+
Object.assign(outputs, {
|
|
424
|
+
[formatLogicalId(name)]: {
|
|
425
|
+
Export: { Name: name },
|
|
426
|
+
Value: value
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
Resources: resources,
|
|
432
|
+
Outputs: outputs
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
toString(pretty = false) {
|
|
436
|
+
return JSON.stringify(
|
|
437
|
+
this.toJSON(),
|
|
438
|
+
void 0,
|
|
439
|
+
pretty ? 4 : void 0
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/stack.ts
|
|
445
|
+
var toStack = ({ config, app, stackConfig, bootstrap: bootstrap2, usEastBootstrap, plugins }) => {
|
|
446
|
+
const name = stackConfig.name;
|
|
447
|
+
const stack = new Stack(name, config.region).tag("app", config.name).tag("stage", config.stage).tag("stack", name);
|
|
448
|
+
debug("Define stack:", style.info(name));
|
|
449
|
+
debug("Run plugin onStack listeners");
|
|
164
450
|
const bindings = [];
|
|
165
451
|
const bind = (cb) => {
|
|
166
452
|
bindings.push(cb);
|
|
167
453
|
};
|
|
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
|
-
|
|
454
|
+
for (const plugin of plugins) {
|
|
455
|
+
plugin.onStack?.({
|
|
456
|
+
config,
|
|
457
|
+
app,
|
|
458
|
+
stack,
|
|
459
|
+
stackConfig,
|
|
460
|
+
bootstrap: bootstrap2,
|
|
461
|
+
usEastBootstrap,
|
|
462
|
+
bind
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (stack.size === 0) {
|
|
466
|
+
throw new Error(`Stack ${style.info(name)} has no resources defined`);
|
|
467
|
+
}
|
|
468
|
+
const functions = stack.find(Function);
|
|
469
|
+
for (const bind2 of bindings) {
|
|
470
|
+
for (const fn of functions) {
|
|
471
|
+
bind2(fn);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
for (const fn of functions) {
|
|
475
|
+
fn.addPermissions({
|
|
476
|
+
actions: [
|
|
477
|
+
"ssm:GetParameter",
|
|
478
|
+
"ssm:GetParameters",
|
|
479
|
+
"ssm:GetParametersByPath"
|
|
480
|
+
],
|
|
481
|
+
resources: [
|
|
482
|
+
sub("arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter" + configParameterPrefix(config))
|
|
483
|
+
]
|
|
484
|
+
});
|
|
485
|
+
}
|
|
200
486
|
return {
|
|
201
487
|
stack,
|
|
202
|
-
functions,
|
|
203
|
-
bindings,
|
|
204
488
|
depends: stackConfig.depends
|
|
205
489
|
};
|
|
206
490
|
};
|
|
207
491
|
|
|
208
|
-
// src/util/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
492
|
+
// src/util/deployment.ts
|
|
493
|
+
var createDependencyTree = (stacks) => {
|
|
494
|
+
const list3 = stacks.map(({ stack, config }) => ({
|
|
495
|
+
stack,
|
|
496
|
+
depends: config?.depends?.map((dep) => dep.name) || []
|
|
497
|
+
}));
|
|
498
|
+
const findChildren = (list4, parents) => {
|
|
499
|
+
const children = [];
|
|
500
|
+
const rests = [];
|
|
501
|
+
for (const item of list4) {
|
|
502
|
+
const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
|
|
503
|
+
if (isChild) {
|
|
504
|
+
children.push(item);
|
|
505
|
+
} else {
|
|
506
|
+
rests.push(item);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (!rests.length) {
|
|
510
|
+
return children.map(({ stack }) => ({
|
|
511
|
+
stack,
|
|
512
|
+
children: []
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
return children.map(({ stack }) => {
|
|
516
|
+
return {
|
|
517
|
+
stack,
|
|
518
|
+
children: findChildren(rests, [...parents, stack.name])
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
return findChildren(list3, []);
|
|
523
|
+
};
|
|
524
|
+
var createDeploymentLine = (stacks) => {
|
|
525
|
+
const line = [];
|
|
526
|
+
const walk = (stacks2, level) => {
|
|
527
|
+
stacks2.forEach((node) => {
|
|
528
|
+
if (!line[level]) {
|
|
529
|
+
line[level] = [];
|
|
530
|
+
}
|
|
531
|
+
line[level].push(node.stack);
|
|
532
|
+
walk(node.children, level + 1);
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
walk(stacks, 0);
|
|
536
|
+
return line;
|
|
537
|
+
};
|
|
218
538
|
|
|
219
539
|
// src/plugins/cron/index.ts
|
|
220
|
-
import { z as
|
|
540
|
+
import { z as z7 } from "zod";
|
|
221
541
|
|
|
222
542
|
// src/plugins/cron/schema/schedule.ts
|
|
223
|
-
import { Schedule } from "aws-cdk-lib/aws-events";
|
|
224
543
|
import { z } from "zod";
|
|
225
544
|
import { awsCronExpressionValidator } from "aws-cron-expression-validator";
|
|
226
545
|
var RateExpressionSchema = z.custom((value) => {
|
|
@@ -229,7 +548,7 @@ var RateExpressionSchema = z.custom((value) => {
|
|
|
229
548
|
const number = parseInt(str);
|
|
230
549
|
return number > 0;
|
|
231
550
|
}).safeParse(value).success;
|
|
232
|
-
}, "Invalid rate expression")
|
|
551
|
+
}, "Invalid rate expression");
|
|
233
552
|
var CronExpressionSchema = z.custom((value) => {
|
|
234
553
|
return z.string().startsWith("cron(").endsWith(")").safeParse(value).success;
|
|
235
554
|
}, "Invalid cron expression").superRefine((value, ctx) => {
|
|
@@ -249,58 +568,81 @@ var CronExpressionSchema = z.custom((value) => {
|
|
|
249
568
|
});
|
|
250
569
|
}
|
|
251
570
|
}
|
|
252
|
-
})
|
|
571
|
+
});
|
|
253
572
|
var ScheduleExpressionSchema = RateExpressionSchema.or(CronExpressionSchema);
|
|
254
573
|
|
|
255
|
-
// src/plugins/
|
|
256
|
-
import {
|
|
574
|
+
// src/plugins/function.ts
|
|
575
|
+
import { z as z6 } from "zod";
|
|
257
576
|
|
|
258
|
-
// src/
|
|
259
|
-
import {
|
|
260
|
-
var toId = (resource, id) => {
|
|
261
|
-
return pascalCase(`${resource}-${id}`);
|
|
262
|
-
};
|
|
263
|
-
var toName = (stack, id) => {
|
|
264
|
-
return paramCase(`${stack.stackName}-${id}`);
|
|
265
|
-
};
|
|
266
|
-
var toExportName = (name) => {
|
|
267
|
-
return paramCase(name);
|
|
268
|
-
};
|
|
269
|
-
var toEnvKey = (resource, id) => {
|
|
270
|
-
return `RESOURCE_${resource.toUpperCase()}_${id}`;
|
|
271
|
-
};
|
|
272
|
-
var addResourceEnvironment = (stack, resource, id, lambda) => {
|
|
273
|
-
const key = toEnvKey(resource, id);
|
|
274
|
-
const value = toName(stack, id);
|
|
275
|
-
lambda.addEnvironment(key, value, {
|
|
276
|
-
removeInEdge: true
|
|
277
|
-
});
|
|
278
|
-
};
|
|
577
|
+
// src/schema/duration.ts
|
|
578
|
+
import { z as z2 } from "zod";
|
|
279
579
|
|
|
280
|
-
// src/
|
|
281
|
-
|
|
580
|
+
// src/formation/property/duration.ts
|
|
581
|
+
var Duration = class {
|
|
582
|
+
constructor(value) {
|
|
583
|
+
this.value = value;
|
|
584
|
+
}
|
|
585
|
+
static milliseconds(value) {
|
|
586
|
+
return new Duration(value);
|
|
587
|
+
}
|
|
588
|
+
static seconds(value) {
|
|
589
|
+
return new Duration(value * 1e3 /* seconds */);
|
|
590
|
+
}
|
|
591
|
+
static minutes(value) {
|
|
592
|
+
return new Duration(value * 6e4 /* minutes */);
|
|
593
|
+
}
|
|
594
|
+
static hours(value) {
|
|
595
|
+
return new Duration(value * 36e5 /* hours */);
|
|
596
|
+
}
|
|
597
|
+
static days(value) {
|
|
598
|
+
return new Duration(value * 864e5 /* days */);
|
|
599
|
+
}
|
|
600
|
+
toMilliseconds() {
|
|
601
|
+
return this.value;
|
|
602
|
+
}
|
|
603
|
+
toSeconds() {
|
|
604
|
+
return Math.floor(this.value / 1e3 /* seconds */);
|
|
605
|
+
}
|
|
606
|
+
toMinutes() {
|
|
607
|
+
return Math.floor(this.value / 6e4 /* minutes */);
|
|
608
|
+
}
|
|
609
|
+
toHours() {
|
|
610
|
+
return Math.floor(this.value / 36e5 /* hours */);
|
|
611
|
+
}
|
|
612
|
+
toDays() {
|
|
613
|
+
return Math.floor(this.value / 864e5 /* days */);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
282
616
|
|
|
283
617
|
// src/schema/duration.ts
|
|
284
|
-
import { z as z2 } from "zod";
|
|
285
|
-
import { Duration as CDKDuration } from "aws-cdk-lib/core";
|
|
286
618
|
function toDuration(duration) {
|
|
287
619
|
const [count, unit] = duration.split(" ");
|
|
288
620
|
const countNum = parseInt(count);
|
|
289
621
|
const unitLower = unit.toLowerCase();
|
|
290
622
|
if (unitLower.startsWith("second")) {
|
|
291
|
-
return
|
|
623
|
+
return Duration.seconds(countNum);
|
|
292
624
|
} else if (unitLower.startsWith("minute")) {
|
|
293
|
-
return
|
|
625
|
+
return Duration.minutes(countNum);
|
|
294
626
|
} else if (unitLower.startsWith("hour")) {
|
|
295
|
-
return
|
|
627
|
+
return Duration.hours(countNum);
|
|
296
628
|
} else if (unitLower.startsWith("day")) {
|
|
297
|
-
return
|
|
629
|
+
return Duration.days(countNum);
|
|
298
630
|
}
|
|
299
|
-
return
|
|
631
|
+
return Duration.days(0);
|
|
300
632
|
}
|
|
301
633
|
var DurationSchema = z2.custom((value) => {
|
|
302
634
|
return z2.string().regex(/[0-9]+ (seconds?|minutes?|hours?|days?)/).safeParse(value).success;
|
|
303
635
|
}, "Invalid duration").transform(toDuration);
|
|
636
|
+
var durationMin = (min) => {
|
|
637
|
+
return (duration) => {
|
|
638
|
+
return duration.toSeconds() >= min.toSeconds();
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
var durationMax = (max) => {
|
|
642
|
+
return (duration) => {
|
|
643
|
+
return duration.toSeconds() <= max.toSeconds();
|
|
644
|
+
};
|
|
645
|
+
};
|
|
304
646
|
|
|
305
647
|
// src/schema/local-file.ts
|
|
306
648
|
import { access, constants } from "fs/promises";
|
|
@@ -314,202 +656,86 @@ var LocalFileSchema = z3.string().refine(async (path) => {
|
|
|
314
656
|
return true;
|
|
315
657
|
}, `File doesn't exist`);
|
|
316
658
|
|
|
317
|
-
// src/
|
|
318
|
-
import { Code, Function } from "aws-cdk-lib/aws-lambda";
|
|
319
|
-
|
|
320
|
-
// src/plugins/function/schema/runtime.ts
|
|
321
|
-
import { Runtime as CdkRuntime } from "aws-cdk-lib/aws-lambda";
|
|
659
|
+
// src/schema/resource-id.ts
|
|
322
660
|
import { z as z4 } from "zod";
|
|
323
|
-
var
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
"nodejs16.x": CdkRuntime.NODEJS_16_X,
|
|
327
|
-
"nodejs18.x": CdkRuntime.NODEJS_18_X,
|
|
328
|
-
"python3.9": CdkRuntime.PYTHON_3_9,
|
|
329
|
-
"python3.10": CdkRuntime.PYTHON_3_10,
|
|
330
|
-
"go1.x": CdkRuntime.PROVIDED_AL2,
|
|
331
|
-
"go": CdkRuntime.PROVIDED_AL2
|
|
332
|
-
};
|
|
333
|
-
var toRuntime = (runtime) => {
|
|
334
|
-
return runtimes[runtime];
|
|
335
|
-
};
|
|
336
|
-
var RuntimeSchema = z4.enum(Object.keys(runtimes)).transform(toRuntime);
|
|
337
|
-
|
|
338
|
-
// src/plugins/function/schema/architecture.ts
|
|
339
|
-
import { Architecture as CdkArchitecture } from "aws-cdk-lib/aws-lambda";
|
|
661
|
+
var ResourceIdSchema = z4.string().min(3).max(24).regex(/[a-z\-]+/, "Invalid resource ID");
|
|
662
|
+
|
|
663
|
+
// src/schema/size.ts
|
|
340
664
|
import { z as z5 } from "zod";
|
|
341
|
-
var toArchitecture = (architecture) => {
|
|
342
|
-
return architecture === "x86_64" ? CdkArchitecture.X86_64 : CdkArchitecture.ARM_64;
|
|
343
|
-
};
|
|
344
|
-
var ArchitectureSchema = z5.enum(["x86_64", "arm_64"]).transform(toArchitecture);
|
|
345
665
|
|
|
346
|
-
// src/
|
|
347
|
-
|
|
348
|
-
|
|
666
|
+
// src/formation/property/size.ts
|
|
667
|
+
var Size = class {
|
|
668
|
+
constructor(bytes) {
|
|
669
|
+
this.bytes = bytes;
|
|
670
|
+
}
|
|
671
|
+
static bytes(value) {
|
|
672
|
+
return new Size(value);
|
|
673
|
+
}
|
|
674
|
+
static kiloBytes(value) {
|
|
675
|
+
return new Size(value * 1024 /* kilo */);
|
|
676
|
+
}
|
|
677
|
+
static megaBytes(value) {
|
|
678
|
+
return new Size(value * 1048576 /* mega */);
|
|
679
|
+
}
|
|
680
|
+
static gigaBytes(value) {
|
|
681
|
+
return new Size(value * 1073741824 /* giga */);
|
|
682
|
+
}
|
|
683
|
+
toBytes() {
|
|
684
|
+
return this.bytes;
|
|
685
|
+
}
|
|
686
|
+
toKiloBytes() {
|
|
687
|
+
return Math.floor(this.bytes / 1024 /* kilo */);
|
|
688
|
+
}
|
|
689
|
+
toMegaBytes() {
|
|
690
|
+
return Math.floor(this.bytes / 1048576 /* mega */);
|
|
691
|
+
}
|
|
692
|
+
toGigaBytes() {
|
|
693
|
+
return Math.floor(this.bytes / 1073741824 /* giga */);
|
|
694
|
+
}
|
|
695
|
+
};
|
|
349
696
|
|
|
350
697
|
// src/schema/size.ts
|
|
351
|
-
import { Size as CDKSize } from "aws-cdk-lib/core";
|
|
352
|
-
import { z as z7 } from "zod";
|
|
353
698
|
function toSize(size) {
|
|
354
699
|
const [count, unit] = size.split(" ");
|
|
355
700
|
const countNum = parseInt(count);
|
|
356
701
|
if (unit === "KB") {
|
|
357
|
-
return
|
|
702
|
+
return Size.kiloBytes(countNum);
|
|
358
703
|
} else if (unit === "MB") {
|
|
359
|
-
return
|
|
704
|
+
return Size.megaBytes(countNum);
|
|
360
705
|
} else if (unit === "GB") {
|
|
361
|
-
return
|
|
706
|
+
return Size.gigaBytes(countNum);
|
|
362
707
|
}
|
|
363
708
|
throw new TypeError(`Invalid size ${size}`);
|
|
364
709
|
}
|
|
365
|
-
var SizeSchema =
|
|
366
|
-
return
|
|
710
|
+
var SizeSchema = z5.custom((value) => {
|
|
711
|
+
return z5.string().regex(/[0-9]+ (KB|MB|GB)/).safeParse(value).success;
|
|
367
712
|
}, "Invalid size").transform(toSize);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
373
|
-
import { filesize } from "filesize";
|
|
374
|
-
var zipFiles = (files) => {
|
|
375
|
-
const zip = new JSZip();
|
|
376
|
-
for (const file of files) {
|
|
377
|
-
zip.file(file.name, file.code);
|
|
378
|
-
}
|
|
379
|
-
return zip.generateAsync({
|
|
380
|
-
type: "nodebuffer",
|
|
381
|
-
compression: "DEFLATE",
|
|
382
|
-
compressionOptions: {
|
|
383
|
-
level: 9
|
|
384
|
-
}
|
|
385
|
-
});
|
|
713
|
+
var sizeMin = (min) => {
|
|
714
|
+
return (size) => {
|
|
715
|
+
return size.toBytes() >= min.toBytes();
|
|
716
|
+
};
|
|
386
717
|
};
|
|
387
|
-
var
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
await writeFile(versionFile, hash);
|
|
391
|
-
};
|
|
392
|
-
var writeBuildFiles = async (config, stack, id, files) => {
|
|
393
|
-
const bundle = await zipFiles(files);
|
|
394
|
-
const funcPath = join2(assetDir, "function", config.name, stack.artifactId, id);
|
|
395
|
-
const filesPath = join2(funcPath, "files");
|
|
396
|
-
const bundleFile = join2(funcPath, "bundle.zip");
|
|
397
|
-
debug("Bundle size of", style.info(join2(config.name, stack.artifactId, id)), "is", style.attr(filesize(bundle.byteLength)));
|
|
398
|
-
await mkdir(filesPath, { recursive: true });
|
|
399
|
-
await writeFile(bundleFile, bundle);
|
|
400
|
-
await Promise.all(files.map(async (file) => {
|
|
401
|
-
const fileName = join2(filesPath, file.name);
|
|
402
|
-
await mkdir(dirname(fileName), { recursive: true });
|
|
403
|
-
await writeFile(fileName, file.code);
|
|
404
|
-
if (file.map) {
|
|
405
|
-
const mapName = join2(filesPath, `${file.name}.map`);
|
|
406
|
-
await writeFile(mapName, file.map);
|
|
407
|
-
}
|
|
408
|
-
}));
|
|
409
|
-
return {
|
|
410
|
-
file: bundleFile,
|
|
411
|
-
size: bundle.byteLength
|
|
718
|
+
var sizeMax = (max) => {
|
|
719
|
+
return (size) => {
|
|
720
|
+
return size.toBytes() <= max.toBytes();
|
|
412
721
|
};
|
|
413
722
|
};
|
|
414
723
|
|
|
415
|
-
// src/
|
|
416
|
-
import {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// src/stack/bootstrap.ts
|
|
421
|
-
import { CfnOutput, RemovalPolicy, Stack as Stack2 } from "aws-cdk-lib";
|
|
422
|
-
import { Bucket, BucketAccessControl } from "aws-cdk-lib/aws-s3";
|
|
423
|
-
var assetBucketName = (config) => {
|
|
424
|
-
return `awsless-bootstrap-${config.account}-${config.region}`;
|
|
425
|
-
};
|
|
426
|
-
var assetBucketUrl = (config, stackName) => {
|
|
427
|
-
const bucket = assetBucketName(config);
|
|
428
|
-
return `https://s3-${config.region}.amazonaws.com/${bucket}/${stackName}/cloudformation.json`;
|
|
429
|
-
};
|
|
430
|
-
var version = "1";
|
|
431
|
-
var bootstrapStack = (config, app) => {
|
|
432
|
-
const stack = new Stack2(app, "bootstrap", {
|
|
433
|
-
stackName: `awsless-bootstrap`
|
|
434
|
-
});
|
|
435
|
-
new Bucket(stack, "assets", {
|
|
436
|
-
bucketName: assetBucketName(config),
|
|
437
|
-
versioned: true,
|
|
438
|
-
accessControl: BucketAccessControl.PRIVATE,
|
|
439
|
-
removalPolicy: RemovalPolicy.DESTROY
|
|
440
|
-
});
|
|
441
|
-
new CfnOutput(stack, "version", {
|
|
442
|
-
exportName: "version",
|
|
443
|
-
value: version
|
|
444
|
-
});
|
|
445
|
-
return stack;
|
|
446
|
-
};
|
|
447
|
-
var shouldDeployBootstrap = async (client, name) => {
|
|
448
|
-
debug("Check bootstrap status");
|
|
449
|
-
const info = await client.get(name);
|
|
450
|
-
return !info || info.outputs.version !== version || !["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(info.status);
|
|
724
|
+
// src/util/byte-size.ts
|
|
725
|
+
import { filesize } from "filesize";
|
|
726
|
+
var formatByteSize = (size) => {
|
|
727
|
+
const [number, unit] = filesize(size).toString().split(" ");
|
|
728
|
+
return style.attr(number) + style.attr.dim(unit);
|
|
451
729
|
};
|
|
452
730
|
|
|
453
|
-
// src/
|
|
454
|
-
var publishFunctionAsset = async (config, stack, id) => {
|
|
455
|
-
const bucket = assetBucketName(config);
|
|
456
|
-
const key = `${config.name}/${stack.artifactId}/function/${id}.zip`;
|
|
457
|
-
const funcPath = join3(assetDir, "function", config.name, stack.artifactId, id);
|
|
458
|
-
const bundleFile = join3(funcPath, "bundle.zip");
|
|
459
|
-
const hashFile = join3(funcPath, "HASH");
|
|
460
|
-
const hash = await readFile(hashFile, "utf8");
|
|
461
|
-
const file = await readFile(bundleFile);
|
|
462
|
-
const client = new S3Client({
|
|
463
|
-
credentials: config.credentials,
|
|
464
|
-
region: config.region
|
|
465
|
-
});
|
|
466
|
-
let getResult;
|
|
467
|
-
try {
|
|
468
|
-
getResult = await client.send(new GetObjectCommand({
|
|
469
|
-
Bucket: bucket,
|
|
470
|
-
Key: key
|
|
471
|
-
}));
|
|
472
|
-
} catch (error) {
|
|
473
|
-
if (error instanceof Error && error.name === "NoSuchKey") {
|
|
474
|
-
} else {
|
|
475
|
-
throw error;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (getResult?.Metadata?.hash === hash) {
|
|
479
|
-
return getResult.VersionId;
|
|
480
|
-
}
|
|
481
|
-
const putResult = await client.send(new PutObjectCommand({
|
|
482
|
-
Bucket: bucket,
|
|
483
|
-
Key: key,
|
|
484
|
-
Body: file,
|
|
485
|
-
ACL: ObjectCannedACL.private,
|
|
486
|
-
StorageClass: StorageClass.STANDARD,
|
|
487
|
-
Metadata: {
|
|
488
|
-
hash
|
|
489
|
-
}
|
|
490
|
-
}));
|
|
491
|
-
return putResult.VersionId;
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
// src/plugins/function/schema/retry-attempts.ts
|
|
495
|
-
import { z as z8 } from "zod";
|
|
496
|
-
var RetryAttempts = z8.number().int().min(0).max(2);
|
|
497
|
-
|
|
498
|
-
// src/util/byte-size.ts
|
|
499
|
-
import { filesize as filesize2 } from "filesize";
|
|
500
|
-
var formatByteSize = (size) => {
|
|
501
|
-
const [number, unit] = filesize2(size).toString().split(" ");
|
|
502
|
-
return style.attr(number) + style.attr.dim(unit);
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
// src/plugins/function/util/bundler/rollup.ts
|
|
731
|
+
// src/formation/resource/lambda/util/rollup.ts
|
|
506
732
|
import { rollup } from "rollup";
|
|
507
733
|
import { createHash } from "crypto";
|
|
508
734
|
import { swc } from "rollup-plugin-swc3";
|
|
509
735
|
import json from "@rollup/plugin-json";
|
|
510
736
|
import commonjs from "@rollup/plugin-commonjs";
|
|
511
737
|
import nodeResolve from "@rollup/plugin-node-resolve";
|
|
512
|
-
var
|
|
738
|
+
var rollupBundle = async (input) => {
|
|
513
739
|
const bundle = await rollup({
|
|
514
740
|
input,
|
|
515
741
|
external: (importee) => {
|
|
@@ -554,34 +780,160 @@ var rollupBuild = async (input) => {
|
|
|
554
780
|
};
|
|
555
781
|
};
|
|
556
782
|
|
|
557
|
-
// src/
|
|
558
|
-
|
|
783
|
+
// src/formation/resource/lambda/util/zip.ts
|
|
784
|
+
import JSZip from "jszip";
|
|
785
|
+
var zipFiles = (files) => {
|
|
786
|
+
const zip = new JSZip();
|
|
787
|
+
for (const file of files) {
|
|
788
|
+
zip.file(file.name, file.code);
|
|
789
|
+
}
|
|
790
|
+
return zip.generateAsync({
|
|
791
|
+
type: "nodebuffer",
|
|
792
|
+
compression: "DEFLATE",
|
|
793
|
+
compressionOptions: {
|
|
794
|
+
level: 9
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/formation/resource/lambda/code.ts
|
|
800
|
+
import { createHash as createHash2 } from "crypto";
|
|
801
|
+
var Code = class {
|
|
802
|
+
static fromFile(id, file, bundler) {
|
|
803
|
+
return new FileCode(id, file, bundler);
|
|
804
|
+
}
|
|
805
|
+
static fromInline(id, code, handler) {
|
|
806
|
+
return new InlineCode(id, code, handler);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
var InlineCode = class extends Asset {
|
|
810
|
+
constructor(id, code, handler = "index.default") {
|
|
811
|
+
super("function", id);
|
|
812
|
+
this.code = code;
|
|
813
|
+
this.handler = handler;
|
|
814
|
+
}
|
|
815
|
+
hash;
|
|
816
|
+
bundle;
|
|
817
|
+
s3;
|
|
818
|
+
async build({ write }) {
|
|
819
|
+
const hash = createHash2("sha1").update(this.code).digest("hex");
|
|
820
|
+
const bundle = await zipFiles([{
|
|
821
|
+
name: "index.js",
|
|
822
|
+
code: this.code
|
|
823
|
+
}]);
|
|
824
|
+
await Promise.all([
|
|
825
|
+
write("HASH", hash),
|
|
826
|
+
write("bundle.zip", bundle),
|
|
827
|
+
write("files/inline.js", this.code)
|
|
828
|
+
]);
|
|
829
|
+
this.bundle = bundle;
|
|
830
|
+
this.hash = hash;
|
|
831
|
+
return {
|
|
832
|
+
size: formatByteSize(bundle.byteLength)
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
async publish({ publish }) {
|
|
836
|
+
this.s3 = await publish(
|
|
837
|
+
`${this.id}.zip`,
|
|
838
|
+
this.bundle,
|
|
839
|
+
this.hash
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
toCodeJson() {
|
|
843
|
+
return {
|
|
844
|
+
Handler: this.handler,
|
|
845
|
+
Code: {
|
|
846
|
+
S3Bucket: this.s3.bucket,
|
|
847
|
+
S3Key: this.s3.key,
|
|
848
|
+
S3ObjectVersion: this.s3.version
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
var FileCode = class extends Asset {
|
|
854
|
+
constructor(id, file, bundler) {
|
|
855
|
+
super("function", id);
|
|
856
|
+
this.file = file;
|
|
857
|
+
this.bundler = bundler;
|
|
858
|
+
}
|
|
859
|
+
handler;
|
|
860
|
+
hash;
|
|
861
|
+
bundle;
|
|
862
|
+
s3;
|
|
863
|
+
async build({ write }) {
|
|
864
|
+
const bundler = this.bundler ?? rollupBundle;
|
|
865
|
+
const { hash, files, handler } = await bundler(this.file);
|
|
866
|
+
const bundle = await zipFiles(files);
|
|
867
|
+
await Promise.all([
|
|
868
|
+
write("HASH", hash),
|
|
869
|
+
write("bundle.zip", bundle),
|
|
870
|
+
...files.map((file) => write(`files/${file.name}`, file.code)),
|
|
871
|
+
...files.map((file) => file.map ? write(`files/${file.name}.map`, file.map) : void 0)
|
|
872
|
+
]);
|
|
873
|
+
this.handler = handler;
|
|
874
|
+
this.bundle = bundle;
|
|
875
|
+
this.hash = hash;
|
|
876
|
+
return {
|
|
877
|
+
size: formatByteSize(bundle.byteLength)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
async publish({ publish }) {
|
|
881
|
+
this.s3 = await publish(
|
|
882
|
+
`${this.id}.zip`,
|
|
883
|
+
this.bundle,
|
|
884
|
+
this.hash
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
toCodeJson() {
|
|
888
|
+
return {
|
|
889
|
+
Handler: this.handler,
|
|
890
|
+
Code: {
|
|
891
|
+
S3Bucket: this.s3?.bucket ?? "",
|
|
892
|
+
S3Key: this.s3?.key ?? "",
|
|
893
|
+
S3ObjectVersion: this.s3?.version ?? ""
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// src/plugins/function.ts
|
|
900
|
+
var MemorySizeSchema = SizeSchema.refine(sizeMin(Size.megaBytes(128)), "Minimum memory size is 128 MB").refine(sizeMax(Size.gigaBytes(10)), "Minimum memory size is 10 GB");
|
|
901
|
+
var TimeoutSchema = DurationSchema.refine(durationMin(Duration.seconds(10)), "Minimum timeout duration is 10 seconds").refine(durationMax(Duration.minutes(15)), "Maximum timeout duration is 15 minutes");
|
|
902
|
+
var EphemeralStorageSizeSchema = SizeSchema.refine(sizeMin(Size.megaBytes(512)), "Minimum ephemeral storage size is 512 MB").refine(sizeMax(Size.gigaBytes(10)), "Minimum ephemeral storage size is 10 GB");
|
|
903
|
+
var EnvironmentSchema = z6.record(z6.string(), z6.string()).optional();
|
|
904
|
+
var ArchitectureSchema = z6.enum(["x86_64", "arm64"]);
|
|
905
|
+
var RetryAttemptsSchema = z6.number().int().min(0).max(2);
|
|
906
|
+
var RuntimeSchema = z6.enum([
|
|
907
|
+
"nodejs16.x",
|
|
908
|
+
"nodejs18.x"
|
|
909
|
+
]);
|
|
910
|
+
var FunctionSchema = z6.union([
|
|
559
911
|
LocalFileSchema,
|
|
560
|
-
|
|
912
|
+
z6.object({
|
|
561
913
|
file: LocalFileSchema,
|
|
562
|
-
timeout:
|
|
914
|
+
timeout: TimeoutSchema.optional(),
|
|
563
915
|
runtime: RuntimeSchema.optional(),
|
|
564
|
-
memorySize:
|
|
916
|
+
memorySize: MemorySizeSchema.optional(),
|
|
565
917
|
architecture: ArchitectureSchema.optional(),
|
|
566
|
-
ephemeralStorageSize:
|
|
567
|
-
retryAttempts:
|
|
568
|
-
environment:
|
|
918
|
+
ephemeralStorageSize: EphemeralStorageSizeSchema.optional(),
|
|
919
|
+
retryAttempts: RetryAttemptsSchema.optional(),
|
|
920
|
+
environment: EnvironmentSchema.optional()
|
|
569
921
|
})
|
|
570
922
|
]);
|
|
571
|
-
var schema =
|
|
572
|
-
defaults:
|
|
573
|
-
function:
|
|
574
|
-
timeout:
|
|
923
|
+
var schema = z6.object({
|
|
924
|
+
defaults: z6.object({
|
|
925
|
+
function: z6.object({
|
|
926
|
+
timeout: TimeoutSchema.default("10 seconds"),
|
|
575
927
|
runtime: RuntimeSchema.default("nodejs18.x"),
|
|
576
|
-
memorySize:
|
|
577
|
-
architecture: ArchitectureSchema.default("
|
|
578
|
-
ephemeralStorageSize:
|
|
579
|
-
retryAttempts:
|
|
580
|
-
environment:
|
|
928
|
+
memorySize: MemorySizeSchema.default("128 MB"),
|
|
929
|
+
architecture: ArchitectureSchema.default("arm64"),
|
|
930
|
+
ephemeralStorageSize: EphemeralStorageSizeSchema.default("512 MB"),
|
|
931
|
+
retryAttempts: RetryAttemptsSchema.default(2),
|
|
932
|
+
environment: EnvironmentSchema.optional()
|
|
581
933
|
}).default({})
|
|
582
934
|
}).default({}),
|
|
583
|
-
stacks:
|
|
584
|
-
functions:
|
|
935
|
+
stacks: z6.object({
|
|
936
|
+
functions: z6.record(
|
|
585
937
|
ResourceIdSchema,
|
|
586
938
|
FunctionSchema
|
|
587
939
|
).optional()
|
|
@@ -590,200 +942,466 @@ var schema = z9.object({
|
|
|
590
942
|
var functionPlugin = definePlugin({
|
|
591
943
|
name: "function",
|
|
592
944
|
schema,
|
|
593
|
-
onStack(
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
945
|
+
onStack(ctx) {
|
|
946
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.functions || {})) {
|
|
947
|
+
const lambda = toLambdaFunction(ctx, id, props);
|
|
948
|
+
ctx.stack.add(lambda);
|
|
949
|
+
}
|
|
597
950
|
}
|
|
598
951
|
});
|
|
599
|
-
var
|
|
952
|
+
var toLambdaFunction = (ctx, id, fileOrProps) => {
|
|
953
|
+
const config = ctx.config;
|
|
954
|
+
const stack = ctx.stack;
|
|
600
955
|
const props = typeof fileOrProps === "string" ? { ...config.defaults?.function, file: fileOrProps } : { ...config.defaults?.function, ...fileOrProps };
|
|
601
|
-
const lambda = new Function(
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
...props,
|
|
606
|
-
memorySize: props.memorySize.toMebibytes()
|
|
956
|
+
const lambda = new Function(id, {
|
|
957
|
+
name: `${config.name}-${stack.name}-${id}`,
|
|
958
|
+
code: Code.fromFile(id, props.file),
|
|
959
|
+
...props
|
|
607
960
|
});
|
|
608
|
-
lambda.addEnvironment("APP", config.name
|
|
609
|
-
lambda.addEnvironment("STAGE", config.stage
|
|
610
|
-
lambda.addEnvironment("STACK", stack.
|
|
611
|
-
if (
|
|
612
|
-
lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1"
|
|
613
|
-
removeInEdge: true
|
|
614
|
-
});
|
|
961
|
+
lambda.addEnvironment("APP", config.name);
|
|
962
|
+
lambda.addEnvironment("STAGE", config.stage);
|
|
963
|
+
lambda.addEnvironment("STACK", stack.name);
|
|
964
|
+
if (props.runtime.startsWith("nodejs")) {
|
|
965
|
+
lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
|
|
615
966
|
}
|
|
616
|
-
assets.add({
|
|
617
|
-
stackName: stack.artifactId,
|
|
618
|
-
resource: "function",
|
|
619
|
-
resourceName: id,
|
|
620
|
-
async build() {
|
|
621
|
-
const result = await rollupBuild(props.file);
|
|
622
|
-
const bundle = await writeBuildFiles(config, stack, id, result.files);
|
|
623
|
-
await writeBuildHash(config, stack, id, result.hash);
|
|
624
|
-
const func = lambda.node.defaultChild;
|
|
625
|
-
func.handler = result.handler;
|
|
626
|
-
return {
|
|
627
|
-
size: formatByteSize(bundle.size)
|
|
628
|
-
};
|
|
629
|
-
},
|
|
630
|
-
async publish() {
|
|
631
|
-
const version2 = await publishFunctionAsset(config, stack, id);
|
|
632
|
-
const func = lambda.node.defaultChild;
|
|
633
|
-
func.code = {
|
|
634
|
-
s3Bucket: assetBucketName(config),
|
|
635
|
-
s3Key: `${config.name}/${stack.artifactId}/function/${id}.zip`,
|
|
636
|
-
s3ObjectVersion: version2
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
967
|
return lambda;
|
|
641
968
|
};
|
|
642
969
|
|
|
970
|
+
// src/formation/resource/events/rule.ts
|
|
971
|
+
var Rule = class extends Resource {
|
|
972
|
+
constructor(logicalId, props) {
|
|
973
|
+
super("AWS::Events::Rule", logicalId);
|
|
974
|
+
this.props = props;
|
|
975
|
+
this.name = formatName(this.props.name || logicalId);
|
|
976
|
+
}
|
|
977
|
+
name;
|
|
978
|
+
get id() {
|
|
979
|
+
return ref(this.logicalId);
|
|
980
|
+
}
|
|
981
|
+
get arn() {
|
|
982
|
+
return getAtt(this.logicalId, "Arn");
|
|
983
|
+
}
|
|
984
|
+
properties() {
|
|
985
|
+
return {
|
|
986
|
+
Name: this.name,
|
|
987
|
+
...this.attr("State", "ENABLED"),
|
|
988
|
+
...this.attr("Description", this.props.description),
|
|
989
|
+
...this.attr("ScheduleExpression", this.props.schedule),
|
|
990
|
+
...this.attr("RoleArn", this.props.roleArn),
|
|
991
|
+
...this.attr("EventBusName", this.props.eventBusName),
|
|
992
|
+
...this.attr("EventPattern", this.props.eventPattern),
|
|
993
|
+
Targets: this.props.targets.map((target) => ({
|
|
994
|
+
Arn: target.arn,
|
|
995
|
+
Id: target.id,
|
|
996
|
+
...this.attr("Input", target.input && JSON.stringify(target.input))
|
|
997
|
+
}))
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
// src/formation/resource/lambda/permission.ts
|
|
1003
|
+
var Permission2 = class extends Resource {
|
|
1004
|
+
constructor(logicalId, props) {
|
|
1005
|
+
super("AWS::Lambda::Permission", logicalId);
|
|
1006
|
+
this.props = props;
|
|
1007
|
+
}
|
|
1008
|
+
properties() {
|
|
1009
|
+
return {
|
|
1010
|
+
FunctionName: this.props.functionArn,
|
|
1011
|
+
Action: this.props.action || "lambda:InvokeFunction",
|
|
1012
|
+
Principal: this.props.principal,
|
|
1013
|
+
SourceArn: this.props.sourceArn
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
// src/formation/resource/lambda/event-source/events.ts
|
|
1019
|
+
var EventsEventSource = class extends Group {
|
|
1020
|
+
constructor(id, lambda, props) {
|
|
1021
|
+
const rule = new Rule(id, {
|
|
1022
|
+
schedule: props.schedule,
|
|
1023
|
+
targets: [{
|
|
1024
|
+
id,
|
|
1025
|
+
arn: lambda.arn,
|
|
1026
|
+
input: props.payload
|
|
1027
|
+
}]
|
|
1028
|
+
});
|
|
1029
|
+
const permission = new Permission2(id, {
|
|
1030
|
+
action: "lambda:InvokeFunction",
|
|
1031
|
+
principal: "events.amazonaws.com",
|
|
1032
|
+
functionArn: lambda.arn,
|
|
1033
|
+
sourceArn: rule.arn
|
|
1034
|
+
});
|
|
1035
|
+
super([rule, permission]);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
|
|
643
1039
|
// src/plugins/cron/index.ts
|
|
644
|
-
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
|
|
645
1040
|
var cronPlugin = definePlugin({
|
|
646
1041
|
name: "cron",
|
|
647
|
-
schema:
|
|
648
|
-
stacks:
|
|
649
|
-
crons:
|
|
1042
|
+
schema: z7.object({
|
|
1043
|
+
stacks: z7.object({
|
|
1044
|
+
crons: z7.record(ResourceIdSchema, z7.object({
|
|
650
1045
|
consumer: FunctionSchema,
|
|
651
1046
|
schedule: ScheduleExpressionSchema,
|
|
652
|
-
|
|
1047
|
+
payload: z7.unknown().optional()
|
|
653
1048
|
})).optional()
|
|
654
1049
|
}).array()
|
|
655
1050
|
}),
|
|
656
|
-
onStack(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
new
|
|
661
|
-
ruleName: toName(context.stack, id),
|
|
1051
|
+
onStack(ctx) {
|
|
1052
|
+
const { stack, stackConfig } = ctx;
|
|
1053
|
+
for (const [id, props] of Object.entries(stackConfig.crons || {})) {
|
|
1054
|
+
const lambda = toLambdaFunction(ctx, id, props.consumer);
|
|
1055
|
+
const source = new EventsEventSource(id, lambda, {
|
|
662
1056
|
schedule: props.schedule,
|
|
663
|
-
|
|
664
|
-
targets: [target]
|
|
1057
|
+
payload: props.payload
|
|
665
1058
|
});
|
|
666
|
-
|
|
667
|
-
}
|
|
1059
|
+
stack.add(lambda, source);
|
|
1060
|
+
}
|
|
668
1061
|
}
|
|
669
1062
|
});
|
|
670
1063
|
|
|
671
1064
|
// src/plugins/queue.ts
|
|
672
|
-
import { z as
|
|
673
|
-
|
|
674
|
-
|
|
1065
|
+
import { z as z8 } from "zod";
|
|
1066
|
+
|
|
1067
|
+
// src/formation/resource/sqs/queue.ts
|
|
1068
|
+
var Queue = class extends Resource {
|
|
1069
|
+
constructor(logicalId, props = {}) {
|
|
1070
|
+
super("AWS::SQS::Queue", logicalId);
|
|
1071
|
+
this.props = props;
|
|
1072
|
+
this.name = formatName(this.props.name || logicalId);
|
|
1073
|
+
}
|
|
1074
|
+
name;
|
|
1075
|
+
get arn() {
|
|
1076
|
+
return getAtt(this.logicalId, "Arn");
|
|
1077
|
+
}
|
|
1078
|
+
get url() {
|
|
1079
|
+
return getAtt(this.logicalId, "QueueUrl");
|
|
1080
|
+
}
|
|
1081
|
+
get permissions() {
|
|
1082
|
+
return {
|
|
1083
|
+
actions: [
|
|
1084
|
+
"sqs:SendMessage",
|
|
1085
|
+
"sqs:ReceiveMessage",
|
|
1086
|
+
"sqs:GetQueueUrl",
|
|
1087
|
+
"sqs:GetQueueAttributes"
|
|
1088
|
+
],
|
|
1089
|
+
resources: [this.arn]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
properties() {
|
|
1093
|
+
return {
|
|
1094
|
+
QueueName: this.name,
|
|
1095
|
+
DelaySeconds: this.props.deliveryDelay?.toSeconds() ?? 0,
|
|
1096
|
+
MaximumMessageSize: this.props.maxMessageSize?.toBytes() ?? Size.kiloBytes(256).toBytes(),
|
|
1097
|
+
MessageRetentionPeriod: this.props.retentionPeriod?.toSeconds() ?? Duration.days(4).toSeconds(),
|
|
1098
|
+
ReceiveMessageWaitTimeSeconds: this.props.receiveMessageWaitTime?.toSeconds() ?? 0,
|
|
1099
|
+
VisibilityTimeout: this.props.visibilityTimeout?.toSeconds() ?? 30
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// src/formation/resource/lambda/event-source-mapping.ts
|
|
1105
|
+
import { constantCase } from "change-case";
|
|
1106
|
+
var EventSourceMapping = class extends Resource {
|
|
1107
|
+
constructor(logicalId, props) {
|
|
1108
|
+
super("AWS::Lambda::EventSourceMapping", logicalId);
|
|
1109
|
+
this.props = props;
|
|
1110
|
+
}
|
|
1111
|
+
properties() {
|
|
1112
|
+
return {
|
|
1113
|
+
Enabled: true,
|
|
1114
|
+
FunctionName: this.props.functionArn,
|
|
1115
|
+
EventSourceArn: this.props.sourceArn,
|
|
1116
|
+
...this.attr("BatchSize", this.props.batchSize),
|
|
1117
|
+
...this.attr("MaximumBatchingWindowInSeconds", this.props.maxBatchingWindow?.toSeconds()),
|
|
1118
|
+
...this.attr("MaximumRecordAgeInSeconds", this.props.maxRecordAge?.toSeconds()),
|
|
1119
|
+
...this.attr("MaximumRetryAttempts", this.props.retryAttempts),
|
|
1120
|
+
...this.attr("ParallelizationFactor", this.props.parallelizationFactor),
|
|
1121
|
+
...this.attr("TumblingWindowInSeconds", this.props.tumblingWindow?.toSeconds()),
|
|
1122
|
+
...this.attr("BisectBatchOnFunctionError", this.props.bisectBatchOnError),
|
|
1123
|
+
...this.attr("StartingPosition", this.props.startingPosition && constantCase(this.props.startingPosition)),
|
|
1124
|
+
...this.attr("StartingPositionTimestamp", this.props.startingPositionTimestamp),
|
|
1125
|
+
...this.props.maxConcurrency ? {
|
|
1126
|
+
ScalingConfig: {
|
|
1127
|
+
MaximumConcurrency: this.props.maxConcurrency
|
|
1128
|
+
}
|
|
1129
|
+
} : {},
|
|
1130
|
+
...this.props.onFailure ? {
|
|
1131
|
+
DestinationConfig: {
|
|
1132
|
+
OnFailure: {
|
|
1133
|
+
Destination: this.props.onFailure
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
} : {}
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
// src/formation/resource/lambda/event-source/sqs.ts
|
|
1142
|
+
var SqsEventSource = class extends Group {
|
|
1143
|
+
constructor(id, lambda, props) {
|
|
1144
|
+
const source = new EventSourceMapping(id, {
|
|
1145
|
+
functionArn: lambda.arn,
|
|
1146
|
+
sourceArn: props.queueArn,
|
|
1147
|
+
batchSize: props.batchSize ?? 10,
|
|
1148
|
+
maxBatchingWindow: props.maxBatchingWindow,
|
|
1149
|
+
maxConcurrency: props.maxConcurrency,
|
|
1150
|
+
onFailure: props.onFailure
|
|
1151
|
+
});
|
|
1152
|
+
lambda.addPermissions({
|
|
1153
|
+
actions: [
|
|
1154
|
+
"sqs:ReceiveMessage",
|
|
1155
|
+
"sqs:DeleteMessage",
|
|
1156
|
+
"sqs:GetQueueAttributes"
|
|
1157
|
+
],
|
|
1158
|
+
resources: [props.queueArn]
|
|
1159
|
+
});
|
|
1160
|
+
super([source]);
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
// src/plugins/queue.ts
|
|
675
1165
|
var queuePlugin = definePlugin({
|
|
676
1166
|
name: "queue",
|
|
677
|
-
schema:
|
|
678
|
-
defaults:
|
|
679
|
-
queue
|
|
680
|
-
|
|
1167
|
+
schema: z8.object({
|
|
1168
|
+
defaults: z8.object({
|
|
1169
|
+
/** Define the defaults properties for all queue's in your app */
|
|
1170
|
+
queue: z8.object({
|
|
1171
|
+
/** The number of seconds that Amazon SQS retains a message.
|
|
1172
|
+
* You can specify a duration value from 1 minute to 14 days.
|
|
1173
|
+
* @default '7 days' */
|
|
681
1174
|
retentionPeriod: DurationSchema.default("7 days"),
|
|
1175
|
+
/** The length of time during which a message will be unavailable after a message is delivered from the queue.
|
|
1176
|
+
* This blocks other components from receiving the same message and gives the initial component time to process and delete the message from the queue.
|
|
1177
|
+
* You can specify a duration value from 0 to 12 hours.
|
|
1178
|
+
* @default '30 seconds' */
|
|
682
1179
|
visibilityTimeout: DurationSchema.default("30 seconds"),
|
|
1180
|
+
/** The time in seconds for which the delivery of all messages in the queue is delayed.
|
|
1181
|
+
* You can specify a duration value from 0 to 15 minutes.
|
|
1182
|
+
* @default '0 seconds' */
|
|
683
1183
|
deliveryDelay: DurationSchema.default("0 seconds"),
|
|
1184
|
+
/** Specifies the duration, in seconds,
|
|
1185
|
+
* that the ReceiveMessage action call waits until a message is in the queue in order to include it in the response,
|
|
1186
|
+
* rather than returning an empty response if a message isn't yet available.
|
|
1187
|
+
* You can specify an integer from 1 to 20.
|
|
1188
|
+
* You can specify a duration value from 1 to 20 seconds.
|
|
1189
|
+
* @default '0 seconds' */
|
|
684
1190
|
receiveMessageWaitTime: DurationSchema.default("0 seconds"),
|
|
1191
|
+
/** The limit of how many bytes that a message can contain before Amazon SQS rejects it.
|
|
1192
|
+
* You can specify an size value from 1 KB to 256 KB.
|
|
1193
|
+
* @default '256 KB' */
|
|
685
1194
|
maxMessageSize: SizeSchema.default("256 KB")
|
|
686
1195
|
}).default({})
|
|
687
1196
|
}).default({}),
|
|
688
|
-
stacks:
|
|
689
|
-
queues
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1197
|
+
stacks: z8.object({
|
|
1198
|
+
/** Define the queues in your stack
|
|
1199
|
+
* @example
|
|
1200
|
+
* {
|
|
1201
|
+
* queues: {
|
|
1202
|
+
* QUEUE_NAME: 'function.ts'
|
|
1203
|
+
* }
|
|
1204
|
+
* }
|
|
1205
|
+
* */
|
|
1206
|
+
queues: z8.record(
|
|
1207
|
+
ResourceIdSchema,
|
|
1208
|
+
z8.union([
|
|
1209
|
+
LocalFileSchema,
|
|
1210
|
+
z8.object({
|
|
1211
|
+
/** The consuming lambda function properties */
|
|
1212
|
+
consumer: FunctionSchema,
|
|
1213
|
+
/** The number of seconds that Amazon SQS retains a message.
|
|
1214
|
+
* You can specify a duration value from 1 minute to 14 days.
|
|
1215
|
+
* @default '7 days' */
|
|
1216
|
+
retentionPeriod: DurationSchema.optional(),
|
|
1217
|
+
/** The length of time during which a message will be unavailable after a message is delivered from the queue.
|
|
1218
|
+
* This blocks other components from receiving the same message and gives the initial component time to process and delete the message from the queue.
|
|
1219
|
+
* You can specify a duration value from 0 to 12 hours.
|
|
1220
|
+
* @default '30 seconds' */
|
|
1221
|
+
visibilityTimeout: DurationSchema.optional(),
|
|
1222
|
+
/** The time in seconds for which the delivery of all messages in the queue is delayed.
|
|
1223
|
+
* You can specify a duration value from 0 to 15 minutes.
|
|
1224
|
+
* @default '0 seconds' */
|
|
1225
|
+
deliveryDelay: DurationSchema.optional(),
|
|
1226
|
+
/** Specifies the duration, in seconds,
|
|
1227
|
+
* that the ReceiveMessage action call waits until a message is in the queue in order to include it in the response,
|
|
1228
|
+
* rather than returning an empty response if a message isn't yet available.
|
|
1229
|
+
* You can specify an integer from 1 to 20.
|
|
1230
|
+
* You can specify a duration value from 1 to 20 seconds.
|
|
1231
|
+
* @default '0 seconds' */
|
|
1232
|
+
receiveMessageWaitTime: DurationSchema.optional(),
|
|
1233
|
+
/** The limit of how many bytes that a message can contain before Amazon SQS rejects it.
|
|
1234
|
+
* You can specify an size value from 1 KB to 256 KB.
|
|
1235
|
+
* @default '256 KB' */
|
|
1236
|
+
maxMessageSize: SizeSchema.optional()
|
|
1237
|
+
})
|
|
1238
|
+
])
|
|
1239
|
+
).optional()
|
|
701
1240
|
}).array()
|
|
702
1241
|
}),
|
|
703
1242
|
onStack(ctx) {
|
|
704
1243
|
const { stack, config, stackConfig, bind } = ctx;
|
|
705
|
-
|
|
1244
|
+
for (const [id, functionOrProps] of Object.entries(stackConfig.queues || {})) {
|
|
706
1245
|
const props = typeof functionOrProps === "string" ? { ...config.defaults.queue, consumer: functionOrProps } : { ...config.defaults.queue, ...functionOrProps };
|
|
707
|
-
const queue2 = new Queue(
|
|
708
|
-
|
|
709
|
-
...props
|
|
710
|
-
|
|
1246
|
+
const queue2 = new Queue(id, {
|
|
1247
|
+
name: `${config.name}-${stack.name}-${id}`,
|
|
1248
|
+
...props
|
|
1249
|
+
});
|
|
1250
|
+
const lambda = toLambdaFunction(ctx, id, props.consumer);
|
|
1251
|
+
const source = new SqsEventSource(id, lambda, {
|
|
1252
|
+
queueArn: queue2.arn
|
|
711
1253
|
});
|
|
712
|
-
|
|
713
|
-
lambda.addEventSource(new SqsEventSource(queue2));
|
|
1254
|
+
stack.add(queue2, lambda, source);
|
|
714
1255
|
bind((lambda2) => {
|
|
715
|
-
queue2.
|
|
716
|
-
addResourceEnvironment(stack, "queue", id, lambda2);
|
|
1256
|
+
lambda2.addPermissions(queue2.permissions);
|
|
717
1257
|
});
|
|
718
|
-
|
|
719
|
-
});
|
|
1258
|
+
}
|
|
720
1259
|
}
|
|
721
1260
|
});
|
|
722
1261
|
|
|
723
|
-
// src/plugins/table
|
|
724
|
-
import { z as
|
|
725
|
-
import { BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
|
|
726
|
-
|
|
727
|
-
// src/plugins/table/schema/class-type.ts
|
|
728
|
-
import { TableClass } from "aws-cdk-lib/aws-dynamodb";
|
|
729
|
-
import { z as z12 } from "zod";
|
|
730
|
-
var types = {
|
|
731
|
-
"standard": TableClass.STANDARD,
|
|
732
|
-
"standard-infrequent-access": TableClass.STANDARD_INFREQUENT_ACCESS
|
|
733
|
-
};
|
|
734
|
-
var TableClassSchema = z12.enum(Object.keys(types)).transform((value) => {
|
|
735
|
-
return types[value];
|
|
736
|
-
});
|
|
1262
|
+
// src/plugins/table.ts
|
|
1263
|
+
import { z as z9 } from "zod";
|
|
737
1264
|
|
|
738
|
-
// src/
|
|
739
|
-
import {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1265
|
+
// src/formation/resource/dynamodb/table.ts
|
|
1266
|
+
import { constantCase as constantCase2 } from "change-case";
|
|
1267
|
+
var Table = class extends Resource {
|
|
1268
|
+
constructor(logicalId, props) {
|
|
1269
|
+
super("AWS::DynamoDB::Table", logicalId);
|
|
1270
|
+
this.props = props;
|
|
1271
|
+
this.name = formatName(this.props.name || logicalId);
|
|
1272
|
+
this.indexes = { ...this.props.indexes || {} };
|
|
1273
|
+
}
|
|
1274
|
+
name;
|
|
1275
|
+
indexes;
|
|
1276
|
+
addIndex(name, props) {
|
|
1277
|
+
this.indexes[name] = props;
|
|
1278
|
+
}
|
|
1279
|
+
get arn() {
|
|
1280
|
+
return ref(this.logicalId);
|
|
1281
|
+
}
|
|
1282
|
+
get permissions() {
|
|
1283
|
+
return {
|
|
1284
|
+
actions: [
|
|
1285
|
+
"dynamodb:DescribeTable",
|
|
1286
|
+
"dynamodb:PutItem",
|
|
1287
|
+
"dynamodb:GetItem",
|
|
1288
|
+
"dynamodb:DeleteItem",
|
|
1289
|
+
"dynamodb:TransactWrite",
|
|
1290
|
+
"dynamodb:BatchWriteItem",
|
|
1291
|
+
"dynamodb:BatchGetItem",
|
|
1292
|
+
"dynamodb:ConditionCheckItem",
|
|
1293
|
+
"dynamodb:Query",
|
|
1294
|
+
"dynamodb:Scan"
|
|
1295
|
+
],
|
|
1296
|
+
resources: [this.arn]
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
properties() {
|
|
1300
|
+
return {
|
|
1301
|
+
TableName: this.name,
|
|
1302
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
1303
|
+
TableClass: constantCase2(this.props.class || "standard"),
|
|
1304
|
+
PointInTimeRecoverySpecification: {
|
|
1305
|
+
PointInTimeRecoveryEnabled: this.props.pointInTimeRecovery || false
|
|
1306
|
+
},
|
|
1307
|
+
KeySchema: [
|
|
1308
|
+
{ KeyType: "HASH", AttributeName: this.props.hash },
|
|
1309
|
+
...this.props.sort ? [{ KeyType: "RANGE", AttributeName: this.props.sort }] : []
|
|
1310
|
+
],
|
|
1311
|
+
AttributeDefinitions: Object.entries(this.props.fields).map(([name, type]) => ({
|
|
1312
|
+
AttributeName: name,
|
|
1313
|
+
AttributeType: type[0].toUpperCase()
|
|
1314
|
+
})),
|
|
1315
|
+
...this.props.timeToLiveAttribute ? {
|
|
1316
|
+
TimeToLiveSpecification: {
|
|
1317
|
+
AttributeName: this.props.timeToLiveAttribute,
|
|
1318
|
+
Enabled: true
|
|
1319
|
+
}
|
|
1320
|
+
} : {},
|
|
1321
|
+
...Object.keys(this.indexes).length ? {
|
|
1322
|
+
GlobalSecondaryIndexes: Object.entries(this.indexes).map(([name, props]) => ({
|
|
1323
|
+
IndexName: name,
|
|
1324
|
+
KeySchema: [
|
|
1325
|
+
{ KeyType: "HASH", AttributeName: props.hash },
|
|
1326
|
+
...props.sort ? [{ KeyType: "RANGE", AttributeName: props.sort }] : []
|
|
1327
|
+
],
|
|
1328
|
+
Projection: {
|
|
1329
|
+
ProjectionType: constantCase2(props.projection || "all")
|
|
1330
|
+
}
|
|
1331
|
+
}))
|
|
1332
|
+
} : {}
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
745
1335
|
};
|
|
746
|
-
var AttributeSchema = z13.enum(Object.keys(types2)).transform((value) => types2[value]);
|
|
747
|
-
|
|
748
|
-
// src/plugins/table/schema/key.ts
|
|
749
|
-
import { z as z14 } from "zod";
|
|
750
|
-
var KeySchema = z14.string().min(1).max(255);
|
|
751
|
-
|
|
752
|
-
// src/plugins/table/schema/projection-type.ts
|
|
753
|
-
import { ProjectionType } from "aws-cdk-lib/aws-dynamodb";
|
|
754
|
-
import { z as z15 } from "zod";
|
|
755
|
-
var types3 = {
|
|
756
|
-
"all": ProjectionType.ALL,
|
|
757
|
-
"keys-only": ProjectionType.KEYS_ONLY
|
|
758
|
-
};
|
|
759
|
-
var ProjectionTypeSchema = z15.union([
|
|
760
|
-
z15.enum(Object.keys(types3)).transform((value) => ({
|
|
761
|
-
ProjectionType: types3[value]
|
|
762
|
-
})),
|
|
763
|
-
z15.array(KeySchema).min(0).max(20).transform((keys) => ({
|
|
764
|
-
ProjectionType: ProjectionType.INCLUDE,
|
|
765
|
-
NonKeyAttributes: keys
|
|
766
|
-
}))
|
|
767
|
-
]);
|
|
768
1336
|
|
|
769
|
-
// src/plugins/table
|
|
1337
|
+
// src/plugins/table.ts
|
|
1338
|
+
var KeySchema = z9.string().min(1).max(255);
|
|
770
1339
|
var tablePlugin = definePlugin({
|
|
771
1340
|
name: "table",
|
|
772
|
-
schema:
|
|
773
|
-
stacks:
|
|
774
|
-
tables
|
|
1341
|
+
schema: z9.object({
|
|
1342
|
+
stacks: z9.object({
|
|
1343
|
+
/** Define the tables in your stack
|
|
1344
|
+
* @example
|
|
1345
|
+
* {
|
|
1346
|
+
* tables: {
|
|
1347
|
+
* TABLE_NAME: {
|
|
1348
|
+
* hash: 'id',
|
|
1349
|
+
* fields: {
|
|
1350
|
+
* id: 'number'
|
|
1351
|
+
* }
|
|
1352
|
+
* }
|
|
1353
|
+
* }
|
|
1354
|
+
* }
|
|
1355
|
+
* */
|
|
1356
|
+
tables: z9.record(
|
|
775
1357
|
ResourceIdSchema,
|
|
776
|
-
|
|
1358
|
+
z9.object({
|
|
1359
|
+
/** Specifies the name of the partition / hash key that makes up the primary key for the table. */
|
|
777
1360
|
hash: KeySchema,
|
|
1361
|
+
/** Specifies the name of the range / sort key that makes up the primary key for the table. */
|
|
778
1362
|
sort: KeySchema.optional(),
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1363
|
+
/** A list of attributes that describe the key schema for the table and indexes.
|
|
1364
|
+
* @example
|
|
1365
|
+
* {
|
|
1366
|
+
* fields: {
|
|
1367
|
+
* id: 'string'
|
|
1368
|
+
* }
|
|
1369
|
+
* }
|
|
1370
|
+
*/
|
|
1371
|
+
fields: z9.record(z9.string(), z9.enum(["string", "number", "binary"])),
|
|
1372
|
+
/** The table class of the table.
|
|
1373
|
+
* @default 'standard'
|
|
1374
|
+
*/
|
|
1375
|
+
class: z9.enum(["standard", "standard-infrequent-access"]).default("standard"),
|
|
1376
|
+
/** Indicates whether point in time recovery is enabled on the table.
|
|
1377
|
+
* @default false
|
|
1378
|
+
*/
|
|
1379
|
+
pointInTimeRecovery: z9.boolean().default(false),
|
|
1380
|
+
/** The name of the TTL attribute used to store the expiration time for items in the table.
|
|
1381
|
+
* - To update this property, you must first disable TTL and then enable TTL with the new attribute name.
|
|
1382
|
+
*/
|
|
1383
|
+
timeToLiveAttribute: KeySchema.optional(),
|
|
1384
|
+
/** Specifies the global secondary indexes to be created on the table.
|
|
1385
|
+
* @example
|
|
1386
|
+
* {
|
|
1387
|
+
* indexes: {
|
|
1388
|
+
* INDEX_NAME: {
|
|
1389
|
+
* hash: 'other'
|
|
1390
|
+
* }
|
|
1391
|
+
* }
|
|
1392
|
+
* }
|
|
1393
|
+
*/
|
|
1394
|
+
indexes: z9.record(z9.string(), z9.object({
|
|
1395
|
+
/** Specifies the name of the partition / hash key that makes up the primary key for the global secondary index. */
|
|
784
1396
|
hash: KeySchema,
|
|
1397
|
+
/** Specifies the name of the range / sort key that makes up the primary key for the global secondary index. */
|
|
785
1398
|
sort: KeySchema.optional(),
|
|
786
|
-
|
|
1399
|
+
/** The set of attributes that are projected into the index:
|
|
1400
|
+
* - all - All of the table attributes are projected into the index.
|
|
1401
|
+
* - keys-only - Only the index and primary keys are projected into the index.
|
|
1402
|
+
* @default 'all'
|
|
1403
|
+
*/
|
|
1404
|
+
projection: z9.enum(["all", "keys-only"]).default("all")
|
|
787
1405
|
})).optional()
|
|
788
1406
|
}).refine((props) => {
|
|
789
1407
|
return (
|
|
@@ -800,640 +1418,1031 @@ var tablePlugin = definePlugin({
|
|
|
800
1418
|
).optional()
|
|
801
1419
|
}).array()
|
|
802
1420
|
}),
|
|
803
|
-
onStack({ stack, stackConfig, bind }) {
|
|
804
|
-
Object.entries(stackConfig.tables || {})
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const table = new Table(stack, toId("table", id), {
|
|
809
|
-
tableName: toName(stack, id),
|
|
810
|
-
partitionKey: buildKey(props.hash),
|
|
811
|
-
sortKey: props.sort ? buildKey(props.sort) : void 0,
|
|
812
|
-
billingMode: BillingMode.PAY_PER_REQUEST,
|
|
813
|
-
pointInTimeRecovery: props.pointInTimeRecovery,
|
|
814
|
-
timeToLiveAttribute: props.timeToLiveAttribute,
|
|
815
|
-
tableClass: props.class
|
|
816
|
-
});
|
|
817
|
-
Object.entries(props.indexes || {}).forEach(([indexName, entry]) => {
|
|
818
|
-
table.addGlobalSecondaryIndex({
|
|
819
|
-
indexName,
|
|
820
|
-
partitionKey: buildKey(entry.hash),
|
|
821
|
-
sortKey: entry.sort ? buildKey(entry.sort) : void 0,
|
|
822
|
-
...entry.projection
|
|
823
|
-
});
|
|
1421
|
+
onStack({ config, stack, stackConfig, bind }) {
|
|
1422
|
+
for (const [id, props] of Object.entries(stackConfig.tables || {})) {
|
|
1423
|
+
const table = new Table(id, {
|
|
1424
|
+
name: `${config.name}-${stack.name}-${id}`,
|
|
1425
|
+
...props
|
|
824
1426
|
});
|
|
1427
|
+
stack.add(table);
|
|
825
1428
|
bind((lambda) => {
|
|
826
|
-
table.
|
|
827
|
-
addResourceEnvironment(stack, "table", id, lambda);
|
|
1429
|
+
lambda.addPermissions(table.permissions);
|
|
828
1430
|
});
|
|
829
|
-
}
|
|
1431
|
+
}
|
|
830
1432
|
}
|
|
831
1433
|
});
|
|
832
1434
|
|
|
833
1435
|
// src/plugins/store.ts
|
|
834
|
-
import { z as
|
|
835
|
-
|
|
836
|
-
|
|
1436
|
+
import { z as z10 } from "zod";
|
|
1437
|
+
|
|
1438
|
+
// src/formation/resource/s3/bucket.ts
|
|
1439
|
+
import { pascalCase as pascalCase2 } from "change-case";
|
|
1440
|
+
var Bucket = class extends Resource {
|
|
1441
|
+
constructor(logicalId, props = {}) {
|
|
1442
|
+
super("AWS::S3::Bucket", logicalId);
|
|
1443
|
+
this.props = props;
|
|
1444
|
+
this.name = formatName(this.props.name || logicalId);
|
|
1445
|
+
}
|
|
1446
|
+
name;
|
|
1447
|
+
get arn() {
|
|
1448
|
+
return ref(this.logicalId);
|
|
1449
|
+
}
|
|
1450
|
+
get domainName() {
|
|
1451
|
+
return getAtt(this.logicalId, "DomainName");
|
|
1452
|
+
}
|
|
1453
|
+
get permissions() {
|
|
1454
|
+
return {
|
|
1455
|
+
actions: [
|
|
1456
|
+
"s3:SendMessage",
|
|
1457
|
+
"s3:ReceiveMessage",
|
|
1458
|
+
"s3:GetQueueUrl",
|
|
1459
|
+
"s3:GetQueueAttributes"
|
|
1460
|
+
],
|
|
1461
|
+
resources: [this.arn]
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
properties() {
|
|
1465
|
+
return {
|
|
1466
|
+
BucketName: this.name,
|
|
1467
|
+
AccessControl: pascalCase2(this.props.accessControl ?? "private"),
|
|
1468
|
+
...this.props.versioned ? {
|
|
1469
|
+
VersioningConfiguration: {
|
|
1470
|
+
Status: "Enabled"
|
|
1471
|
+
}
|
|
1472
|
+
} : {}
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
// src/plugins/store.ts
|
|
837
1478
|
var storePlugin = definePlugin({
|
|
838
1479
|
name: "store",
|
|
839
|
-
schema:
|
|
840
|
-
stacks:
|
|
841
|
-
stores
|
|
1480
|
+
schema: z10.object({
|
|
1481
|
+
stacks: z10.object({
|
|
1482
|
+
/** Define the stores in your stack
|
|
1483
|
+
* @example
|
|
1484
|
+
* {
|
|
1485
|
+
* stores: [ 'STORE_NAME' ]
|
|
1486
|
+
* }
|
|
1487
|
+
* */
|
|
1488
|
+
stores: z10.array(ResourceIdSchema).optional()
|
|
842
1489
|
}).array()
|
|
843
1490
|
}),
|
|
844
|
-
onStack({ stack, stackConfig, bind }) {
|
|
845
|
-
(stackConfig.stores || [])
|
|
846
|
-
const bucket = new
|
|
847
|
-
|
|
848
|
-
accessControl:
|
|
849
|
-
removalPolicy: RemovalPolicy2.DESTROY
|
|
1491
|
+
onStack({ config, stack, stackConfig, bind }) {
|
|
1492
|
+
for (const id of stackConfig.stores || []) {
|
|
1493
|
+
const bucket = new Bucket(id, {
|
|
1494
|
+
name: `${config.name}-${stack.name}-${id}`,
|
|
1495
|
+
accessControl: "private"
|
|
850
1496
|
});
|
|
1497
|
+
stack.add(bucket);
|
|
851
1498
|
bind((lambda) => {
|
|
852
|
-
bucket.
|
|
1499
|
+
lambda.addPermissions(bucket.permissions);
|
|
853
1500
|
});
|
|
854
|
-
}
|
|
1501
|
+
}
|
|
855
1502
|
}
|
|
856
1503
|
});
|
|
857
1504
|
|
|
858
1505
|
// src/plugins/topic.ts
|
|
859
|
-
import { z as
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1506
|
+
import { z as z11 } from "zod";
|
|
1507
|
+
|
|
1508
|
+
// src/formation/resource/sns/topic.ts
|
|
1509
|
+
var Topic = class extends Resource {
|
|
1510
|
+
constructor(logicalId, props = {}) {
|
|
1511
|
+
super("AWS::SNS::Topic", logicalId);
|
|
1512
|
+
this.props = props;
|
|
1513
|
+
this.name = formatName(this.props.name || logicalId);
|
|
1514
|
+
}
|
|
1515
|
+
name;
|
|
1516
|
+
get arn() {
|
|
1517
|
+
return ref(this.logicalId);
|
|
1518
|
+
}
|
|
1519
|
+
get permissions() {
|
|
1520
|
+
return {
|
|
1521
|
+
actions: ["sns:Publish"],
|
|
1522
|
+
resources: [this.arn]
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
properties() {
|
|
1526
|
+
return {
|
|
1527
|
+
TopicName: this.name,
|
|
1528
|
+
DisplayName: this.name
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
// src/formation/resource/sns/subscription.ts
|
|
1534
|
+
var Subscription = class extends Resource {
|
|
1535
|
+
constructor(logicalId, props) {
|
|
1536
|
+
super("AWS::SNS::Subscription", logicalId);
|
|
1537
|
+
this.props = props;
|
|
1538
|
+
}
|
|
1539
|
+
properties() {
|
|
1540
|
+
return {
|
|
1541
|
+
TopicArn: this.props.topicArn,
|
|
1542
|
+
Protocol: this.props.protocol,
|
|
1543
|
+
Endpoint: this.props.endpoint
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
// src/formation/resource/lambda/event-source/sns.ts
|
|
1549
|
+
var SnsEventSource = class extends Group {
|
|
1550
|
+
constructor(id, lambda, props) {
|
|
1551
|
+
const topic = new Subscription(id, {
|
|
1552
|
+
topicArn: props.topicArn,
|
|
1553
|
+
protocol: "lambda",
|
|
1554
|
+
endpoint: lambda.arn
|
|
1555
|
+
});
|
|
1556
|
+
const permission = new Permission2(id, {
|
|
1557
|
+
action: "lambda:InvokeFunction",
|
|
1558
|
+
principal: "sns.amazonaws.com",
|
|
1559
|
+
functionArn: lambda.arn,
|
|
1560
|
+
sourceArn: props.topicArn
|
|
1561
|
+
});
|
|
1562
|
+
super([topic, permission]);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
// src/plugins/topic.ts
|
|
863
1567
|
var topicPlugin = definePlugin({
|
|
864
1568
|
name: "topic",
|
|
865
|
-
schema:
|
|
866
|
-
stacks:
|
|
867
|
-
topics
|
|
1569
|
+
schema: z11.object({
|
|
1570
|
+
stacks: z11.object({
|
|
1571
|
+
/** Define the topics to listen too in your stack
|
|
1572
|
+
* @example
|
|
1573
|
+
* {
|
|
1574
|
+
* topics: {
|
|
1575
|
+
* TOPIC_NAME: 'function.ts'
|
|
1576
|
+
* }
|
|
1577
|
+
* }
|
|
1578
|
+
* */
|
|
1579
|
+
topics: z11.record(ResourceIdSchema, FunctionSchema).optional()
|
|
868
1580
|
}).array()
|
|
869
1581
|
}),
|
|
870
|
-
|
|
871
|
-
const allTopicNames = config.stacks.map((
|
|
872
|
-
return Object.keys(
|
|
1582
|
+
onApp({ config, bootstrap: bootstrap2, bind }) {
|
|
1583
|
+
const allTopicNames = config.stacks.map((stack) => {
|
|
1584
|
+
return Object.keys(stack.topics || {});
|
|
873
1585
|
}).flat();
|
|
874
1586
|
const uniqueTopicNames = [...new Set(allTopicNames)];
|
|
875
|
-
|
|
876
|
-
new Topic(
|
|
877
|
-
|
|
878
|
-
|
|
1587
|
+
for (const id of uniqueTopicNames) {
|
|
1588
|
+
const topic = new Topic(id, {
|
|
1589
|
+
name: `${config.name}-${id}`
|
|
1590
|
+
});
|
|
1591
|
+
bootstrap2.add(topic);
|
|
1592
|
+
bootstrap2.export(`topic-${id}-arn`, topic.arn);
|
|
1593
|
+
}
|
|
1594
|
+
bind((lambda) => {
|
|
1595
|
+
lambda.addPermissions({
|
|
1596
|
+
actions: ["sns:Publish"],
|
|
1597
|
+
resources: [
|
|
1598
|
+
sub("arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${app}-*", {
|
|
1599
|
+
app: config.name
|
|
1600
|
+
})
|
|
1601
|
+
]
|
|
879
1602
|
});
|
|
880
1603
|
});
|
|
881
1604
|
},
|
|
882
1605
|
onStack(ctx) {
|
|
883
|
-
const {
|
|
884
|
-
|
|
885
|
-
const lambda =
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
toId("topic", id),
|
|
889
|
-
Arn2.format({
|
|
890
|
-
arnFormat: ArnFormat.NO_RESOURCE_NAME,
|
|
891
|
-
service: "sns",
|
|
892
|
-
resource: `${config.name}-${id}`
|
|
893
|
-
}, stack)
|
|
894
|
-
);
|
|
895
|
-
lambda.addEventSource(new SnsEventSource(topic));
|
|
896
|
-
bind((lambda2) => {
|
|
897
|
-
addResourceEnvironment(stack, "topic", id, lambda2);
|
|
898
|
-
topic.grantPublish(lambda2);
|
|
1606
|
+
const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
|
|
1607
|
+
for (const [id, props] of Object.entries(stackConfig.topics || {})) {
|
|
1608
|
+
const lambda = toLambdaFunction(ctx, id, props);
|
|
1609
|
+
const source = new SnsEventSource(id, lambda, {
|
|
1610
|
+
topicArn: bootstrap2.import(`topic-${id}-arn`)
|
|
899
1611
|
});
|
|
900
|
-
|
|
901
|
-
}
|
|
1612
|
+
stack.add(lambda, source);
|
|
1613
|
+
}
|
|
902
1614
|
}
|
|
903
1615
|
});
|
|
904
1616
|
|
|
905
|
-
// src/plugins/
|
|
906
|
-
import { z as
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
return [value];
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
// src/plugins/graphql/index.ts
|
|
920
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
921
|
-
import { print } from "graphql";
|
|
922
|
-
import { paramCase as paramCase2 } from "change-case";
|
|
923
|
-
|
|
924
|
-
// src/plugins/graphql/schema/resolver-field.ts
|
|
925
|
-
import { z as z19 } from "zod";
|
|
926
|
-
var ResolverFieldSchema = z19.custom((value) => {
|
|
927
|
-
return z19.string().regex(/([a-z0-9\_]+)(\s){1}([a-z0-9\_]+)/gi).safeParse(value).success;
|
|
928
|
-
}, `Invalid resolver field. Valid example: "Query list"`);
|
|
929
|
-
|
|
930
|
-
// src/plugins/graphql/index.ts
|
|
931
|
-
import { CfnOutput as CfnOutput2, Fn } from "aws-cdk-lib";
|
|
932
|
-
var graphqlPlugin = definePlugin({
|
|
933
|
-
name: "graphql",
|
|
934
|
-
schema: z20.object({
|
|
935
|
-
defaults: z20.object({
|
|
936
|
-
graphql: z20.record(ResourceIdSchema, z20.object({
|
|
937
|
-
authorization: z20.object({
|
|
938
|
-
authorizer: FunctionSchema,
|
|
939
|
-
ttl: DurationSchema.default("1 hour")
|
|
940
|
-
}).optional(),
|
|
941
|
-
mappingTemplate: z20.object({
|
|
942
|
-
request: LocalFileSchema.optional(),
|
|
943
|
-
response: LocalFileSchema.optional()
|
|
944
|
-
}).optional()
|
|
945
|
-
})).optional()
|
|
946
|
-
}).default({}),
|
|
947
|
-
stacks: z20.object({
|
|
948
|
-
graphql: z20.record(ResourceIdSchema, z20.object({
|
|
949
|
-
schema: z20.union([
|
|
950
|
-
LocalFileSchema,
|
|
951
|
-
z20.array(LocalFileSchema).min(1)
|
|
952
|
-
]).optional(),
|
|
953
|
-
resolvers: z20.record(ResolverFieldSchema, FunctionSchema).optional()
|
|
954
|
-
})).optional()
|
|
1617
|
+
// src/plugins/extend.ts
|
|
1618
|
+
import { z as z12 } from "zod";
|
|
1619
|
+
var extendPlugin = definePlugin({
|
|
1620
|
+
name: "extend",
|
|
1621
|
+
schema: z12.object({
|
|
1622
|
+
/** Extend your app with custom resources */
|
|
1623
|
+
extend: z12.custom().optional(),
|
|
1624
|
+
stacks: z12.object({
|
|
1625
|
+
/** Extend your stack with custom resources */
|
|
1626
|
+
extend: z12.custom().optional()
|
|
955
1627
|
}).array()
|
|
956
1628
|
}),
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
Object.values(config.stacks).forEach((stackConfig) => {
|
|
960
|
-
Object.keys(stackConfig.graphql || {}).forEach((id) => {
|
|
961
|
-
list3.add(id);
|
|
962
|
-
});
|
|
963
|
-
});
|
|
964
|
-
list3.forEach((id) => {
|
|
965
|
-
const file = join4(assetDir, "graphql", config.name, id, "schema.graphql");
|
|
966
|
-
const authorization = config.defaults.graphql?.[id]?.authorization;
|
|
967
|
-
const authProps = {};
|
|
968
|
-
if (authorization) {
|
|
969
|
-
const authorizer = toFunction({ config, assets, stack }, `${id}-authorizer`, authorization.authorizer);
|
|
970
|
-
authProps.additionalAuthenticationProviders = [{
|
|
971
|
-
authenticationType: AuthorizationType.LAMBDA,
|
|
972
|
-
lambdaAuthorizerConfig: {
|
|
973
|
-
authorizerUri: authorizer.functionArn,
|
|
974
|
-
authorizerResultTtlInSeconds: authorization.ttl.toSeconds()
|
|
975
|
-
}
|
|
976
|
-
}];
|
|
977
|
-
}
|
|
978
|
-
const api = new CfnGraphQLApi(stack, toId("graphql", id), {
|
|
979
|
-
...authProps,
|
|
980
|
-
name: toName(stack, id),
|
|
981
|
-
authenticationType: AuthorizationType.API_KEY
|
|
982
|
-
});
|
|
983
|
-
new CfnOutput2(stack, toId("output", id), {
|
|
984
|
-
exportName: toId("graphql", id),
|
|
985
|
-
value: api.attrApiId
|
|
986
|
-
});
|
|
987
|
-
assets.add({
|
|
988
|
-
stackName: stack.artifactId,
|
|
989
|
-
resource: "schema",
|
|
990
|
-
resourceName: id,
|
|
991
|
-
async build() {
|
|
992
|
-
const schemas = [];
|
|
993
|
-
await Promise.all(Object.values(config.stacks).map(async (stackConfig) => {
|
|
994
|
-
const schemaFiles = toArray(stackConfig.graphql?.[id].schema || []);
|
|
995
|
-
await Promise.all(schemaFiles.map(async (schemaFile) => {
|
|
996
|
-
const schema3 = await readFile2(schemaFile, "utf8");
|
|
997
|
-
schemas.push(schema3);
|
|
998
|
-
}));
|
|
999
|
-
}));
|
|
1000
|
-
const schema2 = print(mergeTypeDefs(schemas));
|
|
1001
|
-
await mkdir2(dirname2(file), { recursive: true });
|
|
1002
|
-
await writeFile2(file, schema2);
|
|
1003
|
-
new CfnGraphQLSchema(stack, toId("schema", id), {
|
|
1004
|
-
apiId: api.attrApiId,
|
|
1005
|
-
definition: schema2
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
});
|
|
1629
|
+
onApp(ctx) {
|
|
1630
|
+
ctx.config.extend?.(ctx);
|
|
1010
1631
|
},
|
|
1011
1632
|
onStack(ctx) {
|
|
1012
|
-
|
|
1013
|
-
return Object.entries(stackConfig.graphql || {}).map(([id, props]) => {
|
|
1014
|
-
const defaults = config.defaults.graphql?.[id] || {};
|
|
1015
|
-
return Object.entries(props.resolvers || {}).map(([typeAndField, functionProps]) => {
|
|
1016
|
-
const api = GraphqlApi.fromGraphqlApiAttributes(stack, toId("graphql", id), {
|
|
1017
|
-
graphqlApiId: Fn.importValue(toId("graphql", id))
|
|
1018
|
-
});
|
|
1019
|
-
const [typeName, fieldName] = typeAndField.split(/[\s]+/g);
|
|
1020
|
-
const functionId = paramCase2(`${id}-${typeName}-${fieldName}`);
|
|
1021
|
-
const lambda = toFunction(ctx, functionId, functionProps);
|
|
1022
|
-
const source = api.addLambdaDataSource(toId("data-source", functionId), lambda, {
|
|
1023
|
-
name: toId("data-source", functionId)
|
|
1024
|
-
});
|
|
1025
|
-
source.createResolver(toId("resolver", functionId), {
|
|
1026
|
-
typeName,
|
|
1027
|
-
fieldName,
|
|
1028
|
-
requestMappingTemplate: defaults.mappingTemplate?.request ? MappingTemplate.fromFile(defaults.mappingTemplate.request) : MappingTemplate.lambdaRequest(),
|
|
1029
|
-
responseMappingTemplate: defaults.mappingTemplate?.response ? MappingTemplate.fromFile(defaults.mappingTemplate.response) : MappingTemplate.lambdaResult()
|
|
1030
|
-
});
|
|
1031
|
-
return lambda;
|
|
1032
|
-
});
|
|
1033
|
-
}).flat();
|
|
1633
|
+
ctx.stackConfig.extend?.(ctx);
|
|
1034
1634
|
}
|
|
1035
1635
|
});
|
|
1036
1636
|
|
|
1037
1637
|
// src/plugins/pubsub.ts
|
|
1038
|
-
import { z as
|
|
1039
|
-
|
|
1040
|
-
|
|
1638
|
+
import { z as z13 } from "zod";
|
|
1639
|
+
|
|
1640
|
+
// src/formation/resource/iot/topic-rule.ts
|
|
1041
1641
|
import { snakeCase } from "change-case";
|
|
1642
|
+
var TopicRule = class extends Resource {
|
|
1643
|
+
constructor(logicalId, props) {
|
|
1644
|
+
super("AWS::IoT::TopicRule", logicalId);
|
|
1645
|
+
this.props = props;
|
|
1646
|
+
this.name = snakeCase(this.props.name || logicalId);
|
|
1647
|
+
}
|
|
1648
|
+
name;
|
|
1649
|
+
get arn() {
|
|
1650
|
+
return getAtt(this.logicalId, "Arn");
|
|
1651
|
+
}
|
|
1652
|
+
properties() {
|
|
1653
|
+
return {
|
|
1654
|
+
RuleName: this.name,
|
|
1655
|
+
TopicRulePayload: {
|
|
1656
|
+
Sql: this.props.sql,
|
|
1657
|
+
AwsIotSqlVersion: this.props.sqlVersion ?? "2016-03-23",
|
|
1658
|
+
RuleDisabled: false,
|
|
1659
|
+
Actions: this.props.actions.map((action) => ({
|
|
1660
|
+
Lambda: { FunctionArn: action.lambda.functionArn }
|
|
1661
|
+
}))
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
|
|
1667
|
+
// src/formation/resource/lambda/event-source/iot.ts
|
|
1668
|
+
var IotEventSource = class extends Group {
|
|
1669
|
+
constructor(id, lambda, props) {
|
|
1670
|
+
const topic = new TopicRule(id, {
|
|
1671
|
+
name: props.name,
|
|
1672
|
+
sql: props.sql,
|
|
1673
|
+
sqlVersion: props.sqlVersion,
|
|
1674
|
+
actions: [{ lambda: { functionArn: lambda.arn } }]
|
|
1675
|
+
});
|
|
1676
|
+
const permission = new Permission2(id, {
|
|
1677
|
+
action: "lambda:InvokeFunction",
|
|
1678
|
+
principal: "iot.amazonaws.com",
|
|
1679
|
+
functionArn: lambda.arn,
|
|
1680
|
+
sourceArn: topic.arn
|
|
1681
|
+
});
|
|
1682
|
+
super([topic, permission]);
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
// src/plugins/pubsub.ts
|
|
1042
1687
|
var pubsubPlugin = definePlugin({
|
|
1043
1688
|
name: "pubsub",
|
|
1044
|
-
schema:
|
|
1045
|
-
stacks:
|
|
1046
|
-
pubsub
|
|
1047
|
-
|
|
1048
|
-
|
|
1689
|
+
schema: z13.object({
|
|
1690
|
+
stacks: z13.object({
|
|
1691
|
+
/** Define the pubsub subscriber in your stack
|
|
1692
|
+
* @example
|
|
1693
|
+
* {
|
|
1694
|
+
* pubsub: {
|
|
1695
|
+
* NAME: {
|
|
1696
|
+
* sql: 'SELECT * FROM "table"',
|
|
1697
|
+
* consumer: 'function.ts',
|
|
1698
|
+
* }
|
|
1699
|
+
* }
|
|
1700
|
+
* }
|
|
1701
|
+
*/
|
|
1702
|
+
pubsub: z13.record(ResourceIdSchema, z13.object({
|
|
1703
|
+
/** The SQL statement used to query the iot topic */
|
|
1704
|
+
sql: z13.string(),
|
|
1705
|
+
/** The version of the SQL rules engine to use when evaluating the rule */
|
|
1706
|
+
sqlVersion: z13.enum(["2015-10-08", "2016-03-23", "beta"]).default("2016-03-23"),
|
|
1707
|
+
/** The consuming lambda function properties */
|
|
1049
1708
|
consumer: FunctionSchema
|
|
1050
1709
|
})).optional()
|
|
1051
1710
|
}).array()
|
|
1052
1711
|
}),
|
|
1053
|
-
|
|
1054
|
-
const { stack, stackConfig, bind } = ctx;
|
|
1712
|
+
onApp({ bind }) {
|
|
1055
1713
|
bind((lambda) => {
|
|
1056
|
-
lambda.
|
|
1714
|
+
lambda.addPermissions({
|
|
1057
1715
|
actions: ["iot:publish"],
|
|
1058
1716
|
resources: ["*"]
|
|
1059
|
-
}));
|
|
1060
|
-
});
|
|
1061
|
-
return Object.entries(stackConfig.pubsub || {}).map(([id, props]) => {
|
|
1062
|
-
const lambda = toFunction(ctx, id, props.consumer);
|
|
1063
|
-
new CfnTopicRule(stack, toId("pubsub", id), {
|
|
1064
|
-
ruleName: snakeCase(toName(stack, id)),
|
|
1065
|
-
topicRulePayload: {
|
|
1066
|
-
sql: props.sql,
|
|
1067
|
-
awsIotSqlVersion: props.sqlVersion,
|
|
1068
|
-
actions: [{
|
|
1069
|
-
lambda: {
|
|
1070
|
-
functionArn: lambda.functionArn
|
|
1071
|
-
}
|
|
1072
|
-
}]
|
|
1073
|
-
}
|
|
1074
1717
|
});
|
|
1075
|
-
return lambda;
|
|
1076
1718
|
});
|
|
1719
|
+
},
|
|
1720
|
+
onStack(ctx) {
|
|
1721
|
+
const { config, stack, stackConfig } = ctx;
|
|
1722
|
+
for (const [id, props] of Object.entries(stackConfig.pubsub || {})) {
|
|
1723
|
+
const lambda = toLambdaFunction(ctx, id, props.consumer);
|
|
1724
|
+
const source = new IotEventSource(id, lambda, {
|
|
1725
|
+
name: `${config.name}-${stack.name}-${id}`,
|
|
1726
|
+
sql: props.sql,
|
|
1727
|
+
sqlVersion: props.sqlVersion
|
|
1728
|
+
});
|
|
1729
|
+
stack.add(lambda, source);
|
|
1730
|
+
}
|
|
1077
1731
|
}
|
|
1078
1732
|
});
|
|
1079
1733
|
|
|
1080
|
-
// src/plugins/
|
|
1081
|
-
import { z as
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1734
|
+
// src/plugins/graphql.ts
|
|
1735
|
+
import { z as z14 } from "zod";
|
|
1736
|
+
|
|
1737
|
+
// src/util/array.ts
|
|
1738
|
+
var toArray = (value) => {
|
|
1739
|
+
if (Array.isArray(value)) {
|
|
1740
|
+
return value;
|
|
1741
|
+
}
|
|
1742
|
+
return [value];
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1745
|
+
// src/plugins/graphql.ts
|
|
1089
1746
|
import { paramCase as paramCase3 } from "change-case";
|
|
1090
1747
|
|
|
1091
|
-
// src/
|
|
1092
|
-
import {
|
|
1093
|
-
var
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1748
|
+
// src/formation/resource/appsync/graphql-api.ts
|
|
1749
|
+
import { constantCase as constantCase3 } from "change-case";
|
|
1750
|
+
var GraphQL = class extends Group {
|
|
1751
|
+
constructor(logicalId, props) {
|
|
1752
|
+
const api = new GraphQLApi(logicalId, props);
|
|
1753
|
+
const schema2 = new GraphQLSchema(logicalId, {
|
|
1754
|
+
apiId: api.id,
|
|
1755
|
+
definition: props.schema
|
|
1756
|
+
}).dependsOn(api);
|
|
1757
|
+
super([api, schema2]);
|
|
1758
|
+
this.logicalId = logicalId;
|
|
1759
|
+
this.api = api;
|
|
1760
|
+
this.schema = schema2;
|
|
1761
|
+
}
|
|
1762
|
+
api;
|
|
1763
|
+
schema;
|
|
1764
|
+
attachDomainName(domainName, certificateArn) {
|
|
1765
|
+
const id = this.logicalId + domainName;
|
|
1766
|
+
const domain = new DomainName(id, {
|
|
1767
|
+
domainName,
|
|
1768
|
+
certificateArn
|
|
1769
|
+
});
|
|
1770
|
+
const association = new DomainNameApiAssociation(id, {
|
|
1771
|
+
apiId: this.api.id,
|
|
1772
|
+
domainName
|
|
1773
|
+
}).dependsOn(this.api, domain);
|
|
1774
|
+
this.children.push(domain, association);
|
|
1775
|
+
return this;
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
var GraphQLApi = class extends Resource {
|
|
1779
|
+
constructor(logicalId, props) {
|
|
1780
|
+
super("AWS::AppSync::GraphQLApi", logicalId);
|
|
1781
|
+
this.props = props;
|
|
1782
|
+
this.name = formatName(this.props.name || logicalId);
|
|
1783
|
+
}
|
|
1784
|
+
name;
|
|
1785
|
+
lambdaAuthProviders = [];
|
|
1786
|
+
get arn() {
|
|
1787
|
+
return ref(this.logicalId);
|
|
1788
|
+
}
|
|
1789
|
+
get id() {
|
|
1790
|
+
return getAtt(this.logicalId, "ApiId");
|
|
1791
|
+
}
|
|
1792
|
+
get url() {
|
|
1793
|
+
return getAtt(this.logicalId, "GraphQLUrl");
|
|
1794
|
+
}
|
|
1795
|
+
get dns() {
|
|
1796
|
+
return getAtt(this.logicalId, "GraphQLDns");
|
|
1797
|
+
}
|
|
1798
|
+
addLambdaAuthProvider(lambdaAuthorizerArn, resultTTL = Duration.seconds(0)) {
|
|
1799
|
+
this.lambdaAuthProviders.push({
|
|
1800
|
+
arn: lambdaAuthorizerArn,
|
|
1801
|
+
ttl: resultTTL
|
|
1802
|
+
});
|
|
1803
|
+
return this;
|
|
1804
|
+
}
|
|
1805
|
+
properties() {
|
|
1806
|
+
return {
|
|
1807
|
+
Name: this.name,
|
|
1808
|
+
AuthenticationType: constantCase3(this.props.authenticationType || "api-key"),
|
|
1809
|
+
AdditionalAuthenticationProviders: this.lambdaAuthProviders.map((provider) => ({
|
|
1810
|
+
AuthenticationType: "AWS_LAMBDA",
|
|
1811
|
+
LambdaAuthorizerConfig: {
|
|
1812
|
+
AuthorizerUri: provider.arn,
|
|
1813
|
+
AuthorizerResultTtlInSeconds: provider.ttl.toSeconds()
|
|
1814
|
+
}
|
|
1815
|
+
}))
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
var GraphQLSchema = class extends Resource {
|
|
1820
|
+
constructor(logicalId, props) {
|
|
1821
|
+
super("AWS::AppSync::GraphQLSchema", logicalId, [
|
|
1822
|
+
props.definition
|
|
1823
|
+
]);
|
|
1824
|
+
this.props = props;
|
|
1825
|
+
}
|
|
1826
|
+
properties() {
|
|
1827
|
+
return {
|
|
1828
|
+
ApiId: this.props.apiId,
|
|
1829
|
+
Definition: this.props.definition.toDefinition()
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
var DomainName = class extends Resource {
|
|
1834
|
+
constructor(logicalId, props) {
|
|
1835
|
+
super("AWS::AppSync::DomainName", logicalId);
|
|
1836
|
+
this.props = props;
|
|
1837
|
+
}
|
|
1838
|
+
properties() {
|
|
1839
|
+
return {
|
|
1840
|
+
DomainName: this.props.domainName,
|
|
1841
|
+
CertificateArn: this.props.certificateArn
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
var DomainNameApiAssociation = class extends Resource {
|
|
1846
|
+
constructor(logicalId, props) {
|
|
1847
|
+
super("AWS::AppSync::DomainNameApiAssociation", logicalId);
|
|
1848
|
+
this.props = props;
|
|
1849
|
+
}
|
|
1850
|
+
properties() {
|
|
1851
|
+
return {
|
|
1852
|
+
ApiId: this.props.apiId,
|
|
1853
|
+
DomainName: this.props.domainName
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
|
|
1858
|
+
// src/formation/resource/route53/record-set.ts
|
|
1859
|
+
var RecordSet = class extends Resource {
|
|
1860
|
+
constructor(logicalId, props) {
|
|
1861
|
+
super("AWS::Route53::RecordSet", logicalId);
|
|
1862
|
+
this.props = props;
|
|
1863
|
+
this.name = this.props.name || this.logicalId;
|
|
1864
|
+
}
|
|
1865
|
+
name;
|
|
1866
|
+
properties() {
|
|
1867
|
+
return {
|
|
1868
|
+
HostedZoneId: this.props.hostedZoneId,
|
|
1869
|
+
Name: this.name + ".",
|
|
1870
|
+
Type: this.props.type,
|
|
1871
|
+
TTL: this.props.ttl,
|
|
1872
|
+
...this.props.records ? {
|
|
1873
|
+
ResourceRecords: this.props.records
|
|
1874
|
+
} : {},
|
|
1875
|
+
...this.props.alias ? {
|
|
1876
|
+
AliasTarget: {
|
|
1877
|
+
DNSName: this.props.alias,
|
|
1878
|
+
HostedZoneId: this.props.hostedZoneId
|
|
1879
|
+
}
|
|
1880
|
+
} : {}
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
|
|
1885
|
+
// src/formation/resource/appsync/schema.ts
|
|
1886
|
+
import { print } from "graphql";
|
|
1887
|
+
import { readFile } from "fs/promises";
|
|
1888
|
+
import { mergeTypeDefs } from "@graphql-tools/merge";
|
|
1889
|
+
var Schema = class extends Asset {
|
|
1890
|
+
constructor(id, files) {
|
|
1891
|
+
super("graphql", id);
|
|
1892
|
+
this.files = files;
|
|
1893
|
+
}
|
|
1894
|
+
schema;
|
|
1895
|
+
async build({ write }) {
|
|
1896
|
+
const files = [this.files].flat();
|
|
1897
|
+
const schemas = await Promise.all(files.map((file) => {
|
|
1898
|
+
return readFile(file, "utf8");
|
|
1899
|
+
}));
|
|
1900
|
+
const defs = mergeTypeDefs(schemas);
|
|
1901
|
+
const schema2 = print(defs);
|
|
1902
|
+
await write("schema.gql", schema2);
|
|
1903
|
+
this.schema = schema2;
|
|
1904
|
+
}
|
|
1905
|
+
toDefinition() {
|
|
1906
|
+
return this.schema;
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
// src/formation/resource/appsync/code.ts
|
|
1911
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1912
|
+
var Code2 = class {
|
|
1913
|
+
static fromFile(id, file) {
|
|
1914
|
+
return new FileCode2(id, file);
|
|
1915
|
+
}
|
|
1916
|
+
static fromInline(id, code) {
|
|
1917
|
+
return new InlineCode2(id, code);
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
var InlineCode2 = class extends Asset {
|
|
1921
|
+
constructor(id, code) {
|
|
1922
|
+
super("resolver", id);
|
|
1923
|
+
this.code = code;
|
|
1924
|
+
}
|
|
1925
|
+
toCodeJson() {
|
|
1926
|
+
return {
|
|
1927
|
+
Code: this.code
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
var FileCode2 = class extends Asset {
|
|
1932
|
+
constructor(id, file) {
|
|
1933
|
+
super("resolver", id);
|
|
1934
|
+
this.file = file;
|
|
1935
|
+
}
|
|
1936
|
+
code;
|
|
1937
|
+
async build() {
|
|
1938
|
+
const code = await readFile2(this.file);
|
|
1939
|
+
this.code = code.toString("utf8");
|
|
1940
|
+
return {
|
|
1941
|
+
size: formatByteSize(code.byteLength)
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
toCodeJson() {
|
|
1945
|
+
return {
|
|
1946
|
+
Code: this.code
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
// src/formation/resource/appsync/data-source.ts
|
|
1952
|
+
import { snakeCase as snakeCase2 } from "change-case";
|
|
1953
|
+
var DataSource = class extends Resource {
|
|
1954
|
+
constructor(logicalId, props) {
|
|
1955
|
+
super("AWS::AppSync::DataSource", logicalId);
|
|
1956
|
+
this.props = props;
|
|
1957
|
+
this.name = snakeCase2(this.props.name || logicalId);
|
|
1958
|
+
}
|
|
1959
|
+
static fromLambda(logicalId, apiId, props) {
|
|
1960
|
+
return new DataSource(logicalId, {
|
|
1961
|
+
apiId,
|
|
1962
|
+
type: "AWS_LAMBDA",
|
|
1963
|
+
serviceRoleArn: props.serviceRoleArn,
|
|
1964
|
+
config: {
|
|
1965
|
+
lambda: {
|
|
1966
|
+
functionArn: props.functionArn
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
static fromNone(logicalId, apiId) {
|
|
1972
|
+
return new DataSource(logicalId, {
|
|
1973
|
+
apiId,
|
|
1974
|
+
type: "NONE"
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
name;
|
|
1978
|
+
get arn() {
|
|
1979
|
+
return ref(this.logicalId);
|
|
1980
|
+
}
|
|
1981
|
+
properties() {
|
|
1982
|
+
return {
|
|
1983
|
+
ApiId: this.props.apiId,
|
|
1984
|
+
Name: this.name,
|
|
1985
|
+
Type: this.props.type,
|
|
1986
|
+
ServiceRoleArn: this.props.serviceRoleArn,
|
|
1987
|
+
...this.props.config?.lambda ? {
|
|
1988
|
+
LambdaConfig: {
|
|
1989
|
+
LambdaFunctionArn: this.props.config.lambda.functionArn
|
|
1990
|
+
}
|
|
1991
|
+
} : {}
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
// src/formation/resource/appsync/function-configuration.ts
|
|
1997
|
+
import { snakeCase as snakeCase3 } from "change-case";
|
|
1998
|
+
var FunctionConfiguration = class extends Resource {
|
|
1999
|
+
constructor(logicalId, props) {
|
|
2000
|
+
super("AWS::AppSync::FunctionConfiguration", logicalId, [
|
|
2001
|
+
props.code
|
|
2002
|
+
]);
|
|
2003
|
+
this.props = props;
|
|
2004
|
+
this.name = snakeCase3(this.props.name || logicalId);
|
|
2005
|
+
}
|
|
2006
|
+
name;
|
|
2007
|
+
get id() {
|
|
2008
|
+
return getAtt(this.logicalId, "FunctionId");
|
|
2009
|
+
}
|
|
2010
|
+
get arn() {
|
|
2011
|
+
return ref(this.logicalId);
|
|
2012
|
+
}
|
|
2013
|
+
properties() {
|
|
2014
|
+
return {
|
|
2015
|
+
ApiId: this.props.apiId,
|
|
2016
|
+
Name: this.name,
|
|
2017
|
+
DataSourceName: this.props.dataSourceName,
|
|
2018
|
+
...this.props.code.toCodeJson(),
|
|
2019
|
+
FunctionVersion: "2018-05-29",
|
|
2020
|
+
Runtime: {
|
|
2021
|
+
Name: "APPSYNC_JS",
|
|
2022
|
+
RuntimeVersion: "1.0.0"
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
|
|
2028
|
+
// src/formation/resource/appsync/resolver.ts
|
|
2029
|
+
var Resolver = class extends Resource {
|
|
2030
|
+
constructor(logicalId, props) {
|
|
2031
|
+
super("AWS::AppSync::Resolver", logicalId);
|
|
2032
|
+
this.props = props;
|
|
2033
|
+
}
|
|
2034
|
+
properties() {
|
|
2035
|
+
return {
|
|
2036
|
+
ApiId: this.props.apiId,
|
|
2037
|
+
Kind: "PIPELINE",
|
|
2038
|
+
TypeName: this.props.typeName,
|
|
2039
|
+
FieldName: this.props.fieldName,
|
|
2040
|
+
PipelineConfig: {
|
|
2041
|
+
Functions: this.props.functions
|
|
2042
|
+
},
|
|
2043
|
+
// DataSourceName: this.props.dataSourceName,
|
|
2044
|
+
...this.props.code.toCodeJson(),
|
|
2045
|
+
Runtime: {
|
|
2046
|
+
Name: "APPSYNC_JS",
|
|
2047
|
+
RuntimeVersion: "1.0.0"
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
|
|
2053
|
+
// src/formation/resource/lambda/event-source/appsync.ts
|
|
2054
|
+
var AppsyncEventSource = class extends Group {
|
|
2055
|
+
constructor(id, lambda, props) {
|
|
2056
|
+
const role = new Role(id + "AppSync", {
|
|
2057
|
+
assumedBy: "appsync.amazonaws.com"
|
|
2058
|
+
}).dependsOn(lambda);
|
|
2059
|
+
role.addInlinePolicy(new InlinePolicy(id, {
|
|
2060
|
+
statements: [{
|
|
2061
|
+
actions: ["lambda:InvokeFunction"],
|
|
2062
|
+
resources: [lambda.arn]
|
|
2063
|
+
}]
|
|
2064
|
+
}));
|
|
2065
|
+
const source = DataSource.fromLambda(id, props.apiId, {
|
|
2066
|
+
functionArn: lambda.arn,
|
|
2067
|
+
serviceRoleArn: role.arn
|
|
2068
|
+
}).dependsOn(role).dependsOn(lambda);
|
|
2069
|
+
const config = new FunctionConfiguration(id, {
|
|
2070
|
+
apiId: props.apiId,
|
|
2071
|
+
code: props.code,
|
|
2072
|
+
dataSourceName: source.name
|
|
2073
|
+
}).dependsOn(source);
|
|
2074
|
+
const resolver = new Resolver(id, {
|
|
2075
|
+
apiId: props.apiId,
|
|
2076
|
+
typeName: props.typeName,
|
|
2077
|
+
fieldName: props.fieldName,
|
|
2078
|
+
functions: [config.id],
|
|
2079
|
+
code: props.code
|
|
2080
|
+
}).dependsOn(config);
|
|
2081
|
+
super([role, source, config, resolver]);
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
// src/plugins/graphql.ts
|
|
2086
|
+
var defaultResolver = `
|
|
2087
|
+
export function request(ctx) {
|
|
2088
|
+
return {
|
|
2089
|
+
operation: 'Invoke',
|
|
2090
|
+
payload: ctx,
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
export function response(ctx) {
|
|
2095
|
+
return ctx.result
|
|
2096
|
+
}
|
|
2097
|
+
`;
|
|
2098
|
+
var ResolverFieldSchema = z14.custom((value) => {
|
|
2099
|
+
return z14.string().regex(/([a-z0-9\_]+)(\s){1}([a-z0-9\_]+)/gi).safeParse(value).success;
|
|
2100
|
+
}, `Invalid resolver field. Valid example: "Query list"`);
|
|
2101
|
+
var graphqlPlugin = definePlugin({
|
|
2102
|
+
name: "graphql",
|
|
2103
|
+
schema: z14.object({
|
|
2104
|
+
defaults: z14.object({
|
|
2105
|
+
graphql: z14.record(ResourceIdSchema, z14.object({
|
|
2106
|
+
domain: z14.string().optional(),
|
|
2107
|
+
subDomain: z14.string().optional(),
|
|
2108
|
+
authorization: z14.object({
|
|
2109
|
+
authorizer: FunctionSchema,
|
|
2110
|
+
ttl: DurationSchema.default("1 hour")
|
|
2111
|
+
}).optional(),
|
|
2112
|
+
resolver: LocalFileSchema.optional()
|
|
2113
|
+
})).optional()
|
|
1120
2114
|
}).default({}),
|
|
1121
|
-
stacks:
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
2115
|
+
stacks: z14.object({
|
|
2116
|
+
graphql: z14.record(ResourceIdSchema, z14.object({
|
|
2117
|
+
schema: z14.union([
|
|
2118
|
+
LocalFileSchema,
|
|
2119
|
+
z14.array(LocalFileSchema).min(1)
|
|
2120
|
+
]).optional(),
|
|
2121
|
+
resolvers: z14.record(ResolverFieldSchema, FunctionSchema).optional()
|
|
2122
|
+
})).optional()
|
|
1126
2123
|
}).array()
|
|
1127
2124
|
}),
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
2125
|
+
onApp(ctx) {
|
|
2126
|
+
const { config, bootstrap: bootstrap2, usEastBootstrap } = ctx;
|
|
2127
|
+
const apis = /* @__PURE__ */ new Set();
|
|
2128
|
+
for (const stackConfig of config.stacks) {
|
|
2129
|
+
for (const id of Object.keys(stackConfig.graphql || {})) {
|
|
2130
|
+
apis.add(id);
|
|
2131
|
+
}
|
|
1131
2132
|
}
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
config.
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
]
|
|
1143
|
-
});
|
|
1144
|
-
const securityGroup = new SecurityGroup(stack, toId("security-group", "http"), {
|
|
1145
|
-
vpc
|
|
1146
|
-
});
|
|
1147
|
-
securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(443));
|
|
1148
|
-
securityGroup.addIngressRule(Peer.anyIpv6(), Port.tcp(443));
|
|
1149
|
-
new CfnOutput3(stack, toId("output", "http-vpc"), {
|
|
1150
|
-
exportName: "http-vpc-id",
|
|
1151
|
-
value: vpc.vpcId
|
|
1152
|
-
});
|
|
1153
|
-
new CfnOutput3(stack, toId("output", "http-security-group"), {
|
|
1154
|
-
exportName: "http-security-group-id",
|
|
1155
|
-
value: securityGroup.securityGroupId
|
|
1156
|
-
});
|
|
1157
|
-
Object.entries(config.defaults?.http || {}).forEach(([id, props]) => {
|
|
1158
|
-
const loadBalancer = new ApplicationLoadBalancer(stack, toId("load-balancer", id), {
|
|
1159
|
-
vpc,
|
|
1160
|
-
securityGroup
|
|
1161
|
-
});
|
|
1162
|
-
const zone = HostedZone.fromHostedZoneAttributes(
|
|
1163
|
-
stack,
|
|
1164
|
-
toId("hosted-zone", id),
|
|
1165
|
-
{
|
|
1166
|
-
hostedZoneId: Token.asString(Fn2.ref(toId("hosted-zone", props.domain))),
|
|
1167
|
-
zoneName: props.domain + "."
|
|
1168
|
-
}
|
|
1169
|
-
);
|
|
1170
|
-
const certificate = Certificate.fromCertificateArn(
|
|
1171
|
-
stack,
|
|
1172
|
-
toId("certificate", id),
|
|
1173
|
-
Token.asString(Fn2.ref(toId("certificate", props.domain)))
|
|
1174
|
-
);
|
|
1175
|
-
const target = RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer));
|
|
1176
|
-
const recordName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
|
|
1177
|
-
new RecordSet(stack, toId("record-set", id), {
|
|
1178
|
-
zone,
|
|
1179
|
-
target,
|
|
1180
|
-
recordName,
|
|
1181
|
-
recordType: RecordType.A
|
|
1182
|
-
});
|
|
1183
|
-
const listener = loadBalancer.addListener(toId("listener", id), {
|
|
1184
|
-
port: 443,
|
|
1185
|
-
protocol: ApplicationProtocol.HTTPS,
|
|
1186
|
-
certificates: [certificate],
|
|
1187
|
-
defaultAction: ListenerAction.fixedResponse(404, {
|
|
1188
|
-
contentType: "application/json",
|
|
1189
|
-
messageBody: JSON.stringify({
|
|
1190
|
-
message: "Route not found"
|
|
1191
|
-
})
|
|
1192
|
-
})
|
|
1193
|
-
});
|
|
1194
|
-
new CfnOutput3(stack, toId("output", `http-${id}-listener`), {
|
|
1195
|
-
exportName: `http-${id}-listener-arn`,
|
|
1196
|
-
value: listener.listenerArn
|
|
2133
|
+
for (const id of apis) {
|
|
2134
|
+
const schema2 = [];
|
|
2135
|
+
for (const stack of config.stacks) {
|
|
2136
|
+
const files = toArray(stack.graphql?.[id]?.schema || []);
|
|
2137
|
+
schema2.push(...files);
|
|
2138
|
+
}
|
|
2139
|
+
const graphql = new GraphQL(id, {
|
|
2140
|
+
name: `${config.name}-${id}`,
|
|
2141
|
+
authenticationType: "api-key",
|
|
2142
|
+
schema: new Schema(id, schema2)
|
|
1197
2143
|
});
|
|
1198
|
-
|
|
2144
|
+
bootstrap2.add(graphql).export(`graphql-${id}`, graphql.api.id);
|
|
2145
|
+
const props = config.defaults.graphql?.[id];
|
|
2146
|
+
if (!props) {
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
if (props.authorization) {
|
|
2150
|
+
const lambda = toLambdaFunction(ctx, `${id}-authorizer`, props.authorization.authorizer);
|
|
2151
|
+
graphql.api.addLambdaAuthProvider(lambda.arn, props.authorization.ttl);
|
|
2152
|
+
bootstrap2.add(lambda);
|
|
2153
|
+
}
|
|
2154
|
+
if (props.domain) {
|
|
2155
|
+
const domainName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
|
|
2156
|
+
const hostedZoneId = ref(`${props.domain}Route53HostedZone`);
|
|
2157
|
+
const certificateArn = usEastBootstrap.import(`certificate-${props.domain}-arn`);
|
|
2158
|
+
graphql.attachDomainName(domainName, certificateArn);
|
|
2159
|
+
const record = new RecordSet(id, {
|
|
2160
|
+
hostedZoneId,
|
|
2161
|
+
type: "A",
|
|
2162
|
+
name: domainName,
|
|
2163
|
+
alias: graphql.api.dns
|
|
2164
|
+
});
|
|
2165
|
+
bootstrap2.add(record);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
1199
2168
|
},
|
|
1200
2169
|
onStack(ctx) {
|
|
1201
|
-
const { stack, stackConfig } = ctx;
|
|
1202
|
-
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const [method, ...paths] = route.split(" ");
|
|
1214
|
-
const path = paths.join(" ");
|
|
1215
|
-
new ApplicationListenerRule(stack, toId("listener-rule", route), {
|
|
1216
|
-
listener,
|
|
1217
|
-
priority: generatePriority(stackConfig.name, route),
|
|
1218
|
-
action: ListenerAction.forward([
|
|
1219
|
-
new ApplicationTargetGroup(stack, toId("target-group", route), {
|
|
1220
|
-
targets: [new LambdaTarget(lambda)]
|
|
1221
|
-
})
|
|
1222
|
-
]),
|
|
1223
|
-
conditions: [
|
|
1224
|
-
ListenerCondition.httpRequestMethods([method]),
|
|
1225
|
-
ListenerCondition.pathPatterns([path])
|
|
1226
|
-
]
|
|
2170
|
+
const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
|
|
2171
|
+
for (const [id, props] of Object.entries(stackConfig.graphql || {})) {
|
|
2172
|
+
const apiId = bootstrap2.import(`graphql-${id}`);
|
|
2173
|
+
for (const [typeAndField, functionProps] of Object.entries(props.resolvers || {})) {
|
|
2174
|
+
const [typeName, fieldName] = typeAndField.split(/[\s]+/g);
|
|
2175
|
+
const entryId = paramCase3(`${id}-${typeName}-${fieldName}`);
|
|
2176
|
+
const lambda = toLambdaFunction(ctx, entryId, functionProps);
|
|
2177
|
+
const source = new AppsyncEventSource(entryId, lambda, {
|
|
2178
|
+
apiId,
|
|
2179
|
+
typeName,
|
|
2180
|
+
fieldName,
|
|
2181
|
+
code: Code2.fromInline(entryId, defaultResolver)
|
|
1227
2182
|
});
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
2183
|
+
stack.add(lambda, source);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
1231
2186
|
}
|
|
1232
2187
|
});
|
|
1233
2188
|
|
|
1234
|
-
// src/plugins/domain
|
|
1235
|
-
import { z as
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
// src/
|
|
1256
|
-
|
|
2189
|
+
// src/plugins/domain.ts
|
|
2190
|
+
import { z as z15 } from "zod";
|
|
2191
|
+
|
|
2192
|
+
// src/formation/resource/route53/hosted-zone.ts
|
|
2193
|
+
var HostedZone = class extends Resource {
|
|
2194
|
+
constructor(logicalId, props = {}) {
|
|
2195
|
+
super("AWS::Route53::HostedZone", logicalId);
|
|
2196
|
+
this.props = props;
|
|
2197
|
+
this.name = this.props.domainName || logicalId;
|
|
2198
|
+
}
|
|
2199
|
+
name;
|
|
2200
|
+
get id() {
|
|
2201
|
+
return ref(this.logicalId);
|
|
2202
|
+
}
|
|
2203
|
+
properties() {
|
|
2204
|
+
return {
|
|
2205
|
+
Name: this.name + "."
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
|
|
2210
|
+
// src/formation/resource/certificate-manager/certificate.ts
|
|
2211
|
+
var Certificate = class extends Resource {
|
|
2212
|
+
constructor(logicalId, props = {}) {
|
|
2213
|
+
super("AWS::CertificateManager::Certificate", logicalId);
|
|
2214
|
+
this.props = props;
|
|
2215
|
+
this.name = this.props.domainName || logicalId;
|
|
2216
|
+
}
|
|
2217
|
+
name;
|
|
2218
|
+
get arn() {
|
|
2219
|
+
return ref(this.logicalId);
|
|
2220
|
+
}
|
|
2221
|
+
properties() {
|
|
2222
|
+
return {
|
|
2223
|
+
DomainName: this.name,
|
|
2224
|
+
ValidationMethod: "DNS",
|
|
2225
|
+
SubjectAlternativeNames: this.props.alternativeNames || []
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
|
|
2230
|
+
// src/formation/resource/route53/record-set-group.ts
|
|
2231
|
+
var RecordSetGroup = class extends Resource {
|
|
2232
|
+
constructor(logicalId, props) {
|
|
2233
|
+
super("AWS::Route53::RecordSetGroup", logicalId);
|
|
2234
|
+
this.props = props;
|
|
2235
|
+
}
|
|
2236
|
+
properties() {
|
|
2237
|
+
return {
|
|
2238
|
+
HostedZoneId: this.props.hostedZoneId,
|
|
2239
|
+
RecordSets: this.props.records.map((props) => ({
|
|
2240
|
+
Name: props.name + ".",
|
|
2241
|
+
Type: props.type,
|
|
2242
|
+
TTL: props.ttl,
|
|
2243
|
+
...props.records ? {
|
|
2244
|
+
ResourceRecords: props.records
|
|
2245
|
+
} : {},
|
|
2246
|
+
...props.alias ? {
|
|
2247
|
+
AliasTarget: {
|
|
2248
|
+
DNSName: props.alias,
|
|
2249
|
+
HostedZoneId: this.props.hostedZoneId
|
|
2250
|
+
}
|
|
2251
|
+
} : {}
|
|
2252
|
+
}))
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
|
|
2257
|
+
// src/plugins/domain.ts
|
|
2258
|
+
var DomainNameSchema = z15.string().regex(/[a-z\-\_\.]/g, "Invalid domain name");
|
|
1257
2259
|
var domainPlugin = definePlugin({
|
|
1258
2260
|
name: "domain",
|
|
1259
|
-
schema:
|
|
1260
|
-
domains:
|
|
2261
|
+
schema: z15.object({
|
|
2262
|
+
domains: z15.record(DomainNameSchema, z15.object({
|
|
1261
2263
|
name: DomainNameSchema.optional(),
|
|
1262
|
-
type:
|
|
2264
|
+
type: z15.enum(["A", "AAAA", "CAA", "CNAME", "DS", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"]),
|
|
1263
2265
|
ttl: DurationSchema,
|
|
1264
|
-
records:
|
|
2266
|
+
records: z15.string().array()
|
|
1265
2267
|
}).array()).optional()
|
|
1266
2268
|
}),
|
|
1267
|
-
|
|
1268
|
-
Object.entries(config.domains || {})
|
|
1269
|
-
const hostedZone = new
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
});
|
|
1273
|
-
hostedZone.node.defaultChild.overrideLogicalId(toId("hosted-zone", domain));
|
|
1274
|
-
const certificate = new Certificate2(stack, toId("certificate", domain), {
|
|
1275
|
-
domainName: domain,
|
|
1276
|
-
validation: CertificateValidation.fromDns(hostedZone),
|
|
1277
|
-
subjectAlternativeNames: [`*.${domain}`]
|
|
1278
|
-
});
|
|
1279
|
-
certificate.node.defaultChild.overrideLogicalId(toId("certificate", domain));
|
|
1280
|
-
new CfnOutput4(stack, toId("output-hosted-zone", domain), {
|
|
1281
|
-
exportName: toExportName(`hosted-zone-${domain}-id`),
|
|
1282
|
-
value: hostedZone.hostedZoneId
|
|
2269
|
+
onApp({ config, bootstrap: bootstrap2, usEastBootstrap }) {
|
|
2270
|
+
for (const [domain, records] of Object.entries(config.domains || {})) {
|
|
2271
|
+
const hostedZone = new HostedZone(domain);
|
|
2272
|
+
const certificate = new Certificate(domain, {
|
|
2273
|
+
alternativeNames: [`*.${domain}`]
|
|
1283
2274
|
});
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
name: props.name || "",
|
|
1293
|
-
type: props.type,
|
|
1294
|
-
ttl: props.ttl.toSeconds().toString(),
|
|
1295
|
-
resourceRecords: props.records
|
|
1296
|
-
}))
|
|
1297
|
-
});
|
|
2275
|
+
bootstrap2.add(certificate);
|
|
2276
|
+
usEastBootstrap.add(hostedZone).add(certificate).export(`certificate-${domain}-arn`, certificate.arn);
|
|
2277
|
+
if (records.length > 0) {
|
|
2278
|
+
const group = new RecordSetGroup(domain, {
|
|
2279
|
+
hostedZoneId: hostedZone.id,
|
|
2280
|
+
records
|
|
2281
|
+
}).dependsOn(hostedZone);
|
|
2282
|
+
usEastBootstrap.add(group);
|
|
1298
2283
|
}
|
|
1299
|
-
}
|
|
2284
|
+
}
|
|
1300
2285
|
}
|
|
1301
2286
|
});
|
|
1302
2287
|
|
|
1303
2288
|
// src/plugins/index.ts
|
|
1304
2289
|
var defaultPlugins = [
|
|
2290
|
+
extendPlugin,
|
|
1305
2291
|
functionPlugin,
|
|
1306
2292
|
cronPlugin,
|
|
1307
2293
|
queuePlugin,
|
|
1308
2294
|
tablePlugin,
|
|
1309
2295
|
storePlugin,
|
|
1310
2296
|
topicPlugin,
|
|
1311
|
-
// searchPlugin,
|
|
1312
|
-
graphqlPlugin,
|
|
1313
2297
|
pubsubPlugin,
|
|
2298
|
+
// searchPlugin,
|
|
1314
2299
|
domainPlugin,
|
|
1315
|
-
|
|
2300
|
+
graphqlPlugin
|
|
2301
|
+
// httpPlugin,
|
|
1316
2302
|
];
|
|
1317
2303
|
|
|
1318
|
-
// src/
|
|
1319
|
-
var
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
2304
|
+
// src/formation/app.ts
|
|
2305
|
+
var App = class {
|
|
2306
|
+
constructor(name) {
|
|
2307
|
+
this.name = name;
|
|
2308
|
+
}
|
|
2309
|
+
list = /* @__PURE__ */ new Map();
|
|
2310
|
+
add(...stacks) {
|
|
2311
|
+
stacks.forEach((stack) => this.list.set(stack.name, stack));
|
|
2312
|
+
return this;
|
|
2313
|
+
}
|
|
2314
|
+
find(resourceType) {
|
|
2315
|
+
return this.stacks.map((stack) => stack.find(resourceType)).flat();
|
|
2316
|
+
}
|
|
2317
|
+
[Symbol.iterator]() {
|
|
2318
|
+
return this.list.values();
|
|
2319
|
+
}
|
|
2320
|
+
get stacks() {
|
|
2321
|
+
return [...this.list.values()];
|
|
2322
|
+
}
|
|
2323
|
+
// get resources() {
|
|
2324
|
+
// return this.stacks.map(stack => stack.resources).flat()
|
|
2325
|
+
// }
|
|
1338
2326
|
};
|
|
1339
2327
|
|
|
1340
|
-
// src/
|
|
1341
|
-
var
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
|
|
2328
|
+
// src/formation/resource/cloud-formation/custom-resource.ts
|
|
2329
|
+
var CustomResource = class extends Resource {
|
|
2330
|
+
constructor(logicalId, props) {
|
|
2331
|
+
super("AWS::CloudFormation::CustomResource", logicalId);
|
|
2332
|
+
this.props = props;
|
|
2333
|
+
}
|
|
2334
|
+
getAtt(name) {
|
|
2335
|
+
return getAtt(this.logicalId, name);
|
|
2336
|
+
}
|
|
2337
|
+
properties() {
|
|
2338
|
+
return {
|
|
2339
|
+
ServiceToken: this.props.serviceToken,
|
|
2340
|
+
...this.props.properties
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
1351
2343
|
};
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
2344
|
+
|
|
2345
|
+
// src/custom/global-export/handler.ts
|
|
2346
|
+
var globalExportsHandlerCode = (
|
|
2347
|
+
/* JS */
|
|
2348
|
+
`
|
|
2349
|
+
|
|
2350
|
+
const { CloudFormationClient, ListExportsCommand } = require('@aws-sdk/client-cloudformation')
|
|
2351
|
+
|
|
2352
|
+
exports.handler = async (event) => {
|
|
2353
|
+
const region = event.ResourceProperties.region
|
|
2354
|
+
|
|
2355
|
+
try {
|
|
2356
|
+
const data = await listExports(region)
|
|
2357
|
+
|
|
2358
|
+
await send(event, region, 'SUCCESS', data)
|
|
2359
|
+
} catch(error) {
|
|
2360
|
+
if (error instanceof Error) {
|
|
2361
|
+
await send(event, region, 'FAILED', {}, error.message)
|
|
2362
|
+
} else {
|
|
2363
|
+
await send(event, region, 'FAILED', {}, 'Unknown error')
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
const send = async (event, id, status, data, reason = '') => {
|
|
2369
|
+
const body = JSON.stringify({
|
|
2370
|
+
Status: status,
|
|
2371
|
+
Reason: reason,
|
|
2372
|
+
PhysicalResourceId: id,
|
|
2373
|
+
StackId: event.StackId,
|
|
2374
|
+
RequestId: event.RequestId,
|
|
2375
|
+
LogicalResourceId: event.LogicalResourceId,
|
|
2376
|
+
NoEcho: false,
|
|
2377
|
+
Data: data
|
|
2378
|
+
})
|
|
2379
|
+
|
|
2380
|
+
await fetch(event.ResponseURL, {
|
|
2381
|
+
method: 'PUT',
|
|
2382
|
+
port: 443,
|
|
2383
|
+
body,
|
|
2384
|
+
headers: {
|
|
2385
|
+
'content-type': '',
|
|
2386
|
+
'content-length': Buffer.from(body).byteLength,
|
|
2387
|
+
},
|
|
2388
|
+
})
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
const listExports = async (region) => {
|
|
2392
|
+
const client = new CloudFormationClient({ region })
|
|
2393
|
+
const data = {}
|
|
2394
|
+
|
|
2395
|
+
let token
|
|
2396
|
+
|
|
2397
|
+
while(true) {
|
|
2398
|
+
const result = await client.send(new ListExportsCommand({
|
|
2399
|
+
NextToken: token
|
|
2400
|
+
}))
|
|
2401
|
+
|
|
2402
|
+
result.Exports?.forEach(item => {
|
|
2403
|
+
data[item.Name] = item.Value
|
|
2404
|
+
})
|
|
2405
|
+
|
|
2406
|
+
if(result.NextToken) {
|
|
2407
|
+
token = result.NextToken
|
|
2408
|
+
} else {
|
|
2409
|
+
return data
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
`
|
|
2414
|
+
);
|
|
2415
|
+
|
|
2416
|
+
// src/custom/global-export/extend.ts
|
|
2417
|
+
var extendWithGlobalExports = (appName, importable, exportable) => {
|
|
2418
|
+
let crossRegionExports;
|
|
2419
|
+
importable.import = (name) => {
|
|
2420
|
+
name = formatName(name);
|
|
2421
|
+
if (!importable.exports.has(name)) {
|
|
2422
|
+
throw new TypeError(`Undefined global export value: ${name}`);
|
|
1367
2423
|
}
|
|
1368
|
-
if (!
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
2424
|
+
if (!crossRegionExports) {
|
|
2425
|
+
const lambda = new Function("global-exports", {
|
|
2426
|
+
name: `${appName}-global-exports`,
|
|
2427
|
+
code: Code.fromInline(globalExportsHandlerCode, "index.handler")
|
|
2428
|
+
});
|
|
2429
|
+
lambda.addPermissions({
|
|
2430
|
+
actions: ["cloudformation:ListExports"],
|
|
2431
|
+
resources: ["*"]
|
|
2432
|
+
});
|
|
2433
|
+
crossRegionExports = new CustomResource("global-exports", {
|
|
2434
|
+
serviceToken: lambda.arn,
|
|
2435
|
+
properties: {
|
|
2436
|
+
region: importable.region
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
exportable.add(crossRegionExports);
|
|
1374
2440
|
}
|
|
1375
|
-
return
|
|
1376
|
-
return {
|
|
1377
|
-
stack,
|
|
1378
|
-
level,
|
|
1379
|
-
children: findChildren(rests, [...parents, stack.artifactId], level + 1)
|
|
1380
|
-
};
|
|
1381
|
-
});
|
|
2441
|
+
return crossRegionExports.getAtt(name);
|
|
1382
2442
|
};
|
|
1383
|
-
return findChildren(list3, [], startingLevel);
|
|
1384
|
-
};
|
|
1385
|
-
var createDeploymentLine = (stacks) => {
|
|
1386
|
-
const flat = flattenDependencyTree(stacks);
|
|
1387
|
-
const line = [];
|
|
1388
|
-
flat.forEach((node) => {
|
|
1389
|
-
const level = node.level;
|
|
1390
|
-
if (!line[level]) {
|
|
1391
|
-
line[level] = [];
|
|
1392
|
-
}
|
|
1393
|
-
line[level].push(node.stack);
|
|
1394
|
-
});
|
|
1395
|
-
return line;
|
|
1396
|
-
};
|
|
1397
|
-
|
|
1398
|
-
// src/util/assets.ts
|
|
1399
|
-
var Assets = class {
|
|
1400
|
-
assets = {};
|
|
1401
|
-
id = 0;
|
|
1402
|
-
add(opts) {
|
|
1403
|
-
if (!this.assets[opts.stackName]) {
|
|
1404
|
-
this.assets[opts.stackName] = [];
|
|
1405
|
-
}
|
|
1406
|
-
this.assets[opts.stackName].push({
|
|
1407
|
-
...opts,
|
|
1408
|
-
id: this.id++
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
list() {
|
|
1412
|
-
return this.assets;
|
|
1413
|
-
}
|
|
1414
|
-
forEach(cb) {
|
|
1415
|
-
Object.values(this.assets).forEach((assets) => {
|
|
1416
|
-
cb(assets[0].stackName, assets);
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
map(cb) {
|
|
1420
|
-
return Object.values(this.assets).map((assets) => {
|
|
1421
|
-
return cb(assets[0].stackName, assets);
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
2443
|
};
|
|
1425
2444
|
|
|
1426
2445
|
// src/app.ts
|
|
1427
|
-
var makeApp = (config) => {
|
|
1428
|
-
return new App4({
|
|
1429
|
-
outdir: assemblyDir,
|
|
1430
|
-
defaultStackSynthesizer: new DefaultStackSynthesizer({
|
|
1431
|
-
fileAssetsBucketName: assetBucketName(config),
|
|
1432
|
-
fileAssetPublishingRoleArn: "",
|
|
1433
|
-
generateBootstrapVersionRule: false
|
|
1434
|
-
})
|
|
1435
|
-
});
|
|
1436
|
-
};
|
|
1437
2446
|
var getAllDepends = (filters) => {
|
|
1438
2447
|
const list3 = [];
|
|
1439
2448
|
const walk = (deps) => {
|
|
@@ -1446,54 +2455,76 @@ var getAllDepends = (filters) => {
|
|
|
1446
2455
|
return list3;
|
|
1447
2456
|
};
|
|
1448
2457
|
var toApp = async (config, filters) => {
|
|
1449
|
-
const
|
|
1450
|
-
const app = makeApp(config);
|
|
2458
|
+
const app = new App(config.name);
|
|
1451
2459
|
const stacks = [];
|
|
1452
2460
|
const plugins = [
|
|
1453
2461
|
...defaultPlugins,
|
|
1454
2462
|
...config.plugins || []
|
|
1455
2463
|
];
|
|
1456
2464
|
debug("Plugins detected:", plugins.map((plugin) => style.info(plugin.name)).join(", "));
|
|
2465
|
+
const bootstrap2 = new Stack("bootstrap", config.region);
|
|
2466
|
+
const usEastBootstrap = new Stack("us-east-bootstrap", "us-east-1");
|
|
2467
|
+
extendWithGlobalExports(config.name, usEastBootstrap, bootstrap2);
|
|
2468
|
+
app.add(bootstrap2, usEastBootstrap);
|
|
1457
2469
|
debug("Run plugin onApp listeners");
|
|
1458
|
-
|
|
1459
|
-
const
|
|
2470
|
+
const bindings = [];
|
|
2471
|
+
const bind = (cb) => {
|
|
2472
|
+
bindings.push(cb);
|
|
2473
|
+
};
|
|
2474
|
+
for (const plugin of plugins) {
|
|
2475
|
+
plugin.onApp?.({
|
|
2476
|
+
config,
|
|
2477
|
+
app,
|
|
2478
|
+
bootstrap: bootstrap2,
|
|
2479
|
+
usEastBootstrap,
|
|
2480
|
+
bind
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
1460
2483
|
debug("Stack filters:", filters.map((filter) => style.info(filter)).join(", "));
|
|
1461
2484
|
const filterdStacks = filters.length === 0 ? config.stacks : getAllDepends(
|
|
1462
2485
|
// config.stacks,
|
|
1463
2486
|
config.stacks.filter((stack) => filters.includes(stack.name))
|
|
1464
2487
|
);
|
|
1465
2488
|
for (const stackConfig of filterdStacks) {
|
|
1466
|
-
const { stack
|
|
2489
|
+
const { stack } = toStack({
|
|
1467
2490
|
config,
|
|
1468
2491
|
stackConfig,
|
|
1469
|
-
|
|
2492
|
+
bootstrap: bootstrap2,
|
|
2493
|
+
usEastBootstrap,
|
|
1470
2494
|
plugins,
|
|
1471
2495
|
app
|
|
1472
2496
|
});
|
|
2497
|
+
app.add(stack);
|
|
1473
2498
|
stacks.push({ stack, config: stackConfig });
|
|
1474
|
-
bindings.forEach((cb) => bootstrap2.functions.forEach(cb));
|
|
1475
2499
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
2500
|
+
const functions = app.find(Function);
|
|
2501
|
+
for (const bind2 of bindings) {
|
|
2502
|
+
for (const fn of functions) {
|
|
2503
|
+
bind2(fn);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
let dependencyTree = createDependencyTree(stacks);
|
|
2507
|
+
if (bootstrap2.size > 0) {
|
|
2508
|
+
dependencyTree = [{
|
|
2509
|
+
stack: bootstrap2,
|
|
2510
|
+
children: dependencyTree
|
|
2511
|
+
}];
|
|
2512
|
+
}
|
|
2513
|
+
if (usEastBootstrap.size > 0) {
|
|
1480
2514
|
dependencyTree = [{
|
|
1481
|
-
stack:
|
|
1482
|
-
|
|
1483
|
-
children: createDependencyTree(stacks, 1)
|
|
2515
|
+
stack: usEastBootstrap,
|
|
2516
|
+
children: dependencyTree
|
|
1484
2517
|
}];
|
|
1485
2518
|
}
|
|
1486
2519
|
return {
|
|
1487
2520
|
app,
|
|
1488
|
-
assets,
|
|
1489
2521
|
plugins,
|
|
1490
|
-
stackNames: filterdStacks.map((stack) => stack.name),
|
|
1491
2522
|
dependencyTree
|
|
1492
2523
|
};
|
|
1493
2524
|
};
|
|
1494
2525
|
|
|
1495
2526
|
// src/config.ts
|
|
1496
|
-
import { join as
|
|
2527
|
+
import { join as join3 } from "path";
|
|
1497
2528
|
|
|
1498
2529
|
// src/util/account.ts
|
|
1499
2530
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -1512,17 +2543,17 @@ var getCredentials = (profile) => {
|
|
|
1512
2543
|
};
|
|
1513
2544
|
|
|
1514
2545
|
// src/schema/app.ts
|
|
1515
|
-
import { z as
|
|
2546
|
+
import { z as z19 } from "zod";
|
|
1516
2547
|
|
|
1517
2548
|
// src/schema/stack.ts
|
|
1518
|
-
import { z as
|
|
1519
|
-
var StackSchema =
|
|
2549
|
+
import { z as z16 } from "zod";
|
|
2550
|
+
var StackSchema = z16.object({
|
|
1520
2551
|
name: ResourceIdSchema,
|
|
1521
|
-
depends:
|
|
2552
|
+
depends: z16.array(z16.lazy(() => StackSchema)).optional()
|
|
1522
2553
|
});
|
|
1523
2554
|
|
|
1524
2555
|
// src/schema/region.ts
|
|
1525
|
-
import { z as
|
|
2556
|
+
import { z as z17 } from "zod";
|
|
1526
2557
|
var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
|
|
1527
2558
|
var AF = ["af-south-1"];
|
|
1528
2559
|
var AP = ["ap-east-1", "ap-south-2", "ap-southeast-3", "ap-southeast-4", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1"];
|
|
@@ -1539,35 +2570,48 @@ var regions = [
|
|
|
1539
2570
|
...ME,
|
|
1540
2571
|
...SA
|
|
1541
2572
|
];
|
|
1542
|
-
var RegionSchema =
|
|
2573
|
+
var RegionSchema = z17.enum(regions);
|
|
1543
2574
|
|
|
1544
2575
|
// src/schema/plugin.ts
|
|
1545
|
-
import { z as
|
|
1546
|
-
var PluginSchema =
|
|
1547
|
-
name:
|
|
1548
|
-
schema:
|
|
2576
|
+
import { z as z18 } from "zod";
|
|
2577
|
+
var PluginSchema = z18.object({
|
|
2578
|
+
name: z18.string(),
|
|
2579
|
+
schema: z18.custom().optional(),
|
|
1549
2580
|
// depends: z.array(z.lazy(() => PluginSchema)).optional(),
|
|
1550
|
-
onBootstrap:
|
|
1551
|
-
onStack:
|
|
1552
|
-
onApp:
|
|
2581
|
+
onBootstrap: z18.function().returns(z18.any()).optional(),
|
|
2582
|
+
onStack: z18.function().returns(z18.any()).optional(),
|
|
2583
|
+
onApp: z18.function().returns(z18.void()).optional()
|
|
1553
2584
|
// bind: z.function().optional(),
|
|
1554
2585
|
});
|
|
1555
2586
|
|
|
1556
2587
|
// src/schema/app.ts
|
|
1557
|
-
var AppSchema =
|
|
2588
|
+
var AppSchema = z19.object({
|
|
1558
2589
|
name: ResourceIdSchema,
|
|
1559
2590
|
region: RegionSchema,
|
|
1560
|
-
profile:
|
|
1561
|
-
stage:
|
|
1562
|
-
defaults:
|
|
1563
|
-
stacks:
|
|
1564
|
-
|
|
2591
|
+
profile: z19.string(),
|
|
2592
|
+
stage: z19.string().regex(/[a-z]+/).default("prod"),
|
|
2593
|
+
defaults: z19.object({}).default({}),
|
|
2594
|
+
stacks: z19.array(StackSchema).min(1).refine((stacks) => {
|
|
2595
|
+
const unique = new Set(stacks.map((stack) => stack.name));
|
|
2596
|
+
return unique.size === stacks.length;
|
|
2597
|
+
}, "Must be an array of unique stacks"),
|
|
2598
|
+
plugins: z19.array(PluginSchema).optional()
|
|
1565
2599
|
});
|
|
1566
2600
|
|
|
1567
2601
|
// src/util/import.ts
|
|
1568
2602
|
import { transformFile } from "@swc/core";
|
|
1569
|
-
import { dirname
|
|
1570
|
-
import { lstat, mkdir
|
|
2603
|
+
import { dirname, join as join2 } from "path";
|
|
2604
|
+
import { lstat, mkdir, writeFile } from "fs/promises";
|
|
2605
|
+
|
|
2606
|
+
// src/util/path.ts
|
|
2607
|
+
import { join } from "path";
|
|
2608
|
+
var rootDir = process.cwd();
|
|
2609
|
+
var outDir = join(rootDir, ".awsless");
|
|
2610
|
+
var templateDir = join(outDir, "template");
|
|
2611
|
+
var assetDir = join(outDir, "asset");
|
|
2612
|
+
var cacheDir = join(outDir, "cache");
|
|
2613
|
+
|
|
2614
|
+
// src/util/import.ts
|
|
1571
2615
|
var resolveFileNameExtension = async (path) => {
|
|
1572
2616
|
const options = [
|
|
1573
2617
|
"",
|
|
@@ -1591,7 +2635,7 @@ var resolveFileNameExtension = async (path) => {
|
|
|
1591
2635
|
throw new Error(`Failed to load file: ${path}`);
|
|
1592
2636
|
};
|
|
1593
2637
|
var resolveDir = (path) => {
|
|
1594
|
-
return
|
|
2638
|
+
return dirname(path).replace(rootDir + "/", "");
|
|
1595
2639
|
};
|
|
1596
2640
|
var importFile = async (path) => {
|
|
1597
2641
|
const load = async (file) => {
|
|
@@ -1599,7 +2643,7 @@ var importFile = async (path) => {
|
|
|
1599
2643
|
let { code: code2 } = await transformFile(file, {
|
|
1600
2644
|
isModule: true
|
|
1601
2645
|
});
|
|
1602
|
-
const path2 =
|
|
2646
|
+
const path2 = dirname(file);
|
|
1603
2647
|
const dir = resolveDir(file);
|
|
1604
2648
|
code2 = code2.replaceAll("__dirname", `"${dir}"`);
|
|
1605
2649
|
const matches = code2.match(/(import|export)\s*{\s*[a-z0-9\_\,\s\*]+\s*}\s*from\s*('|")(\.\.?[\/a-z0-9\_\-\.]+)('|");?/ig);
|
|
@@ -1608,23 +2652,23 @@ var importFile = async (path) => {
|
|
|
1608
2652
|
await Promise.all(matches?.map(async (match) => {
|
|
1609
2653
|
const parts = /('|")(\.\.?[\/a-z0-9\_\-\.]+)('|")/ig.exec(match);
|
|
1610
2654
|
const from = parts[2];
|
|
1611
|
-
const file2 = await resolveFileNameExtension(
|
|
2655
|
+
const file2 = await resolveFileNameExtension(join2(path2, from));
|
|
1612
2656
|
const result = await load(file2);
|
|
1613
2657
|
code2 = code2.replace(match, result);
|
|
1614
2658
|
}));
|
|
1615
2659
|
return code2;
|
|
1616
2660
|
};
|
|
1617
2661
|
const code = await load(path);
|
|
1618
|
-
const outputFile =
|
|
1619
|
-
await
|
|
1620
|
-
await
|
|
2662
|
+
const outputFile = join2(outDir, "config.js");
|
|
2663
|
+
await mkdir(outDir, { recursive: true });
|
|
2664
|
+
await writeFile(outputFile, code);
|
|
1621
2665
|
return import(outputFile);
|
|
1622
2666
|
};
|
|
1623
2667
|
|
|
1624
2668
|
// src/config.ts
|
|
1625
2669
|
var importConfig = async (options) => {
|
|
1626
2670
|
debug("Import config file");
|
|
1627
|
-
const fileName =
|
|
2671
|
+
const fileName = join3(process.cwd(), options.configFile || "awsless.config.ts");
|
|
1628
2672
|
const module = await importFile(fileName);
|
|
1629
2673
|
const appConfig = typeof module.default === "function" ? await module.default(options) : module.default;
|
|
1630
2674
|
debug("Validate config file");
|
|
@@ -1715,7 +2759,7 @@ var Signal = class {
|
|
|
1715
2759
|
}
|
|
1716
2760
|
set(value) {
|
|
1717
2761
|
this.value = value;
|
|
1718
|
-
this.subs.forEach((
|
|
2762
|
+
this.subs.forEach((sub2) => sub2(value));
|
|
1719
2763
|
}
|
|
1720
2764
|
update(cb) {
|
|
1721
2765
|
this.set(cb(this.value));
|
|
@@ -1956,6 +3000,9 @@ var Renderer = class {
|
|
|
1956
3000
|
async end() {
|
|
1957
3001
|
this.gap();
|
|
1958
3002
|
await this.flush();
|
|
3003
|
+
clearTimeout(this.timeout);
|
|
3004
|
+
this.unsubs.forEach((unsub) => unsub());
|
|
3005
|
+
this.unsubs = [];
|
|
1959
3006
|
const y = this.screen.length - 1;
|
|
1960
3007
|
await this.setCursor(0, y);
|
|
1961
3008
|
}
|
|
@@ -2120,6 +3167,9 @@ var layout = async (cb) => {
|
|
|
2120
3167
|
}
|
|
2121
3168
|
};
|
|
2122
3169
|
|
|
3170
|
+
// src/cli/ui/complex/builder.ts
|
|
3171
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
3172
|
+
|
|
2123
3173
|
// src/cli/ui/layout/flex-line.ts
|
|
2124
3174
|
var stripEscapeCode = (str) => {
|
|
2125
3175
|
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
@@ -2140,35 +3190,52 @@ var flexLine = (term, left, right, reserveSpace = 0) => {
|
|
|
2140
3190
|
]);
|
|
2141
3191
|
};
|
|
2142
3192
|
|
|
2143
|
-
// src/cli/ui/complex/
|
|
2144
|
-
|
|
3193
|
+
// src/cli/ui/complex/builder.ts
|
|
3194
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
3195
|
+
var assetBuilder = (app) => {
|
|
2145
3196
|
return async (term) => {
|
|
3197
|
+
const assets = [];
|
|
3198
|
+
const stacks = [];
|
|
3199
|
+
for (const stack of app) {
|
|
3200
|
+
for (const asset of stack.assets) {
|
|
3201
|
+
if (asset.build) {
|
|
3202
|
+
assets.push(asset);
|
|
3203
|
+
stacks.push(stack);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
if (assets.length === 0) {
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
2146
3210
|
const done = term.out.write(loadingDialog("Building stack assets..."));
|
|
2147
3211
|
const groups = new Signal([""]);
|
|
2148
3212
|
term.out.gap();
|
|
2149
3213
|
term.out.write(groups);
|
|
2150
|
-
const stackNameSize = Math.max(...
|
|
2151
|
-
const
|
|
2152
|
-
await Promise.all(
|
|
3214
|
+
const stackNameSize = Math.max(...stacks.map((stack) => stack.name.length));
|
|
3215
|
+
const assetTypeSize = Math.max(...assets.map((asset) => asset.type.length));
|
|
3216
|
+
await Promise.all(app.stacks.map(async (stack) => {
|
|
2153
3217
|
const group = new Signal([]);
|
|
2154
3218
|
groups.update((groups2) => [...groups2, group]);
|
|
2155
|
-
await Promise.all(
|
|
3219
|
+
await Promise.all([...stack.assets].map(async (asset) => {
|
|
3220
|
+
if (!asset.build) {
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
2156
3223
|
const [icon, stop] = createSpinner();
|
|
2157
3224
|
const details = new Signal({});
|
|
2158
3225
|
const line = flexLine(term, [
|
|
2159
3226
|
icon,
|
|
2160
3227
|
" ",
|
|
2161
|
-
style.label(
|
|
2162
|
-
" ".repeat(stackNameSize -
|
|
3228
|
+
style.label(stack.name),
|
|
3229
|
+
" ".repeat(stackNameSize - stack.name.length),
|
|
2163
3230
|
" ",
|
|
2164
3231
|
style.placeholder(symbol.pointerSmall),
|
|
2165
3232
|
" ",
|
|
2166
|
-
style.warning(asset.
|
|
2167
|
-
" ".repeat(
|
|
3233
|
+
style.warning(asset.type),
|
|
3234
|
+
" ".repeat(assetTypeSize - asset.type.length),
|
|
2168
3235
|
" ",
|
|
2169
3236
|
style.placeholder(symbol.pointerSmall),
|
|
2170
3237
|
" ",
|
|
2171
|
-
style.info(asset.
|
|
3238
|
+
style.info(asset.id),
|
|
2172
3239
|
" "
|
|
2173
3240
|
], [
|
|
2174
3241
|
" ",
|
|
@@ -2181,7 +3248,14 @@ var assetBuilder = (assets) => {
|
|
|
2181
3248
|
]);
|
|
2182
3249
|
group.update((group2) => [...group2, line]);
|
|
2183
3250
|
const timer = createTimer();
|
|
2184
|
-
const data = await asset.build
|
|
3251
|
+
const data = await asset.build({
|
|
3252
|
+
async write(file, data2) {
|
|
3253
|
+
const fullpath = join4(assetDir, asset.type, app.name, stack.name, asset.id, file);
|
|
3254
|
+
const basepath = dirname2(fullpath);
|
|
3255
|
+
await mkdir2(basepath, { recursive: true });
|
|
3256
|
+
await writeFile2(fullpath, data2);
|
|
3257
|
+
}
|
|
3258
|
+
});
|
|
2185
3259
|
details.set({
|
|
2186
3260
|
...data,
|
|
2187
3261
|
time: timer()
|
|
@@ -2196,11 +3270,11 @@ var assetBuilder = (assets) => {
|
|
|
2196
3270
|
};
|
|
2197
3271
|
|
|
2198
3272
|
// src/util/cleanup.ts
|
|
2199
|
-
import { mkdir as
|
|
3273
|
+
import { mkdir as mkdir3, rm } from "fs/promises";
|
|
2200
3274
|
var cleanUp = async () => {
|
|
2201
|
-
debug("Clean up
|
|
3275
|
+
debug("Clean up template, cache, and asset files");
|
|
2202
3276
|
const paths = [
|
|
2203
|
-
|
|
3277
|
+
templateDir,
|
|
2204
3278
|
assetDir,
|
|
2205
3279
|
cacheDir
|
|
2206
3280
|
];
|
|
@@ -2209,113 +3283,181 @@ var cleanUp = async () => {
|
|
|
2209
3283
|
force: true,
|
|
2210
3284
|
maxRetries: 2
|
|
2211
3285
|
})));
|
|
2212
|
-
await Promise.all(paths.map((path) =>
|
|
3286
|
+
await Promise.all(paths.map((path) => mkdir3(path, {
|
|
2213
3287
|
recursive: true
|
|
2214
3288
|
})));
|
|
2215
3289
|
};
|
|
2216
3290
|
|
|
3291
|
+
// src/cli/ui/complex/template.ts
|
|
3292
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
3293
|
+
import { join as join5 } from "path";
|
|
3294
|
+
var templateBuilder = (app) => {
|
|
3295
|
+
return async (term) => {
|
|
3296
|
+
const done = term.out.write(loadingDialog("Building stack templates..."));
|
|
3297
|
+
await Promise.all(app.stacks.map(async (stack) => {
|
|
3298
|
+
const template = stack.toString(true);
|
|
3299
|
+
const path = join5(templateDir, app.name);
|
|
3300
|
+
const file = join5(path, `${stack.name}.json`);
|
|
3301
|
+
await mkdir4(path, { recursive: true });
|
|
3302
|
+
await writeFile3(file, template);
|
|
3303
|
+
}));
|
|
3304
|
+
done("Done building stack templates");
|
|
3305
|
+
};
|
|
3306
|
+
};
|
|
3307
|
+
|
|
2217
3308
|
// src/cli/command/build.ts
|
|
2218
3309
|
var build = (program2) => {
|
|
2219
3310
|
program2.command("build").argument("[stack...]", "Optionally filter stacks to build").description("Build your app").action(async (filters) => {
|
|
2220
3311
|
await layout(async (config, write) => {
|
|
2221
|
-
const { app
|
|
3312
|
+
const { app } = await toApp(config, filters);
|
|
2222
3313
|
await cleanUp();
|
|
2223
|
-
await write(assetBuilder(
|
|
2224
|
-
app
|
|
3314
|
+
await write(assetBuilder(app));
|
|
3315
|
+
await write(templateBuilder(app));
|
|
2225
3316
|
});
|
|
2226
3317
|
});
|
|
2227
3318
|
};
|
|
2228
3319
|
|
|
2229
|
-
// src/
|
|
3320
|
+
// src/formation/bootstrap.ts
|
|
3321
|
+
var assetBucketName = (account, region) => {
|
|
3322
|
+
return `awsless-bootstrap-${account}-${region}`;
|
|
3323
|
+
};
|
|
3324
|
+
var assetBucketUrl = (account, region, stack) => {
|
|
3325
|
+
const bucket = assetBucketName(account, region);
|
|
3326
|
+
return `https://s3-${region}.amazonaws.com/${bucket}/${stack.name}/cloudformation.json`;
|
|
3327
|
+
};
|
|
3328
|
+
var version = "1";
|
|
3329
|
+
var bootstrapStack = (account, region) => {
|
|
3330
|
+
const app = new App("awsless");
|
|
3331
|
+
const stack = new Stack("bootstrap", region);
|
|
3332
|
+
stack.add(new Bucket("assets", {
|
|
3333
|
+
name: assetBucketName(account, region),
|
|
3334
|
+
accessControl: "private",
|
|
3335
|
+
versioned: true
|
|
3336
|
+
}));
|
|
3337
|
+
stack.export("version", version);
|
|
3338
|
+
app.add(stack);
|
|
3339
|
+
return { app, stack };
|
|
3340
|
+
};
|
|
3341
|
+
var shouldDeployBootstrap = async (client, stack) => {
|
|
3342
|
+
debug("Check bootstrap status");
|
|
3343
|
+
const info = await client.get(stack.name, stack.region);
|
|
3344
|
+
return !info || info.outputs.version !== version || !["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(info.status);
|
|
3345
|
+
};
|
|
3346
|
+
|
|
3347
|
+
// src/formation/client.ts
|
|
2230
3348
|
import { CloudFormationClient, CreateStackCommand, DeleteStackCommand, DescribeStacksCommand, GetTemplateCommand, OnFailure, TemplateStage, UpdateStackCommand, ValidateTemplateCommand, waitUntilStackCreateComplete, waitUntilStackDeleteComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
|
|
2231
|
-
import { S3Client
|
|
3349
|
+
import { S3Client, PutObjectCommand, ObjectCannedACL, StorageClass } from "@aws-sdk/client-s3";
|
|
3350
|
+
import { paramCase as paramCase4 } from "change-case";
|
|
2232
3351
|
var StackClient = class {
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
this.
|
|
2236
|
-
this.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
});
|
|
3352
|
+
constructor(app, account, region, credentials) {
|
|
3353
|
+
this.app = app;
|
|
3354
|
+
this.account = account;
|
|
3355
|
+
this.region = region;
|
|
3356
|
+
this.credentials = credentials;
|
|
3357
|
+
this.assetBucketName = assetBucketName(this.account, this.region);
|
|
2240
3358
|
}
|
|
2241
|
-
client;
|
|
2242
3359
|
maxWaitTime = 60 * 30;
|
|
2243
3360
|
// 30 minutes
|
|
2244
3361
|
maxDelay = 30;
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
3362
|
+
// 30 seconds
|
|
3363
|
+
assetBucketName;
|
|
3364
|
+
getClient(region) {
|
|
3365
|
+
return new CloudFormationClient({
|
|
3366
|
+
credentials: this.credentials,
|
|
3367
|
+
region
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
shouldUploadTemplate(template) {
|
|
3371
|
+
const size = Buffer.byteLength(template, "utf8");
|
|
2248
3372
|
return size > 5e4;
|
|
2249
3373
|
}
|
|
2250
3374
|
templateProp(stack) {
|
|
2251
|
-
|
|
2252
|
-
|
|
3375
|
+
const template = stack.toString();
|
|
3376
|
+
return this.shouldUploadTemplate(template) ? {
|
|
3377
|
+
TemplateUrl: assetBucketUrl(this.account, this.region, stack)
|
|
2253
3378
|
} : {
|
|
2254
|
-
TemplateBody:
|
|
3379
|
+
TemplateBody: template
|
|
2255
3380
|
};
|
|
2256
3381
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
3382
|
+
stackName(stackName) {
|
|
3383
|
+
return paramCase4(`${this.app.name}-${stackName}`);
|
|
3384
|
+
}
|
|
3385
|
+
tags(stack) {
|
|
3386
|
+
const tags = [];
|
|
3387
|
+
for (const [name, value] of stack.tags.entries()) {
|
|
3388
|
+
tags.push({ Key: name, Value: value });
|
|
3389
|
+
}
|
|
3390
|
+
return tags;
|
|
3391
|
+
}
|
|
3392
|
+
async upload(stack, template) {
|
|
3393
|
+
debug("Upload the", style.info(stack.name), "stack to awsless assets bucket");
|
|
3394
|
+
const client = new S3Client({
|
|
3395
|
+
credentials: this.credentials,
|
|
3396
|
+
region: stack.region
|
|
2262
3397
|
});
|
|
2263
|
-
await client.send(new
|
|
2264
|
-
Bucket:
|
|
2265
|
-
Key: `${stack.
|
|
2266
|
-
Body:
|
|
2267
|
-
ACL:
|
|
2268
|
-
StorageClass:
|
|
3398
|
+
await client.send(new PutObjectCommand({
|
|
3399
|
+
Bucket: this.assetBucketName,
|
|
3400
|
+
Key: `${this.app.name}/${stack.name}/cloudformation.json`,
|
|
3401
|
+
Body: template,
|
|
3402
|
+
ACL: ObjectCannedACL.private,
|
|
3403
|
+
StorageClass: StorageClass.STANDARD_IA
|
|
2269
3404
|
}));
|
|
2270
3405
|
}
|
|
2271
3406
|
async create(stack, capabilities) {
|
|
2272
|
-
debug("Create the", style.info(stack.
|
|
2273
|
-
|
|
2274
|
-
|
|
3407
|
+
debug("Create the", style.info(stack.name), "stack");
|
|
3408
|
+
const client = this.getClient(stack.region);
|
|
3409
|
+
await client.send(new CreateStackCommand({
|
|
3410
|
+
StackName: this.stackName(stack.name),
|
|
2275
3411
|
EnableTerminationProtection: false,
|
|
2276
3412
|
OnFailure: OnFailure.DELETE,
|
|
2277
3413
|
Capabilities: capabilities,
|
|
3414
|
+
Tags: this.tags(stack),
|
|
2278
3415
|
...this.templateProp(stack)
|
|
2279
3416
|
}));
|
|
2280
3417
|
await waitUntilStackCreateComplete({
|
|
2281
|
-
client
|
|
3418
|
+
client,
|
|
2282
3419
|
maxWaitTime: this.maxWaitTime,
|
|
2283
3420
|
maxDelay: this.maxDelay
|
|
2284
3421
|
}, {
|
|
2285
|
-
StackName: stack.
|
|
3422
|
+
StackName: this.stackName(stack.name)
|
|
2286
3423
|
});
|
|
2287
3424
|
}
|
|
2288
3425
|
async update(stack, capabilities) {
|
|
2289
|
-
debug("Update the", style.info(stack.
|
|
2290
|
-
|
|
2291
|
-
|
|
3426
|
+
debug("Update the", style.info(stack.name), "stack");
|
|
3427
|
+
const client = this.getClient(stack.region);
|
|
3428
|
+
await client.send(new UpdateStackCommand({
|
|
3429
|
+
StackName: this.stackName(stack.name),
|
|
2292
3430
|
Capabilities: capabilities,
|
|
3431
|
+
Tags: this.tags(stack),
|
|
2293
3432
|
...this.templateProp(stack)
|
|
2294
3433
|
}));
|
|
2295
3434
|
await waitUntilStackUpdateComplete({
|
|
2296
|
-
client
|
|
3435
|
+
client,
|
|
2297
3436
|
maxWaitTime: this.maxWaitTime,
|
|
2298
3437
|
maxDelay: this.maxDelay
|
|
2299
3438
|
}, {
|
|
2300
|
-
StackName: stack.
|
|
3439
|
+
StackName: this.stackName(stack.name)
|
|
2301
3440
|
});
|
|
2302
3441
|
}
|
|
2303
3442
|
async validate(stack) {
|
|
2304
|
-
debug("Validate the", style.info(stack.
|
|
2305
|
-
const
|
|
3443
|
+
debug("Validate the", style.info(stack.name), "stack");
|
|
3444
|
+
const client = this.getClient(stack.region);
|
|
3445
|
+
const result = await client.send(new ValidateTemplateCommand({
|
|
2306
3446
|
...this.templateProp(stack)
|
|
2307
3447
|
}));
|
|
2308
3448
|
return result.Capabilities;
|
|
2309
3449
|
}
|
|
2310
|
-
async get(name) {
|
|
3450
|
+
async get(name, region) {
|
|
2311
3451
|
debug("Get stack info for:", style.info(name));
|
|
3452
|
+
const client = this.getClient(region);
|
|
2312
3453
|
let result;
|
|
2313
3454
|
try {
|
|
2314
|
-
result = await
|
|
2315
|
-
StackName: name
|
|
3455
|
+
result = await client.send(new DescribeStacksCommand({
|
|
3456
|
+
StackName: this.stackName(name)
|
|
2316
3457
|
}));
|
|
2317
3458
|
} catch (error) {
|
|
2318
3459
|
if (error instanceof Error && error.name === "ValidationError" && error.message.includes("does not exist")) {
|
|
3460
|
+
debug("Stack not found");
|
|
2319
3461
|
return;
|
|
2320
3462
|
}
|
|
2321
3463
|
throw error;
|
|
@@ -2325,8 +3467,8 @@ var StackClient = class {
|
|
|
2325
3467
|
debug("Stack not found");
|
|
2326
3468
|
return;
|
|
2327
3469
|
}
|
|
2328
|
-
const resultTemplate = await
|
|
2329
|
-
StackName: name,
|
|
3470
|
+
const resultTemplate = await client.send(new GetTemplateCommand({
|
|
3471
|
+
StackName: this.stackName(name),
|
|
2330
3472
|
TemplateStage: TemplateStage.Original
|
|
2331
3473
|
}));
|
|
2332
3474
|
const outputs = {};
|
|
@@ -2344,14 +3486,15 @@ var StackClient = class {
|
|
|
2344
3486
|
};
|
|
2345
3487
|
}
|
|
2346
3488
|
async deploy(stack) {
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
|
|
3489
|
+
const template = stack.toString();
|
|
3490
|
+
const data = await this.get(stack.name, stack.region);
|
|
3491
|
+
debug("Deploy:", style.info(stack.name));
|
|
3492
|
+
if (data?.template === template) {
|
|
2350
3493
|
debug("No stack changes");
|
|
2351
3494
|
return false;
|
|
2352
3495
|
}
|
|
2353
|
-
if (this.shouldUploadTemplate(
|
|
2354
|
-
await this.upload(stack);
|
|
3496
|
+
if (this.shouldUploadTemplate(template)) {
|
|
3497
|
+
await this.upload(stack, template);
|
|
2355
3498
|
}
|
|
2356
3499
|
const capabilities = await this.validate(stack);
|
|
2357
3500
|
if (!data) {
|
|
@@ -2363,22 +3506,23 @@ var StackClient = class {
|
|
|
2363
3506
|
}
|
|
2364
3507
|
return true;
|
|
2365
3508
|
}
|
|
2366
|
-
async delete(name) {
|
|
2367
|
-
const data = await this.get(name);
|
|
3509
|
+
async delete(name, region) {
|
|
3510
|
+
const data = await this.get(name, region);
|
|
3511
|
+
const client = this.getClient(region);
|
|
2368
3512
|
debug("Delete the", style.info(name), "stack");
|
|
2369
3513
|
if (!data) {
|
|
2370
3514
|
debug("Already deleted");
|
|
2371
3515
|
return;
|
|
2372
3516
|
}
|
|
2373
|
-
await
|
|
2374
|
-
StackName: name
|
|
3517
|
+
await client.send(new DeleteStackCommand({
|
|
3518
|
+
StackName: this.stackName(name)
|
|
2375
3519
|
}));
|
|
2376
3520
|
await waitUntilStackDeleteComplete({
|
|
2377
|
-
client
|
|
3521
|
+
client,
|
|
2378
3522
|
maxWaitTime: this.maxWaitTime,
|
|
2379
3523
|
maxDelay: this.maxDelay
|
|
2380
3524
|
}, {
|
|
2381
|
-
StackName: name
|
|
3525
|
+
StackName: this.stackName(name)
|
|
2382
3526
|
});
|
|
2383
3527
|
}
|
|
2384
3528
|
};
|
|
@@ -2467,10 +3611,9 @@ var confirmPrompt = (label, options = {}) => {
|
|
|
2467
3611
|
var bootstrapDeployer = (config) => {
|
|
2468
3612
|
return async (term) => {
|
|
2469
3613
|
debug("Initializing bootstrap");
|
|
2470
|
-
const app =
|
|
2471
|
-
const client = new StackClient(config);
|
|
2472
|
-
const
|
|
2473
|
-
const shouldDeploy = await shouldDeployBootstrap(client, bootstrap2.stackName);
|
|
3614
|
+
const { app, stack } = bootstrapStack(config.account, config.region);
|
|
3615
|
+
const client = new StackClient(app, config.account, config.region, config.credentials);
|
|
3616
|
+
const shouldDeploy = await shouldDeployBootstrap(client, stack);
|
|
2474
3617
|
if (shouldDeploy) {
|
|
2475
3618
|
term.out.write(dialog("warning", [`Your app hasn't been bootstrapped yet`]));
|
|
2476
3619
|
const confirmed = await term.out.write(confirmPrompt("Would you like to bootstrap?"));
|
|
@@ -2478,8 +3621,7 @@ var bootstrapDeployer = (config) => {
|
|
|
2478
3621
|
throw new Cancelled();
|
|
2479
3622
|
}
|
|
2480
3623
|
const done = term.out.write(loadingDialog("Bootstrapping..."));
|
|
2481
|
-
|
|
2482
|
-
await client.deploy(assembly.stacks[0]);
|
|
3624
|
+
await client.deploy(stack);
|
|
2483
3625
|
done("Done deploying the bootstrap stack");
|
|
2484
3626
|
} else {
|
|
2485
3627
|
term.out.write(dialog("success", [
|
|
@@ -2505,8 +3647,8 @@ var stackTree = (nodes, statuses) => {
|
|
|
2505
3647
|
const render = (nodes2, deep = 0, parents = []) => {
|
|
2506
3648
|
const size = nodes2.length - 1;
|
|
2507
3649
|
nodes2.forEach((node, i) => {
|
|
2508
|
-
const
|
|
2509
|
-
const status2 = statuses[
|
|
3650
|
+
const name = node.stack.name;
|
|
3651
|
+
const status2 = statuses[name];
|
|
2510
3652
|
const first = i === 0 && deep === 0;
|
|
2511
3653
|
const last = i === size;
|
|
2512
3654
|
const more = i < size;
|
|
@@ -2520,7 +3662,7 @@ var stackTree = (nodes, statuses) => {
|
|
|
2520
3662
|
first && size === 0 ? " " : first ? "\u250C\u2500" : last ? "\u2514\u2500" : "\u251C\u2500"
|
|
2521
3663
|
),
|
|
2522
3664
|
" ",
|
|
2523
|
-
style.info(
|
|
3665
|
+
style.info(name),
|
|
2524
3666
|
" "
|
|
2525
3667
|
], [
|
|
2526
3668
|
" ",
|
|
@@ -2541,28 +3683,27 @@ var stackTree = (nodes, statuses) => {
|
|
|
2541
3683
|
var status = (program2) => {
|
|
2542
3684
|
program2.command("status").argument("[stacks...]", "Optionally filter stacks to lookup status").description("View the application status").action(async (filters) => {
|
|
2543
3685
|
await layout(async (config, write) => {
|
|
2544
|
-
const { app,
|
|
3686
|
+
const { app, dependencyTree } = await toApp(config, filters);
|
|
2545
3687
|
await cleanUp();
|
|
2546
|
-
await write(assetBuilder(
|
|
2547
|
-
|
|
3688
|
+
await write(assetBuilder(app));
|
|
3689
|
+
await write(templateBuilder(app));
|
|
2548
3690
|
const doneLoading = write(loadingDialog("Loading stack information..."));
|
|
2549
|
-
const client = new StackClient(config);
|
|
3691
|
+
const client = new StackClient(app, config.account, config.region, config.credentials);
|
|
2550
3692
|
const statuses = [];
|
|
2551
3693
|
const stackStatuses = {};
|
|
2552
|
-
|
|
2553
|
-
stackStatuses[stack.
|
|
2554
|
-
}
|
|
3694
|
+
for (const stack of app) {
|
|
3695
|
+
stackStatuses[stack.name] = new Signal(style.info("Loading..."));
|
|
3696
|
+
}
|
|
2555
3697
|
write(stackTree(dependencyTree, stackStatuses));
|
|
2556
3698
|
debug("Load metadata for all deployed stacks on AWS");
|
|
2557
|
-
await Promise.all(
|
|
2558
|
-
const info = await client.get(stack.
|
|
2559
|
-
const
|
|
2560
|
-
const signal = stackStatuses[name];
|
|
3699
|
+
await Promise.all(app.stacks.map(async (stack, i) => {
|
|
3700
|
+
const info = await client.get(stack.name, stack.region);
|
|
3701
|
+
const signal = stackStatuses[stack.name];
|
|
2561
3702
|
await new Promise((resolve) => setTimeout(resolve, i * 1e3));
|
|
2562
3703
|
if (!info) {
|
|
2563
3704
|
signal.set(style.error("non-existent"));
|
|
2564
3705
|
statuses.push("non-existent");
|
|
2565
|
-
} else if (info.template !==
|
|
3706
|
+
} else if (info.template !== stack.toString()) {
|
|
2566
3707
|
signal.set(style.warning("out-of-date"));
|
|
2567
3708
|
statuses.push("out-of-date");
|
|
2568
3709
|
} else {
|
|
@@ -2581,12 +3722,77 @@ var status = (program2) => {
|
|
|
2581
3722
|
});
|
|
2582
3723
|
};
|
|
2583
3724
|
|
|
3725
|
+
// src/cli/ui/complex/publisher.ts
|
|
3726
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
3727
|
+
import { join as join6 } from "path";
|
|
3728
|
+
import { GetObjectCommand, ObjectCannedACL as ObjectCannedACL2, PutObjectCommand as PutObjectCommand2, S3Client as S3Client2, StorageClass as StorageClass2 } from "@aws-sdk/client-s3";
|
|
3729
|
+
var assetPublisher = (config, app) => {
|
|
3730
|
+
const client = new S3Client2({
|
|
3731
|
+
credentials: config.credentials,
|
|
3732
|
+
region: config.region
|
|
3733
|
+
});
|
|
3734
|
+
return async (term) => {
|
|
3735
|
+
const done = term.out.write(loadingDialog("Publishing stack assets to AWS..."));
|
|
3736
|
+
await Promise.all(app.stacks.map(async (stack) => {
|
|
3737
|
+
await Promise.all([...stack.assets].map(async (asset) => {
|
|
3738
|
+
await asset.publish?.({
|
|
3739
|
+
async read(file) {
|
|
3740
|
+
const path = join6(assetDir, asset.type, app.name, stack.name, asset.id, file);
|
|
3741
|
+
const data = await readFile3(path);
|
|
3742
|
+
return data;
|
|
3743
|
+
},
|
|
3744
|
+
async publish(name, data, hash) {
|
|
3745
|
+
const key = `${app.name}/${stack.name}/function/${name}`;
|
|
3746
|
+
const bucket = assetBucketName(config.account, config.region);
|
|
3747
|
+
let getResult;
|
|
3748
|
+
try {
|
|
3749
|
+
getResult = await client.send(new GetObjectCommand({
|
|
3750
|
+
Bucket: bucket,
|
|
3751
|
+
Key: key
|
|
3752
|
+
}));
|
|
3753
|
+
} catch (error) {
|
|
3754
|
+
if (error instanceof Error && error.name === "NoSuchKey") {
|
|
3755
|
+
} else {
|
|
3756
|
+
throw error;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
if (getResult?.Metadata?.hash === hash) {
|
|
3760
|
+
return {
|
|
3761
|
+
bucket,
|
|
3762
|
+
key,
|
|
3763
|
+
version: getResult.VersionId
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
const putResult = await client.send(new PutObjectCommand2({
|
|
3767
|
+
Bucket: bucket,
|
|
3768
|
+
Key: key,
|
|
3769
|
+
Body: data,
|
|
3770
|
+
ACL: ObjectCannedACL2.private,
|
|
3771
|
+
StorageClass: StorageClass2.STANDARD,
|
|
3772
|
+
Metadata: {
|
|
3773
|
+
hash
|
|
3774
|
+
}
|
|
3775
|
+
}));
|
|
3776
|
+
return {
|
|
3777
|
+
bucket,
|
|
3778
|
+
key,
|
|
3779
|
+
version: putResult.VersionId
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
});
|
|
3783
|
+
}));
|
|
3784
|
+
}));
|
|
3785
|
+
done("Done publishing stack assets to AWS");
|
|
3786
|
+
};
|
|
3787
|
+
};
|
|
3788
|
+
|
|
2584
3789
|
// src/cli/command/deploy.ts
|
|
2585
3790
|
var deploy = (program2) => {
|
|
2586
3791
|
program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
|
|
2587
3792
|
await layout(async (config, write) => {
|
|
2588
3793
|
await write(bootstrapDeployer(config));
|
|
2589
|
-
const { app,
|
|
3794
|
+
const { app, dependencyTree } = await toApp(config, filters);
|
|
3795
|
+
const stackNames = app.stacks.map((stack) => stack.name);
|
|
2590
3796
|
const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
|
|
2591
3797
|
debug("Stacks to deploy", formattedFilter);
|
|
2592
3798
|
const deployAll = filters.length === 0;
|
|
@@ -2596,30 +3802,23 @@ var deploy = (program2) => {
|
|
|
2596
3802
|
throw new Cancelled();
|
|
2597
3803
|
}
|
|
2598
3804
|
await cleanUp();
|
|
2599
|
-
await write(assetBuilder(
|
|
2600
|
-
|
|
2601
|
-
await
|
|
2602
|
-
await Promise.all(assets2.map(async (asset) => {
|
|
2603
|
-
await asset.publish?.();
|
|
2604
|
-
}));
|
|
2605
|
-
}));
|
|
2606
|
-
donePublishing("Done publishing stack assets to AWS");
|
|
2607
|
-
const assembly = app.synth();
|
|
3805
|
+
await write(assetBuilder(app));
|
|
3806
|
+
await write(assetPublisher(config, app));
|
|
3807
|
+
await write(templateBuilder(app));
|
|
2608
3808
|
const statuses = {};
|
|
2609
|
-
|
|
2610
|
-
statuses[stack.
|
|
2611
|
-
}
|
|
3809
|
+
for (const stack of app) {
|
|
3810
|
+
statuses[stack.name] = new Signal(style.info("waiting"));
|
|
3811
|
+
}
|
|
2612
3812
|
const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
|
|
2613
3813
|
write(stackTree(dependencyTree, statuses));
|
|
2614
|
-
const client = new StackClient(config);
|
|
3814
|
+
const client = new StackClient(app, config.account, config.region, config.credentials);
|
|
2615
3815
|
const deploymentLine = createDeploymentLine(dependencyTree);
|
|
2616
3816
|
for (const stacks of deploymentLine) {
|
|
2617
3817
|
const results = await Promise.allSettled(stacks.map(async (stack) => {
|
|
2618
|
-
const signal = statuses[stack.
|
|
2619
|
-
const stackArtifect = assembly.stacks.find((item) => item.id === stack.artifactId);
|
|
3818
|
+
const signal = statuses[stack.name];
|
|
2620
3819
|
signal.set(style.warning("deploying"));
|
|
2621
3820
|
try {
|
|
2622
|
-
await client.deploy(
|
|
3821
|
+
await client.deploy(stack);
|
|
2623
3822
|
} catch (error) {
|
|
2624
3823
|
debugError(error);
|
|
2625
3824
|
signal.set(style.error("failed"));
|
|
@@ -2787,6 +3986,16 @@ var secrets = (program2) => {
|
|
|
2787
3986
|
commands.forEach((cb) => cb(command));
|
|
2788
3987
|
};
|
|
2789
3988
|
|
|
3989
|
+
// src/cli/command/test.ts
|
|
3990
|
+
var test = (program2) => {
|
|
3991
|
+
program2.command("test").action(async () => {
|
|
3992
|
+
await layout(async (config) => {
|
|
3993
|
+
const app = new App("test");
|
|
3994
|
+
const name = "test5";
|
|
3995
|
+
});
|
|
3996
|
+
});
|
|
3997
|
+
};
|
|
3998
|
+
|
|
2790
3999
|
// src/cli/program.ts
|
|
2791
4000
|
var program = new Command();
|
|
2792
4001
|
program.name(logo().join("").replace(/\s+/, ""));
|
|
@@ -2807,7 +4016,8 @@ var commands2 = [
|
|
|
2807
4016
|
status,
|
|
2808
4017
|
build,
|
|
2809
4018
|
deploy,
|
|
2810
|
-
secrets
|
|
4019
|
+
secrets,
|
|
4020
|
+
test
|
|
2811
4021
|
// diff,
|
|
2812
4022
|
// remove,
|
|
2813
4023
|
];
|