@awsless/awsless 0.0.13 → 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 +2500 -1044
- package/dist/bin.js +2504 -1048
- package/dist/index.cjs +25 -21
- package/dist/index.d.ts +933 -804
- package/dist/index.js +20 -19
- 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,78 +138,408 @@ var Params = class {
|
|
|
145
138
|
}
|
|
146
139
|
};
|
|
147
140
|
|
|
148
|
-
// src/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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);
|
|
157
177
|
}
|
|
158
|
-
|
|
159
|
-
|
|
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");
|
|
160
450
|
const bindings = [];
|
|
161
451
|
const bind = (cb) => {
|
|
162
452
|
bindings.push(cb);
|
|
163
453
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
}
|
|
196
486
|
return {
|
|
197
487
|
stack,
|
|
198
|
-
functions,
|
|
199
|
-
bindings,
|
|
200
488
|
depends: stackConfig.depends
|
|
201
489
|
};
|
|
202
490
|
};
|
|
203
491
|
|
|
204
|
-
// src/util/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
+
};
|
|
214
538
|
|
|
215
539
|
// src/plugins/cron/index.ts
|
|
216
|
-
import { z as
|
|
540
|
+
import { z as z7 } from "zod";
|
|
217
541
|
|
|
218
542
|
// src/plugins/cron/schema/schedule.ts
|
|
219
|
-
import { Schedule } from "aws-cdk-lib/aws-events";
|
|
220
543
|
import { z } from "zod";
|
|
221
544
|
import { awsCronExpressionValidator } from "aws-cron-expression-validator";
|
|
222
545
|
var RateExpressionSchema = z.custom((value) => {
|
|
@@ -225,7 +548,7 @@ var RateExpressionSchema = z.custom((value) => {
|
|
|
225
548
|
const number = parseInt(str);
|
|
226
549
|
return number > 0;
|
|
227
550
|
}).safeParse(value).success;
|
|
228
|
-
}, "Invalid rate expression")
|
|
551
|
+
}, "Invalid rate expression");
|
|
229
552
|
var CronExpressionSchema = z.custom((value) => {
|
|
230
553
|
return z.string().startsWith("cron(").endsWith(")").safeParse(value).success;
|
|
231
554
|
}, "Invalid cron expression").superRefine((value, ctx) => {
|
|
@@ -245,55 +568,81 @@ var CronExpressionSchema = z.custom((value) => {
|
|
|
245
568
|
});
|
|
246
569
|
}
|
|
247
570
|
}
|
|
248
|
-
})
|
|
571
|
+
});
|
|
249
572
|
var ScheduleExpressionSchema = RateExpressionSchema.or(CronExpressionSchema);
|
|
250
573
|
|
|
251
|
-
// src/plugins/
|
|
252
|
-
import {
|
|
574
|
+
// src/plugins/function.ts
|
|
575
|
+
import { z as z6 } from "zod";
|
|
253
576
|
|
|
254
|
-
// src/
|
|
255
|
-
import {
|
|
256
|
-
var toId = (resource, id) => {
|
|
257
|
-
return pascalCase(`${resource}-${id}`);
|
|
258
|
-
};
|
|
259
|
-
var toName = (stack, id) => {
|
|
260
|
-
return paramCase(`${stack.stackName}-${id}`);
|
|
261
|
-
};
|
|
262
|
-
var toEnvKey = (resource, id) => {
|
|
263
|
-
return `RESOURCE_${resource.toUpperCase()}_${id}`;
|
|
264
|
-
};
|
|
265
|
-
var addResourceEnvironment = (stack, resource, id, lambda) => {
|
|
266
|
-
const key = toEnvKey(resource, id);
|
|
267
|
-
const value = toName(stack, id);
|
|
268
|
-
lambda.addEnvironment(key, value, {
|
|
269
|
-
removeInEdge: true
|
|
270
|
-
});
|
|
271
|
-
};
|
|
577
|
+
// src/schema/duration.ts
|
|
578
|
+
import { z as z2 } from "zod";
|
|
272
579
|
|
|
273
|
-
// src/
|
|
274
|
-
|
|
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
|
+
};
|
|
275
616
|
|
|
276
617
|
// src/schema/duration.ts
|
|
277
|
-
import { z as z2 } from "zod";
|
|
278
|
-
import { Duration as CDKDuration } from "aws-cdk-lib/core";
|
|
279
618
|
function toDuration(duration) {
|
|
280
619
|
const [count, unit] = duration.split(" ");
|
|
281
620
|
const countNum = parseInt(count);
|
|
282
621
|
const unitLower = unit.toLowerCase();
|
|
283
622
|
if (unitLower.startsWith("second")) {
|
|
284
|
-
return
|
|
623
|
+
return Duration.seconds(countNum);
|
|
285
624
|
} else if (unitLower.startsWith("minute")) {
|
|
286
|
-
return
|
|
625
|
+
return Duration.minutes(countNum);
|
|
287
626
|
} else if (unitLower.startsWith("hour")) {
|
|
288
|
-
return
|
|
627
|
+
return Duration.hours(countNum);
|
|
289
628
|
} else if (unitLower.startsWith("day")) {
|
|
290
|
-
return
|
|
629
|
+
return Duration.days(countNum);
|
|
291
630
|
}
|
|
292
|
-
return
|
|
631
|
+
return Duration.days(0);
|
|
293
632
|
}
|
|
294
633
|
var DurationSchema = z2.custom((value) => {
|
|
295
634
|
return z2.string().regex(/[0-9]+ (seconds?|minutes?|hours?|days?)/).safeParse(value).success;
|
|
296
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
|
+
};
|
|
297
646
|
|
|
298
647
|
// src/schema/local-file.ts
|
|
299
648
|
import { access, constants } from "fs/promises";
|
|
@@ -307,202 +656,86 @@ var LocalFileSchema = z3.string().refine(async (path) => {
|
|
|
307
656
|
return true;
|
|
308
657
|
}, `File doesn't exist`);
|
|
309
658
|
|
|
310
|
-
// src/
|
|
311
|
-
import { Code, Function } from "aws-cdk-lib/aws-lambda";
|
|
312
|
-
|
|
313
|
-
// src/plugins/function/schema/runtime.ts
|
|
314
|
-
import { Runtime as CdkRuntime } from "aws-cdk-lib/aws-lambda";
|
|
659
|
+
// src/schema/resource-id.ts
|
|
315
660
|
import { z as z4 } from "zod";
|
|
316
|
-
var
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
"nodejs16.x": CdkRuntime.NODEJS_16_X,
|
|
320
|
-
"nodejs18.x": CdkRuntime.NODEJS_18_X,
|
|
321
|
-
"python3.9": CdkRuntime.PYTHON_3_9,
|
|
322
|
-
"python3.10": CdkRuntime.PYTHON_3_10,
|
|
323
|
-
"go1.x": CdkRuntime.PROVIDED_AL2,
|
|
324
|
-
"go": CdkRuntime.PROVIDED_AL2
|
|
325
|
-
};
|
|
326
|
-
var toRuntime = (runtime) => {
|
|
327
|
-
return runtimes[runtime];
|
|
328
|
-
};
|
|
329
|
-
var RuntimeSchema = z4.enum(Object.keys(runtimes)).transform(toRuntime);
|
|
330
|
-
|
|
331
|
-
// src/plugins/function/schema/architecture.ts
|
|
332
|
-
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
|
|
333
664
|
import { z as z5 } from "zod";
|
|
334
|
-
var toArchitecture = (architecture) => {
|
|
335
|
-
return architecture === "x86_64" ? CdkArchitecture.X86_64 : CdkArchitecture.ARM_64;
|
|
336
|
-
};
|
|
337
|
-
var ArchitectureSchema = z5.enum(["x86_64", "arm_64"]).transform(toArchitecture);
|
|
338
665
|
|
|
339
|
-
// src/
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
};
|
|
342
696
|
|
|
343
697
|
// src/schema/size.ts
|
|
344
|
-
import { Size as CDKSize } from "aws-cdk-lib/core";
|
|
345
|
-
import { z as z7 } from "zod";
|
|
346
698
|
function toSize(size) {
|
|
347
699
|
const [count, unit] = size.split(" ");
|
|
348
700
|
const countNum = parseInt(count);
|
|
349
701
|
if (unit === "KB") {
|
|
350
|
-
return
|
|
702
|
+
return Size.kiloBytes(countNum);
|
|
351
703
|
} else if (unit === "MB") {
|
|
352
|
-
return
|
|
704
|
+
return Size.megaBytes(countNum);
|
|
353
705
|
} else if (unit === "GB") {
|
|
354
|
-
return
|
|
706
|
+
return Size.gigaBytes(countNum);
|
|
355
707
|
}
|
|
356
708
|
throw new TypeError(`Invalid size ${size}`);
|
|
357
709
|
}
|
|
358
|
-
var SizeSchema =
|
|
359
|
-
return
|
|
710
|
+
var SizeSchema = z5.custom((value) => {
|
|
711
|
+
return z5.string().regex(/[0-9]+ (KB|MB|GB)/).safeParse(value).success;
|
|
360
712
|
}, "Invalid size").transform(toSize);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
import { basename, join as join2 } from "path";
|
|
365
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
366
|
-
import { filesize } from "filesize";
|
|
367
|
-
var zipFiles = (files) => {
|
|
368
|
-
const zip = new JSZip();
|
|
369
|
-
for (const file of files) {
|
|
370
|
-
zip.file(file.name, file.code);
|
|
371
|
-
}
|
|
372
|
-
return zip.generateAsync({
|
|
373
|
-
type: "nodebuffer",
|
|
374
|
-
compression: "DEFLATE",
|
|
375
|
-
compressionOptions: {
|
|
376
|
-
level: 9
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
};
|
|
380
|
-
var writeBuildHash = async (config, stack, id, hash) => {
|
|
381
|
-
const funcPath = join2(assetDir, "function", config.name, stack.artifactId, id);
|
|
382
|
-
const versionFile = join2(funcPath, "HASH");
|
|
383
|
-
await writeFile(versionFile, hash);
|
|
384
|
-
};
|
|
385
|
-
var writeBuildFiles = async (config, stack, id, files) => {
|
|
386
|
-
const bundle = await zipFiles(files);
|
|
387
|
-
const funcPath = join2(assetDir, "function", config.name, stack.artifactId, id);
|
|
388
|
-
const filesPath = join2(funcPath, "files");
|
|
389
|
-
const bundleFile = join2(funcPath, "bundle.zip");
|
|
390
|
-
debug("Bundle size of", style.info(join2(config.name, stack.artifactId, id)), "is", style.attr(filesize(bundle.byteLength)));
|
|
391
|
-
await mkdir(filesPath, { recursive: true });
|
|
392
|
-
await writeFile(bundleFile, bundle);
|
|
393
|
-
await Promise.all(files.map(async (file) => {
|
|
394
|
-
const fileName = join2(filesPath, file.name);
|
|
395
|
-
await mkdir(basename(fileName), { recursive: true });
|
|
396
|
-
await writeFile(fileName, file.code);
|
|
397
|
-
if (file.map) {
|
|
398
|
-
const mapName = join2(filesPath, `${file.name}.map`);
|
|
399
|
-
await writeFile(mapName, file.map);
|
|
400
|
-
}
|
|
401
|
-
}));
|
|
402
|
-
return {
|
|
403
|
-
file: bundleFile,
|
|
404
|
-
size: bundle.byteLength
|
|
713
|
+
var sizeMin = (min) => {
|
|
714
|
+
return (size) => {
|
|
715
|
+
return size.toBytes() >= min.toBytes();
|
|
405
716
|
};
|
|
406
717
|
};
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
import { GetObjectCommand, ObjectCannedACL, PutObjectCommand, S3Client, StorageClass } from "@aws-sdk/client-s3";
|
|
412
|
-
|
|
413
|
-
// src/stack/bootstrap.ts
|
|
414
|
-
import { CfnOutput, RemovalPolicy, Stack as Stack2 } from "aws-cdk-lib";
|
|
415
|
-
import { Bucket, BucketAccessControl } from "aws-cdk-lib/aws-s3";
|
|
416
|
-
var assetBucketName = (config) => {
|
|
417
|
-
return `awsless-bootstrap-${config.account}-${config.region}`;
|
|
418
|
-
};
|
|
419
|
-
var assetBucketUrl = (config, stackName) => {
|
|
420
|
-
const bucket = assetBucketName(config);
|
|
421
|
-
return `https://s3-${config.region}.amazonaws.com/${bucket}/${stackName}/cloudformation.json`;
|
|
422
|
-
};
|
|
423
|
-
var version = "2";
|
|
424
|
-
var bootstrapStack = (config, app) => {
|
|
425
|
-
const stack = new Stack2(app, "bootstrap", {
|
|
426
|
-
stackName: `awsless-bootstrap`
|
|
427
|
-
});
|
|
428
|
-
new Bucket(stack, "assets", {
|
|
429
|
-
bucketName: assetBucketName(config),
|
|
430
|
-
versioned: true,
|
|
431
|
-
accessControl: BucketAccessControl.PRIVATE,
|
|
432
|
-
removalPolicy: RemovalPolicy.DESTROY
|
|
433
|
-
});
|
|
434
|
-
new CfnOutput(stack, "version", {
|
|
435
|
-
exportName: "version",
|
|
436
|
-
value: version
|
|
437
|
-
});
|
|
438
|
-
return stack;
|
|
439
|
-
};
|
|
440
|
-
var shouldDeployBootstrap = async (client, name) => {
|
|
441
|
-
debug("Check bootstrap status");
|
|
442
|
-
const info = await client.get(name);
|
|
443
|
-
return !info || info.outputs.version !== version || !["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(info.status);
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
// src/plugins/function/util/publish.ts
|
|
447
|
-
var publishFunctionAsset = async (config, stack, id) => {
|
|
448
|
-
const bucket = assetBucketName(config);
|
|
449
|
-
const key = `${config.name}/${stack.artifactId}/function/${id}.zip`;
|
|
450
|
-
const funcPath = join3(assetDir, "function", config.name, stack.artifactId, id);
|
|
451
|
-
const bundleFile = join3(funcPath, "bundle.zip");
|
|
452
|
-
const hashFile = join3(funcPath, "HASH");
|
|
453
|
-
const hash = await readFile(hashFile, "utf8");
|
|
454
|
-
const file = await readFile(bundleFile);
|
|
455
|
-
const client = new S3Client({
|
|
456
|
-
credentials: config.credentials,
|
|
457
|
-
region: config.region
|
|
458
|
-
});
|
|
459
|
-
let getResult;
|
|
460
|
-
try {
|
|
461
|
-
getResult = await client.send(new GetObjectCommand({
|
|
462
|
-
Bucket: bucket,
|
|
463
|
-
Key: key
|
|
464
|
-
}));
|
|
465
|
-
} catch (error) {
|
|
466
|
-
if (error instanceof Error && error.name === "NoSuchKey") {
|
|
467
|
-
} else {
|
|
468
|
-
throw error;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (getResult?.Metadata?.hash === hash) {
|
|
472
|
-
return getResult.VersionId;
|
|
473
|
-
}
|
|
474
|
-
const putResult = await client.send(new PutObjectCommand({
|
|
475
|
-
Bucket: bucket,
|
|
476
|
-
Key: key,
|
|
477
|
-
Body: file,
|
|
478
|
-
ACL: ObjectCannedACL.private,
|
|
479
|
-
StorageClass: StorageClass.STANDARD,
|
|
480
|
-
Metadata: {
|
|
481
|
-
hash
|
|
482
|
-
}
|
|
483
|
-
}));
|
|
484
|
-
return putResult.VersionId;
|
|
718
|
+
var sizeMax = (max) => {
|
|
719
|
+
return (size) => {
|
|
720
|
+
return size.toBytes() <= max.toBytes();
|
|
721
|
+
};
|
|
485
722
|
};
|
|
486
723
|
|
|
487
|
-
// src/plugins/function/schema/retry-attempts.ts
|
|
488
|
-
import { z as z8 } from "zod";
|
|
489
|
-
var RetryAttempts = z8.number().int().min(0).max(2);
|
|
490
|
-
|
|
491
724
|
// src/util/byte-size.ts
|
|
492
|
-
import { filesize
|
|
725
|
+
import { filesize } from "filesize";
|
|
493
726
|
var formatByteSize = (size) => {
|
|
494
|
-
const [number, unit] =
|
|
727
|
+
const [number, unit] = filesize(size).toString().split(" ");
|
|
495
728
|
return style.attr(number) + style.attr.dim(unit);
|
|
496
729
|
};
|
|
497
730
|
|
|
498
|
-
// src/
|
|
731
|
+
// src/formation/resource/lambda/util/rollup.ts
|
|
499
732
|
import { rollup } from "rollup";
|
|
500
733
|
import { createHash } from "crypto";
|
|
501
734
|
import { swc } from "rollup-plugin-swc3";
|
|
502
735
|
import json from "@rollup/plugin-json";
|
|
503
736
|
import commonjs from "@rollup/plugin-commonjs";
|
|
504
737
|
import nodeResolve from "@rollup/plugin-node-resolve";
|
|
505
|
-
var
|
|
738
|
+
var rollupBundle = async (input) => {
|
|
506
739
|
const bundle = await rollup({
|
|
507
740
|
input,
|
|
508
741
|
external: (importee) => {
|
|
@@ -547,34 +780,160 @@ var rollupBuild = async (input) => {
|
|
|
547
780
|
};
|
|
548
781
|
};
|
|
549
782
|
|
|
550
|
-
// src/
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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([
|
|
911
|
+
LocalFileSchema,
|
|
912
|
+
z6.object({
|
|
913
|
+
file: LocalFileSchema,
|
|
914
|
+
timeout: TimeoutSchema.optional(),
|
|
915
|
+
runtime: RuntimeSchema.optional(),
|
|
916
|
+
memorySize: MemorySizeSchema.optional(),
|
|
917
|
+
architecture: ArchitectureSchema.optional(),
|
|
918
|
+
ephemeralStorageSize: EphemeralStorageSizeSchema.optional(),
|
|
919
|
+
retryAttempts: RetryAttemptsSchema.optional(),
|
|
920
|
+
environment: EnvironmentSchema.optional()
|
|
921
|
+
})
|
|
922
|
+
]);
|
|
923
|
+
var schema = z6.object({
|
|
924
|
+
defaults: z6.object({
|
|
925
|
+
function: z6.object({
|
|
926
|
+
timeout: TimeoutSchema.default("10 seconds"),
|
|
568
927
|
runtime: RuntimeSchema.default("nodejs18.x"),
|
|
569
|
-
memorySize:
|
|
570
|
-
architecture: ArchitectureSchema.default("
|
|
571
|
-
ephemeralStorageSize:
|
|
572
|
-
retryAttempts:
|
|
573
|
-
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()
|
|
574
933
|
}).default({})
|
|
575
934
|
}).default({}),
|
|
576
|
-
stacks:
|
|
577
|
-
functions:
|
|
935
|
+
stacks: z6.object({
|
|
936
|
+
functions: z6.record(
|
|
578
937
|
ResourceIdSchema,
|
|
579
938
|
FunctionSchema
|
|
580
939
|
).optional()
|
|
@@ -583,200 +942,466 @@ var schema = z9.object({
|
|
|
583
942
|
var functionPlugin = definePlugin({
|
|
584
943
|
name: "function",
|
|
585
944
|
schema,
|
|
586
|
-
onStack(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
+
}
|
|
590
950
|
}
|
|
591
951
|
});
|
|
592
|
-
var
|
|
952
|
+
var toLambdaFunction = (ctx, id, fileOrProps) => {
|
|
953
|
+
const config = ctx.config;
|
|
954
|
+
const stack = ctx.stack;
|
|
593
955
|
const props = typeof fileOrProps === "string" ? { ...config.defaults?.function, file: fileOrProps } : { ...config.defaults?.function, ...fileOrProps };
|
|
594
|
-
const lambda = new Function(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
...props,
|
|
599
|
-
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
|
|
600
960
|
});
|
|
601
|
-
lambda.addEnvironment("APP", config.name
|
|
602
|
-
lambda.addEnvironment("STAGE", config.stage
|
|
603
|
-
lambda.addEnvironment("STACK", stack.
|
|
604
|
-
if (
|
|
605
|
-
lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1"
|
|
606
|
-
removeInEdge: true
|
|
607
|
-
});
|
|
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");
|
|
608
966
|
}
|
|
609
|
-
assets.add({
|
|
610
|
-
stackName: stack.artifactId,
|
|
611
|
-
resource: "function",
|
|
612
|
-
resourceName: id,
|
|
613
|
-
async build() {
|
|
614
|
-
const result = await rollupBuild(props.file);
|
|
615
|
-
const bundle = await writeBuildFiles(config, stack, id, result.files);
|
|
616
|
-
await writeBuildHash(config, stack, id, result.hash);
|
|
617
|
-
const func = lambda.node.defaultChild;
|
|
618
|
-
func.handler = result.handler;
|
|
619
|
-
return {
|
|
620
|
-
size: formatByteSize(bundle.size)
|
|
621
|
-
};
|
|
622
|
-
},
|
|
623
|
-
async publish() {
|
|
624
|
-
const version2 = await publishFunctionAsset(config, stack, id);
|
|
625
|
-
const func = lambda.node.defaultChild;
|
|
626
|
-
func.code = {
|
|
627
|
-
s3Bucket: assetBucketName(config),
|
|
628
|
-
s3Key: `${config.name}/${stack.artifactId}/function/${id}.zip`,
|
|
629
|
-
s3ObjectVersion: version2
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
967
|
return lambda;
|
|
634
968
|
};
|
|
635
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
|
+
|
|
636
1039
|
// src/plugins/cron/index.ts
|
|
637
|
-
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
|
|
638
1040
|
var cronPlugin = definePlugin({
|
|
639
1041
|
name: "cron",
|
|
640
|
-
schema:
|
|
641
|
-
stacks:
|
|
642
|
-
crons:
|
|
1042
|
+
schema: z7.object({
|
|
1043
|
+
stacks: z7.object({
|
|
1044
|
+
crons: z7.record(ResourceIdSchema, z7.object({
|
|
643
1045
|
consumer: FunctionSchema,
|
|
644
1046
|
schedule: ScheduleExpressionSchema,
|
|
645
|
-
|
|
1047
|
+
payload: z7.unknown().optional()
|
|
646
1048
|
})).optional()
|
|
647
1049
|
}).array()
|
|
648
1050
|
}),
|
|
649
|
-
onStack(
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
new
|
|
654
|
-
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, {
|
|
655
1056
|
schedule: props.schedule,
|
|
656
|
-
|
|
657
|
-
targets: [target]
|
|
1057
|
+
payload: props.payload
|
|
658
1058
|
});
|
|
659
|
-
|
|
660
|
-
}
|
|
1059
|
+
stack.add(lambda, source);
|
|
1060
|
+
}
|
|
661
1061
|
}
|
|
662
1062
|
});
|
|
663
1063
|
|
|
664
1064
|
// src/plugins/queue.ts
|
|
665
|
-
import { z as
|
|
666
|
-
|
|
667
|
-
|
|
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
|
|
668
1165
|
var queuePlugin = definePlugin({
|
|
669
1166
|
name: "queue",
|
|
670
|
-
schema:
|
|
671
|
-
defaults:
|
|
672
|
-
queue
|
|
673
|
-
|
|
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' */
|
|
674
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' */
|
|
675
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' */
|
|
676
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' */
|
|
677
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' */
|
|
678
1194
|
maxMessageSize: SizeSchema.default("256 KB")
|
|
679
1195
|
}).default({})
|
|
680
1196
|
}).default({}),
|
|
681
|
-
stacks:
|
|
682
|
-
queues
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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()
|
|
694
1240
|
}).array()
|
|
695
1241
|
}),
|
|
696
1242
|
onStack(ctx) {
|
|
697
1243
|
const { stack, config, stackConfig, bind } = ctx;
|
|
698
|
-
|
|
1244
|
+
for (const [id, functionOrProps] of Object.entries(stackConfig.queues || {})) {
|
|
699
1245
|
const props = typeof functionOrProps === "string" ? { ...config.defaults.queue, consumer: functionOrProps } : { ...config.defaults.queue, ...functionOrProps };
|
|
700
|
-
const queue2 = new Queue(
|
|
701
|
-
|
|
702
|
-
...props
|
|
703
|
-
|
|
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
|
|
704
1253
|
});
|
|
705
|
-
|
|
706
|
-
lambda.addEventSource(new SqsEventSource(queue2));
|
|
1254
|
+
stack.add(queue2, lambda, source);
|
|
707
1255
|
bind((lambda2) => {
|
|
708
|
-
queue2.
|
|
709
|
-
addResourceEnvironment(stack, "queue", id, lambda2);
|
|
1256
|
+
lambda2.addPermissions(queue2.permissions);
|
|
710
1257
|
});
|
|
711
|
-
|
|
712
|
-
});
|
|
1258
|
+
}
|
|
713
1259
|
}
|
|
714
1260
|
});
|
|
715
1261
|
|
|
716
|
-
// src/plugins/table
|
|
717
|
-
import { z as
|
|
718
|
-
import { BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
|
|
719
|
-
|
|
720
|
-
// src/plugins/table/schema/class-type.ts
|
|
721
|
-
import { TableClass } from "aws-cdk-lib/aws-dynamodb";
|
|
722
|
-
import { z as z12 } from "zod";
|
|
723
|
-
var types = {
|
|
724
|
-
"standard": TableClass.STANDARD,
|
|
725
|
-
"standard-infrequent-access": TableClass.STANDARD_INFREQUENT_ACCESS
|
|
726
|
-
};
|
|
727
|
-
var TableClassSchema = z12.enum(Object.keys(types)).transform((value) => {
|
|
728
|
-
return types[value];
|
|
729
|
-
});
|
|
1262
|
+
// src/plugins/table.ts
|
|
1263
|
+
import { z as z9 } from "zod";
|
|
730
1264
|
|
|
731
|
-
// src/
|
|
732
|
-
import {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
+
}
|
|
738
1335
|
};
|
|
739
|
-
var AttributeSchema = z13.enum(Object.keys(types2)).transform((value) => types2[value]);
|
|
740
|
-
|
|
741
|
-
// src/plugins/table/schema/key.ts
|
|
742
|
-
import { z as z14 } from "zod";
|
|
743
|
-
var KeySchema = z14.string().min(1).max(255);
|
|
744
|
-
|
|
745
|
-
// src/plugins/table/schema/projection-type.ts
|
|
746
|
-
import { ProjectionType } from "aws-cdk-lib/aws-dynamodb";
|
|
747
|
-
import { z as z15 } from "zod";
|
|
748
|
-
var types3 = {
|
|
749
|
-
"all": ProjectionType.ALL,
|
|
750
|
-
"keys-only": ProjectionType.KEYS_ONLY
|
|
751
|
-
};
|
|
752
|
-
var ProjectionTypeSchema = z15.union([
|
|
753
|
-
z15.enum(Object.keys(types3)).transform((value) => ({
|
|
754
|
-
ProjectionType: types3[value]
|
|
755
|
-
})),
|
|
756
|
-
z15.array(KeySchema).min(0).max(20).transform((keys) => ({
|
|
757
|
-
ProjectionType: ProjectionType.INCLUDE,
|
|
758
|
-
NonKeyAttributes: keys
|
|
759
|
-
}))
|
|
760
|
-
]);
|
|
761
1336
|
|
|
762
|
-
// src/plugins/table
|
|
1337
|
+
// src/plugins/table.ts
|
|
1338
|
+
var KeySchema = z9.string().min(1).max(255);
|
|
763
1339
|
var tablePlugin = definePlugin({
|
|
764
1340
|
name: "table",
|
|
765
|
-
schema:
|
|
766
|
-
stacks:
|
|
767
|
-
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(
|
|
768
1357
|
ResourceIdSchema,
|
|
769
|
-
|
|
1358
|
+
z9.object({
|
|
1359
|
+
/** Specifies the name of the partition / hash key that makes up the primary key for the table. */
|
|
770
1360
|
hash: KeySchema,
|
|
1361
|
+
/** Specifies the name of the range / sort key that makes up the primary key for the table. */
|
|
771
1362
|
sort: KeySchema.optional(),
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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. */
|
|
777
1396
|
hash: KeySchema,
|
|
1397
|
+
/** Specifies the name of the range / sort key that makes up the primary key for the global secondary index. */
|
|
778
1398
|
sort: KeySchema.optional(),
|
|
779
|
-
|
|
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")
|
|
780
1405
|
})).optional()
|
|
781
1406
|
}).refine((props) => {
|
|
782
1407
|
return (
|
|
@@ -793,140 +1418,321 @@ var tablePlugin = definePlugin({
|
|
|
793
1418
|
).optional()
|
|
794
1419
|
}).array()
|
|
795
1420
|
}),
|
|
796
|
-
onStack({ stack, stackConfig, bind }) {
|
|
797
|
-
Object.entries(stackConfig.tables || {})
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
const table = new Table(stack, toId("table", id), {
|
|
802
|
-
tableName: toName(stack, id),
|
|
803
|
-
partitionKey: buildKey(props.hash),
|
|
804
|
-
sortKey: props.sort ? buildKey(props.sort) : void 0,
|
|
805
|
-
billingMode: BillingMode.PAY_PER_REQUEST,
|
|
806
|
-
pointInTimeRecovery: props.pointInTimeRecovery,
|
|
807
|
-
timeToLiveAttribute: props.timeToLiveAttribute,
|
|
808
|
-
tableClass: props.class
|
|
809
|
-
});
|
|
810
|
-
Object.entries(props.indexes || {}).forEach(([indexName, entry]) => {
|
|
811
|
-
table.addGlobalSecondaryIndex({
|
|
812
|
-
indexName,
|
|
813
|
-
partitionKey: buildKey(entry.hash),
|
|
814
|
-
sortKey: entry.sort ? buildKey(entry.sort) : void 0,
|
|
815
|
-
...entry.projection
|
|
816
|
-
});
|
|
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
|
|
817
1426
|
});
|
|
1427
|
+
stack.add(table);
|
|
818
1428
|
bind((lambda) => {
|
|
819
|
-
table.
|
|
820
|
-
addResourceEnvironment(stack, "table", id, lambda);
|
|
1429
|
+
lambda.addPermissions(table.permissions);
|
|
821
1430
|
});
|
|
822
|
-
}
|
|
1431
|
+
}
|
|
823
1432
|
}
|
|
824
1433
|
});
|
|
825
1434
|
|
|
826
1435
|
// src/plugins/store.ts
|
|
827
|
-
import { z as
|
|
828
|
-
|
|
829
|
-
|
|
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
|
|
830
1478
|
var storePlugin = definePlugin({
|
|
831
1479
|
name: "store",
|
|
832
|
-
schema:
|
|
833
|
-
stacks:
|
|
834
|
-
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()
|
|
835
1489
|
}).array()
|
|
836
1490
|
}),
|
|
837
|
-
onStack({ stack, stackConfig, bind }) {
|
|
838
|
-
(stackConfig.stores || [])
|
|
839
|
-
const bucket = new
|
|
840
|
-
|
|
841
|
-
accessControl:
|
|
842
|
-
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"
|
|
843
1496
|
});
|
|
1497
|
+
stack.add(bucket);
|
|
844
1498
|
bind((lambda) => {
|
|
845
|
-
bucket.
|
|
1499
|
+
lambda.addPermissions(bucket.permissions);
|
|
846
1500
|
});
|
|
847
|
-
}
|
|
1501
|
+
}
|
|
848
1502
|
}
|
|
849
1503
|
});
|
|
850
1504
|
|
|
851
1505
|
// src/plugins/topic.ts
|
|
852
|
-
import { z as
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
|
856
1567
|
var topicPlugin = definePlugin({
|
|
857
1568
|
name: "topic",
|
|
858
|
-
schema:
|
|
859
|
-
stacks:
|
|
860
|
-
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()
|
|
861
1580
|
}).array()
|
|
862
1581
|
}),
|
|
863
|
-
|
|
864
|
-
const allTopicNames = config.stacks.map((
|
|
865
|
-
return Object.keys(
|
|
1582
|
+
onApp({ config, bootstrap: bootstrap2, bind }) {
|
|
1583
|
+
const allTopicNames = config.stacks.map((stack) => {
|
|
1584
|
+
return Object.keys(stack.topics || {});
|
|
866
1585
|
}).flat();
|
|
867
1586
|
const uniqueTopicNames = [...new Set(allTopicNames)];
|
|
868
|
-
|
|
869
|
-
new Topic(
|
|
870
|
-
|
|
871
|
-
|
|
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
|
+
]
|
|
872
1602
|
});
|
|
873
1603
|
});
|
|
874
1604
|
},
|
|
875
1605
|
onStack(ctx) {
|
|
876
|
-
const {
|
|
877
|
-
|
|
878
|
-
const lambda =
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
toId("topic", id),
|
|
882
|
-
Arn2.format({
|
|
883
|
-
arnFormat: ArnFormat.NO_RESOURCE_NAME,
|
|
884
|
-
service: "sns",
|
|
885
|
-
resource: `${config.name}-${id}`
|
|
886
|
-
}, stack)
|
|
887
|
-
);
|
|
888
|
-
lambda.addEventSource(new SnsEventSource(topic));
|
|
889
|
-
bind((lambda2) => {
|
|
890
|
-
addResourceEnvironment(stack, "topic", id, lambda2);
|
|
891
|
-
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`)
|
|
892
1611
|
});
|
|
893
|
-
|
|
894
|
-
}
|
|
1612
|
+
stack.add(lambda, source);
|
|
1613
|
+
}
|
|
895
1614
|
}
|
|
896
1615
|
});
|
|
897
1616
|
|
|
898
|
-
// src/plugins/
|
|
899
|
-
import { z as
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
stacks:
|
|
906
|
-
|
|
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()
|
|
907
1627
|
}).array()
|
|
908
1628
|
}),
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1629
|
+
onApp(ctx) {
|
|
1630
|
+
ctx.config.extend?.(ctx);
|
|
1631
|
+
},
|
|
1632
|
+
onStack(ctx) {
|
|
1633
|
+
ctx.stackConfig.extend?.(ctx);
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
// src/plugins/pubsub.ts
|
|
1638
|
+
import { z as z13 } from "zod";
|
|
1639
|
+
|
|
1640
|
+
// src/formation/resource/iot/topic-rule.ts
|
|
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
|
|
1687
|
+
var pubsubPlugin = definePlugin({
|
|
1688
|
+
name: "pubsub",
|
|
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 */
|
|
1708
|
+
consumer: FunctionSchema
|
|
1709
|
+
})).optional()
|
|
1710
|
+
}).array()
|
|
1711
|
+
}),
|
|
1712
|
+
onApp({ bind }) {
|
|
1713
|
+
bind((lambda) => {
|
|
1714
|
+
lambda.addPermissions({
|
|
1715
|
+
actions: ["iot:publish"],
|
|
1716
|
+
resources: ["*"]
|
|
920
1717
|
});
|
|
921
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
|
+
}
|
|
922
1731
|
}
|
|
923
1732
|
});
|
|
924
1733
|
|
|
925
|
-
// src/plugins/graphql
|
|
926
|
-
import { z as
|
|
927
|
-
import { AuthorizationType, CfnGraphQLApi, CfnGraphQLSchema, GraphqlApi, MappingTemplate } from "aws-cdk-lib/aws-appsync";
|
|
928
|
-
import { mergeTypeDefs } from "@graphql-tools/merge";
|
|
929
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
1734
|
+
// src/plugins/graphql.ts
|
|
1735
|
+
import { z as z14 } from "zod";
|
|
930
1736
|
|
|
931
1737
|
// src/util/array.ts
|
|
932
1738
|
var toArray = (value) => {
|
|
@@ -936,298 +1742,707 @@ var toArray = (value) => {
|
|
|
936
1742
|
return [value];
|
|
937
1743
|
};
|
|
938
1744
|
|
|
939
|
-
// src/plugins/graphql
|
|
940
|
-
import {
|
|
1745
|
+
// src/plugins/graphql.ts
|
|
1746
|
+
import { paramCase as paramCase3 } from "change-case";
|
|
1747
|
+
|
|
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
|
|
941
1886
|
import { print } from "graphql";
|
|
942
|
-
import {
|
|
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
|
+
};
|
|
943
1909
|
|
|
944
|
-
// src/
|
|
945
|
-
import {
|
|
946
|
-
var
|
|
947
|
-
|
|
948
|
-
|
|
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
|
+
};
|
|
949
2027
|
|
|
950
|
-
// src/
|
|
951
|
-
|
|
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"`);
|
|
952
2101
|
var graphqlPlugin = definePlugin({
|
|
953
2102
|
name: "graphql",
|
|
954
|
-
schema:
|
|
955
|
-
defaults:
|
|
956
|
-
graphql:
|
|
957
|
-
|
|
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({
|
|
958
2109
|
authorizer: FunctionSchema,
|
|
959
2110
|
ttl: DurationSchema.default("1 hour")
|
|
960
2111
|
}).optional(),
|
|
961
|
-
|
|
962
|
-
request: LocalFileSchema.optional(),
|
|
963
|
-
response: LocalFileSchema.optional()
|
|
964
|
-
}).optional()
|
|
2112
|
+
resolver: LocalFileSchema.optional()
|
|
965
2113
|
})).optional()
|
|
966
2114
|
}).default({}),
|
|
967
|
-
stacks:
|
|
968
|
-
graphql:
|
|
969
|
-
schema:
|
|
2115
|
+
stacks: z14.object({
|
|
2116
|
+
graphql: z14.record(ResourceIdSchema, z14.object({
|
|
2117
|
+
schema: z14.union([
|
|
970
2118
|
LocalFileSchema,
|
|
971
|
-
|
|
2119
|
+
z14.array(LocalFileSchema).min(1)
|
|
972
2120
|
]).optional(),
|
|
973
|
-
resolvers:
|
|
2121
|
+
resolvers: z14.record(ResolverFieldSchema, FunctionSchema).optional()
|
|
974
2122
|
})).optional()
|
|
975
2123
|
}).array()
|
|
976
2124
|
}),
|
|
977
|
-
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
984
|
-
list3.forEach((id) => {
|
|
985
|
-
const file = join4(assetDir, "graphql", config.name, id, "schema.graphql");
|
|
986
|
-
const authorization = config.defaults.graphql?.[id]?.authorization;
|
|
987
|
-
const authProps = {};
|
|
988
|
-
if (authorization) {
|
|
989
|
-
const authorizer = toFunction({ config, assets, stack }, `${id}-authorizer`, authorization.authorizer);
|
|
990
|
-
authProps.additionalAuthenticationProviders = [{
|
|
991
|
-
authenticationType: AuthorizationType.LAMBDA,
|
|
992
|
-
lambdaAuthorizerConfig: {
|
|
993
|
-
authorizerUri: authorizer.functionArn,
|
|
994
|
-
authorizerResultTtlInSeconds: authorization.ttl.toSeconds()
|
|
995
|
-
}
|
|
996
|
-
}];
|
|
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);
|
|
997
2131
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
resource: "schema",
|
|
1010
|
-
resourceName: id,
|
|
1011
|
-
async build() {
|
|
1012
|
-
const schemas = [];
|
|
1013
|
-
await Promise.all(Object.values(config.stacks).map(async (stackConfig) => {
|
|
1014
|
-
const schemaFiles = toArray(stackConfig.graphql?.[id].schema || []);
|
|
1015
|
-
await Promise.all(schemaFiles.map(async (schemaFile) => {
|
|
1016
|
-
const schema3 = await readFile2(schemaFile, "utf8");
|
|
1017
|
-
schemas.push(schema3);
|
|
1018
|
-
}));
|
|
1019
|
-
}));
|
|
1020
|
-
const schema2 = print(mergeTypeDefs(schemas));
|
|
1021
|
-
await mkdir2(dirname(file), { recursive: true });
|
|
1022
|
-
await writeFile2(file, schema2);
|
|
1023
|
-
new CfnGraphQLSchema(stack, toId("schema", id), {
|
|
1024
|
-
apiId: api.attrApiId,
|
|
1025
|
-
definition: schema2
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
2132
|
+
}
|
|
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)
|
|
1028
2143
|
});
|
|
1029
|
-
|
|
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
|
+
}
|
|
1030
2168
|
},
|
|
1031
2169
|
onStack(ctx) {
|
|
1032
|
-
const {
|
|
1033
|
-
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1036
|
-
const api = GraphqlApi.fromGraphqlApiAttributes(stack, toId("graphql", id), {
|
|
1037
|
-
graphqlApiId: Fn.importValue(toId("graphql", id))
|
|
1038
|
-
});
|
|
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 || {})) {
|
|
1039
2174
|
const [typeName, fieldName] = typeAndField.split(/[\s]+/g);
|
|
1040
|
-
const
|
|
1041
|
-
const lambda =
|
|
1042
|
-
const source =
|
|
1043
|
-
|
|
1044
|
-
});
|
|
1045
|
-
source.createResolver(toId("resolver", functionId), {
|
|
2175
|
+
const entryId = paramCase3(`${id}-${typeName}-${fieldName}`);
|
|
2176
|
+
const lambda = toLambdaFunction(ctx, entryId, functionProps);
|
|
2177
|
+
const source = new AppsyncEventSource(entryId, lambda, {
|
|
2178
|
+
apiId,
|
|
1046
2179
|
typeName,
|
|
1047
2180
|
fieldName,
|
|
1048
|
-
|
|
1049
|
-
responseMappingTemplate: defaults.mappingTemplate?.response ? MappingTemplate.fromFile(defaults.mappingTemplate.response) : MappingTemplate.lambdaResult()
|
|
2181
|
+
code: Code2.fromInline(entryId, defaultResolver)
|
|
1050
2182
|
});
|
|
1051
|
-
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
2183
|
+
stack.add(lambda, source);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
1054
2186
|
}
|
|
1055
2187
|
});
|
|
1056
2188
|
|
|
1057
|
-
// src/plugins/
|
|
1058
|
-
import { z as
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
var
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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");
|
|
2259
|
+
var domainPlugin = definePlugin({
|
|
2260
|
+
name: "domain",
|
|
2261
|
+
schema: z15.object({
|
|
2262
|
+
domains: z15.record(DomainNameSchema, z15.object({
|
|
2263
|
+
name: DomainNameSchema.optional(),
|
|
2264
|
+
type: z15.enum(["A", "AAAA", "CAA", "CNAME", "DS", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"]),
|
|
2265
|
+
ttl: DurationSchema,
|
|
2266
|
+
records: z15.string().array()
|
|
2267
|
+
}).array()).optional()
|
|
1071
2268
|
}),
|
|
1072
|
-
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
resources: ["*"]
|
|
1078
|
-
}));
|
|
1079
|
-
});
|
|
1080
|
-
return Object.entries(stackConfig.pubsub || {}).map(([id, props]) => {
|
|
1081
|
-
const lambda = toFunction(ctx, id, props.consumer);
|
|
1082
|
-
new CfnTopicRule(stack, toId("pubsub", id), {
|
|
1083
|
-
ruleName: toName(stack, id),
|
|
1084
|
-
topicRulePayload: {
|
|
1085
|
-
sql: props.sql,
|
|
1086
|
-
awsIotSqlVersion: props.sqlVersion,
|
|
1087
|
-
actions: [{
|
|
1088
|
-
lambda: {
|
|
1089
|
-
functionArn: lambda.functionArn
|
|
1090
|
-
}
|
|
1091
|
-
}]
|
|
1092
|
-
}
|
|
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}`]
|
|
1093
2274
|
});
|
|
1094
|
-
|
|
1095
|
-
|
|
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);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
1096
2285
|
}
|
|
1097
2286
|
});
|
|
1098
2287
|
|
|
1099
2288
|
// src/plugins/index.ts
|
|
1100
2289
|
var defaultPlugins = [
|
|
2290
|
+
extendPlugin,
|
|
1101
2291
|
functionPlugin,
|
|
1102
2292
|
cronPlugin,
|
|
1103
2293
|
queuePlugin,
|
|
1104
2294
|
tablePlugin,
|
|
1105
2295
|
storePlugin,
|
|
1106
2296
|
topicPlugin,
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
2297
|
+
pubsubPlugin,
|
|
2298
|
+
// searchPlugin,
|
|
2299
|
+
domainPlugin,
|
|
2300
|
+
graphqlPlugin
|
|
2301
|
+
// httpPlugin,
|
|
1110
2302
|
];
|
|
1111
2303
|
|
|
1112
|
-
// src/
|
|
1113
|
-
var
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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
|
+
// }
|
|
1132
2326
|
};
|
|
1133
2327
|
|
|
1134
|
-
// src/
|
|
1135
|
-
var
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
depends: config?.depends?.map((dep) => dep.name) || []
|
|
1150
|
-
}));
|
|
1151
|
-
const findChildren = (list4, parents, level) => {
|
|
1152
|
-
const children = [];
|
|
1153
|
-
const rests = [];
|
|
1154
|
-
for (const item of list4) {
|
|
1155
|
-
const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
|
|
1156
|
-
if (isChild) {
|
|
1157
|
-
children.push(item);
|
|
1158
|
-
} else {
|
|
1159
|
-
rests.push(item);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
if (!rests.length) {
|
|
1163
|
-
return children.map(({ stack }) => ({
|
|
1164
|
-
stack,
|
|
1165
|
-
level,
|
|
1166
|
-
children: []
|
|
1167
|
-
}));
|
|
1168
|
-
}
|
|
1169
|
-
return children.map(({ stack }) => {
|
|
1170
|
-
return {
|
|
1171
|
-
stack,
|
|
1172
|
-
level,
|
|
1173
|
-
children: findChildren(rests, [...parents, stack.artifactId], level + 1)
|
|
1174
|
-
};
|
|
1175
|
-
});
|
|
1176
|
-
};
|
|
1177
|
-
return findChildren(list3, [], startingLevel);
|
|
1178
|
-
};
|
|
1179
|
-
var createDeploymentLine = (stacks) => {
|
|
1180
|
-
const flat = flattenDependencyTree(stacks);
|
|
1181
|
-
const line = [];
|
|
1182
|
-
flat.forEach((node) => {
|
|
1183
|
-
const level = node.level;
|
|
1184
|
-
if (!line[level]) {
|
|
1185
|
-
line[level] = [];
|
|
1186
|
-
}
|
|
1187
|
-
line[level].push(node.stack);
|
|
1188
|
-
});
|
|
1189
|
-
return line;
|
|
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
|
+
}
|
|
1190
2343
|
};
|
|
1191
2344
|
|
|
1192
|
-
// src/
|
|
1193
|
-
var
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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}`);
|
|
1199
2423
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1217
|
-
|
|
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);
|
|
2440
|
+
}
|
|
2441
|
+
return crossRegionExports.getAtt(name);
|
|
2442
|
+
};
|
|
1218
2443
|
};
|
|
1219
2444
|
|
|
1220
2445
|
// src/app.ts
|
|
1221
|
-
var makeApp = (config) => {
|
|
1222
|
-
return new App4({
|
|
1223
|
-
outdir: assemblyDir,
|
|
1224
|
-
defaultStackSynthesizer: new DefaultStackSynthesizer({
|
|
1225
|
-
fileAssetsBucketName: assetBucketName(config),
|
|
1226
|
-
fileAssetPublishingRoleArn: "",
|
|
1227
|
-
generateBootstrapVersionRule: false
|
|
1228
|
-
})
|
|
1229
|
-
});
|
|
1230
|
-
};
|
|
1231
2446
|
var getAllDepends = (filters) => {
|
|
1232
2447
|
const list3 = [];
|
|
1233
2448
|
const walk = (deps) => {
|
|
@@ -1240,111 +2455,76 @@ var getAllDepends = (filters) => {
|
|
|
1240
2455
|
return list3;
|
|
1241
2456
|
};
|
|
1242
2457
|
var toApp = async (config, filters) => {
|
|
1243
|
-
const
|
|
1244
|
-
const app = makeApp(config);
|
|
2458
|
+
const app = new App(config.name);
|
|
1245
2459
|
const stacks = [];
|
|
1246
2460
|
const plugins = [
|
|
1247
2461
|
...defaultPlugins,
|
|
1248
2462
|
...config.plugins || []
|
|
1249
2463
|
];
|
|
1250
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);
|
|
1251
2469
|
debug("Run plugin onApp listeners");
|
|
1252
|
-
|
|
1253
|
-
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
|
+
}
|
|
1254
2483
|
debug("Stack filters:", filters.map((filter) => style.info(filter)).join(", "));
|
|
1255
2484
|
const filterdStacks = filters.length === 0 ? config.stacks : getAllDepends(
|
|
1256
2485
|
// config.stacks,
|
|
1257
2486
|
config.stacks.filter((stack) => filters.includes(stack.name))
|
|
1258
2487
|
);
|
|
1259
2488
|
for (const stackConfig of filterdStacks) {
|
|
1260
|
-
const { stack
|
|
2489
|
+
const { stack } = toStack({
|
|
1261
2490
|
config,
|
|
1262
2491
|
stackConfig,
|
|
1263
|
-
|
|
2492
|
+
bootstrap: bootstrap2,
|
|
2493
|
+
usEastBootstrap,
|
|
1264
2494
|
plugins,
|
|
1265
2495
|
app
|
|
1266
2496
|
});
|
|
2497
|
+
app.add(stack);
|
|
1267
2498
|
stacks.push({ stack, config: stackConfig });
|
|
1268
|
-
bindings.forEach((cb) => bootstrap2.functions.forEach(cb));
|
|
1269
2499
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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) {
|
|
1274
2514
|
dependencyTree = [{
|
|
1275
|
-
stack:
|
|
1276
|
-
|
|
1277
|
-
children: createDependencyTree(stacks, 1)
|
|
2515
|
+
stack: usEastBootstrap,
|
|
2516
|
+
children: dependencyTree
|
|
1278
2517
|
}];
|
|
1279
2518
|
}
|
|
1280
2519
|
return {
|
|
1281
2520
|
app,
|
|
1282
|
-
assets,
|
|
1283
2521
|
plugins,
|
|
1284
|
-
stackNames: filterdStacks.map((stack) => stack.name),
|
|
1285
2522
|
dependencyTree
|
|
1286
2523
|
};
|
|
1287
2524
|
};
|
|
1288
2525
|
|
|
1289
|
-
// src/cli/ui/layout/basic.ts
|
|
1290
|
-
var br = () => {
|
|
1291
|
-
return "\n";
|
|
1292
|
-
};
|
|
1293
|
-
var hr = () => {
|
|
1294
|
-
return (term) => {
|
|
1295
|
-
term.out.write([
|
|
1296
|
-
style.placeholder("\u2500".repeat(term.out.width())),
|
|
1297
|
-
br()
|
|
1298
|
-
]);
|
|
1299
|
-
};
|
|
1300
|
-
};
|
|
1301
|
-
|
|
1302
|
-
// src/cli/ui/layout/logs.ts
|
|
1303
|
-
import wrapAnsi from "wrap-ansi";
|
|
1304
|
-
var previous = /* @__PURE__ */ new Date();
|
|
1305
|
-
var logs = () => {
|
|
1306
|
-
if (!process.env.VERBOSE) {
|
|
1307
|
-
return [];
|
|
1308
|
-
}
|
|
1309
|
-
const logs2 = flushDebug();
|
|
1310
|
-
return (term) => {
|
|
1311
|
-
term.out.write([
|
|
1312
|
-
hr(),
|
|
1313
|
-
br(),
|
|
1314
|
-
" ".repeat(3),
|
|
1315
|
-
style.label("Debug Logs:"),
|
|
1316
|
-
br(),
|
|
1317
|
-
br(),
|
|
1318
|
-
logs2.map((log) => {
|
|
1319
|
-
const diff = log.date.getTime() - previous.getTime();
|
|
1320
|
-
const time = `+${diff}`.padStart(8);
|
|
1321
|
-
previous = log.date;
|
|
1322
|
-
return wrapAnsi([
|
|
1323
|
-
style.attr(`${time}${style.attr.dim("ms")}`),
|
|
1324
|
-
" [ ",
|
|
1325
|
-
log.type,
|
|
1326
|
-
" ] ",
|
|
1327
|
-
log.message,
|
|
1328
|
-
br(),
|
|
1329
|
-
log.type === "error" ? br() : ""
|
|
1330
|
-
].join(""), term.out.width(), { hard: true, trim: false });
|
|
1331
|
-
}),
|
|
1332
|
-
br(),
|
|
1333
|
-
hr()
|
|
1334
|
-
]);
|
|
1335
|
-
};
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
// src/cli/ui/layout/footer.ts
|
|
1339
|
-
var footer = () => {
|
|
1340
|
-
return [
|
|
1341
|
-
br(),
|
|
1342
|
-
logs()
|
|
1343
|
-
];
|
|
1344
|
-
};
|
|
1345
|
-
|
|
1346
2526
|
// src/config.ts
|
|
1347
|
-
import { join as
|
|
2527
|
+
import { join as join3 } from "path";
|
|
1348
2528
|
|
|
1349
2529
|
// src/util/account.ts
|
|
1350
2530
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -1363,17 +2543,17 @@ var getCredentials = (profile) => {
|
|
|
1363
2543
|
};
|
|
1364
2544
|
|
|
1365
2545
|
// src/schema/app.ts
|
|
1366
|
-
import { z as
|
|
2546
|
+
import { z as z19 } from "zod";
|
|
1367
2547
|
|
|
1368
2548
|
// src/schema/stack.ts
|
|
1369
|
-
import { z as
|
|
1370
|
-
var StackSchema =
|
|
2549
|
+
import { z as z16 } from "zod";
|
|
2550
|
+
var StackSchema = z16.object({
|
|
1371
2551
|
name: ResourceIdSchema,
|
|
1372
|
-
depends:
|
|
2552
|
+
depends: z16.array(z16.lazy(() => StackSchema)).optional()
|
|
1373
2553
|
});
|
|
1374
2554
|
|
|
1375
2555
|
// src/schema/region.ts
|
|
1376
|
-
import { z as
|
|
2556
|
+
import { z as z17 } from "zod";
|
|
1377
2557
|
var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
|
|
1378
2558
|
var AF = ["af-south-1"];
|
|
1379
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"];
|
|
@@ -1390,35 +2570,48 @@ var regions = [
|
|
|
1390
2570
|
...ME,
|
|
1391
2571
|
...SA
|
|
1392
2572
|
];
|
|
1393
|
-
var RegionSchema =
|
|
2573
|
+
var RegionSchema = z17.enum(regions);
|
|
1394
2574
|
|
|
1395
2575
|
// src/schema/plugin.ts
|
|
1396
|
-
import { z as
|
|
1397
|
-
var PluginSchema =
|
|
1398
|
-
name:
|
|
1399
|
-
schema:
|
|
2576
|
+
import { z as z18 } from "zod";
|
|
2577
|
+
var PluginSchema = z18.object({
|
|
2578
|
+
name: z18.string(),
|
|
2579
|
+
schema: z18.custom().optional(),
|
|
1400
2580
|
// depends: z.array(z.lazy(() => PluginSchema)).optional(),
|
|
1401
|
-
onBootstrap:
|
|
1402
|
-
onStack:
|
|
1403
|
-
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()
|
|
1404
2584
|
// bind: z.function().optional(),
|
|
1405
2585
|
});
|
|
1406
2586
|
|
|
1407
2587
|
// src/schema/app.ts
|
|
1408
|
-
var AppSchema =
|
|
2588
|
+
var AppSchema = z19.object({
|
|
1409
2589
|
name: ResourceIdSchema,
|
|
1410
2590
|
region: RegionSchema,
|
|
1411
|
-
profile:
|
|
1412
|
-
stage:
|
|
1413
|
-
defaults:
|
|
1414
|
-
stacks:
|
|
1415
|
-
|
|
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()
|
|
1416
2599
|
});
|
|
1417
2600
|
|
|
1418
2601
|
// src/util/import.ts
|
|
1419
2602
|
import { transformFile } from "@swc/core";
|
|
1420
|
-
import { dirname
|
|
1421
|
-
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
|
|
1422
2615
|
var resolveFileNameExtension = async (path) => {
|
|
1423
2616
|
const options = [
|
|
1424
2617
|
"",
|
|
@@ -1428,7 +2621,7 @@ var resolveFileNameExtension = async (path) => {
|
|
|
1428
2621
|
"/index.js"
|
|
1429
2622
|
];
|
|
1430
2623
|
for (const option of options) {
|
|
1431
|
-
const file = path + option;
|
|
2624
|
+
const file = path.replace(/\.js$/, "") + option;
|
|
1432
2625
|
let stat;
|
|
1433
2626
|
try {
|
|
1434
2627
|
stat = await lstat(file);
|
|
@@ -1442,45 +2635,42 @@ var resolveFileNameExtension = async (path) => {
|
|
|
1442
2635
|
throw new Error(`Failed to load file: ${path}`);
|
|
1443
2636
|
};
|
|
1444
2637
|
var resolveDir = (path) => {
|
|
1445
|
-
return
|
|
2638
|
+
return dirname(path).replace(rootDir + "/", "");
|
|
1446
2639
|
};
|
|
1447
2640
|
var importFile = async (path) => {
|
|
1448
2641
|
const load = async (file) => {
|
|
2642
|
+
debug("Load file", file);
|
|
1449
2643
|
let { code: code2 } = await transformFile(file, {
|
|
1450
2644
|
isModule: true
|
|
1451
2645
|
});
|
|
1452
|
-
const path2 =
|
|
2646
|
+
const path2 = dirname(file);
|
|
1453
2647
|
const dir = resolveDir(file);
|
|
1454
2648
|
code2 = code2.replaceAll("__dirname", `"${dir}"`);
|
|
1455
|
-
const matches = code2.match(/import\s*{\s*[a-z0-9\_]+\s*}\s*from\s*('|")(
|
|
2649
|
+
const matches = code2.match(/(import|export)\s*{\s*[a-z0-9\_\,\s\*]+\s*}\s*from\s*('|")(\.\.?[\/a-z0-9\_\-\.]+)('|");?/ig);
|
|
1456
2650
|
if (!matches)
|
|
1457
2651
|
return code2;
|
|
1458
2652
|
await Promise.all(matches?.map(async (match) => {
|
|
1459
|
-
const parts = /('|")(
|
|
2653
|
+
const parts = /('|")(\.\.?[\/a-z0-9\_\-\.]+)('|")/ig.exec(match);
|
|
1460
2654
|
const from = parts[2];
|
|
1461
|
-
const file2 = await resolveFileNameExtension(
|
|
2655
|
+
const file2 = await resolveFileNameExtension(join2(path2, from));
|
|
1462
2656
|
const result = await load(file2);
|
|
1463
2657
|
code2 = code2.replace(match, result);
|
|
1464
2658
|
}));
|
|
1465
2659
|
return code2;
|
|
1466
2660
|
};
|
|
1467
2661
|
const code = await load(path);
|
|
1468
|
-
const outputFile =
|
|
1469
|
-
await
|
|
1470
|
-
await
|
|
2662
|
+
const outputFile = join2(outDir, "config.js");
|
|
2663
|
+
await mkdir(outDir, { recursive: true });
|
|
2664
|
+
await writeFile(outputFile, code);
|
|
1471
2665
|
return import(outputFile);
|
|
1472
2666
|
};
|
|
1473
2667
|
|
|
1474
2668
|
// src/config.ts
|
|
1475
2669
|
var importConfig = async (options) => {
|
|
1476
2670
|
debug("Import config file");
|
|
1477
|
-
const fileName =
|
|
2671
|
+
const fileName = join3(process.cwd(), options.configFile || "awsless.config.ts");
|
|
1478
2672
|
const module = await importFile(fileName);
|
|
1479
|
-
const appConfig = typeof module.default === "function" ? await module.default(
|
|
1480
|
-
profile: options.profile,
|
|
1481
|
-
region: options.region,
|
|
1482
|
-
stage: options.stage
|
|
1483
|
-
}) : module.default;
|
|
2673
|
+
const appConfig = typeof module.default === "function" ? await module.default(options) : module.default;
|
|
1484
2674
|
debug("Validate config file");
|
|
1485
2675
|
const plugins = [
|
|
1486
2676
|
...defaultPlugins,
|
|
@@ -1505,6 +2695,19 @@ var importConfig = async (options) => {
|
|
|
1505
2695
|
};
|
|
1506
2696
|
};
|
|
1507
2697
|
|
|
2698
|
+
// src/cli/ui/layout/basic.ts
|
|
2699
|
+
var br = () => {
|
|
2700
|
+
return "\n";
|
|
2701
|
+
};
|
|
2702
|
+
var hr = () => {
|
|
2703
|
+
return (term) => {
|
|
2704
|
+
term.out.write([
|
|
2705
|
+
style.placeholder("\u2500".repeat(term.out.width())),
|
|
2706
|
+
br()
|
|
2707
|
+
]);
|
|
2708
|
+
};
|
|
2709
|
+
};
|
|
2710
|
+
|
|
1508
2711
|
// src/cli/ui/layout/list.ts
|
|
1509
2712
|
var list = (data) => {
|
|
1510
2713
|
const padding = 3;
|
|
@@ -1512,26 +2715,26 @@ var list = (data) => {
|
|
|
1512
2715
|
const size = Object.keys(data).reduce((total, name) => {
|
|
1513
2716
|
return name.length > total ? name.length : total;
|
|
1514
2717
|
}, 0);
|
|
1515
|
-
return
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2718
|
+
return (term) => {
|
|
2719
|
+
term.out.gap();
|
|
2720
|
+
term.out.write(Object.entries(data).map(([name, value]) => [
|
|
2721
|
+
" ".repeat(padding),
|
|
2722
|
+
style.label((name + ":").padEnd(size + gap + 1)),
|
|
2723
|
+
value,
|
|
2724
|
+
br()
|
|
2725
|
+
]));
|
|
2726
|
+
term.out.gap();
|
|
2727
|
+
};
|
|
1521
2728
|
};
|
|
1522
2729
|
|
|
1523
2730
|
// src/cli/ui/layout/header.ts
|
|
1524
2731
|
var header = (config) => {
|
|
1525
|
-
return
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
Profile: config.profile
|
|
1532
|
-
}),
|
|
1533
|
-
br()
|
|
1534
|
-
];
|
|
2732
|
+
return list({
|
|
2733
|
+
App: config.name,
|
|
2734
|
+
Stage: config.stage,
|
|
2735
|
+
Region: config.region,
|
|
2736
|
+
Profile: config.profile
|
|
2737
|
+
});
|
|
1535
2738
|
};
|
|
1536
2739
|
|
|
1537
2740
|
// src/util/timer.ts
|
|
@@ -1556,7 +2759,7 @@ var Signal = class {
|
|
|
1556
2759
|
}
|
|
1557
2760
|
set(value) {
|
|
1558
2761
|
this.value = value;
|
|
1559
|
-
this.subs.forEach((
|
|
2762
|
+
this.subs.forEach((sub2) => sub2(value));
|
|
1560
2763
|
}
|
|
1561
2764
|
update(cb) {
|
|
1562
2765
|
this.set(cb(this.value));
|
|
@@ -1598,16 +2801,16 @@ var createSpinner = () => {
|
|
|
1598
2801
|
};
|
|
1599
2802
|
|
|
1600
2803
|
// src/cli/ui/layout/dialog.ts
|
|
1601
|
-
import
|
|
2804
|
+
import wrapAnsi from "wrap-ansi";
|
|
1602
2805
|
var dialog = (type, lines) => {
|
|
1603
2806
|
const padding = 3;
|
|
1604
2807
|
const icon = style[type](symbol[type].padEnd(padding));
|
|
1605
2808
|
return (term) => {
|
|
1606
2809
|
term.out.write(lines.map((line, i) => {
|
|
1607
2810
|
if (i === 0) {
|
|
1608
|
-
return icon +
|
|
2811
|
+
return icon + wrapAnsi(line, term.out.width(), { hard: true });
|
|
1609
2812
|
}
|
|
1610
|
-
return
|
|
2813
|
+
return wrapAnsi(" ".repeat(padding) + line, term.out.width(), { hard: true });
|
|
1611
2814
|
}).join(br()) + br());
|
|
1612
2815
|
};
|
|
1613
2816
|
};
|
|
@@ -1749,6 +2952,7 @@ var Renderer = class {
|
|
|
1749
2952
|
fragments = [];
|
|
1750
2953
|
unsubs = [];
|
|
1751
2954
|
timeout;
|
|
2955
|
+
flushing = false;
|
|
1752
2956
|
screen = [];
|
|
1753
2957
|
width() {
|
|
1754
2958
|
return this.output.columns;
|
|
@@ -1768,14 +2972,61 @@ var Renderer = class {
|
|
|
1768
2972
|
this.update();
|
|
1769
2973
|
return fragment;
|
|
1770
2974
|
}
|
|
2975
|
+
gap() {
|
|
2976
|
+
const walk = (fragment) => {
|
|
2977
|
+
if (typeof fragment === "string") {
|
|
2978
|
+
return fragment;
|
|
2979
|
+
}
|
|
2980
|
+
if (Array.isArray(fragment)) {
|
|
2981
|
+
return fragment.map(walk).join("");
|
|
2982
|
+
}
|
|
2983
|
+
return walk(fragment.get());
|
|
2984
|
+
};
|
|
2985
|
+
const end = walk(this.fragments.slice(-2));
|
|
2986
|
+
if (end.endsWith("\n\n")) {
|
|
2987
|
+
} else if (end.endsWith("\n")) {
|
|
2988
|
+
this.fragments.push("\n");
|
|
2989
|
+
} else {
|
|
2990
|
+
this.fragments.push("\n\n");
|
|
2991
|
+
}
|
|
2992
|
+
this.update();
|
|
2993
|
+
}
|
|
1771
2994
|
update() {
|
|
1772
2995
|
clearTimeout(this.timeout);
|
|
1773
2996
|
this.timeout = setTimeout(() => {
|
|
1774
2997
|
this.flush();
|
|
1775
2998
|
}, 0);
|
|
1776
2999
|
}
|
|
1777
|
-
|
|
3000
|
+
async end() {
|
|
3001
|
+
this.gap();
|
|
3002
|
+
await this.flush();
|
|
3003
|
+
clearTimeout(this.timeout);
|
|
3004
|
+
this.unsubs.forEach((unsub) => unsub());
|
|
3005
|
+
this.unsubs = [];
|
|
3006
|
+
const y = this.screen.length - 1;
|
|
3007
|
+
await this.setCursor(0, y);
|
|
3008
|
+
}
|
|
3009
|
+
setCursor(x, y) {
|
|
3010
|
+
return new Promise((resolve) => {
|
|
3011
|
+
this.output.cursorTo?.(x, y, () => resolve(void 0));
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
writeString(value) {
|
|
3015
|
+
return new Promise((resolve) => {
|
|
3016
|
+
this.output.write?.(value, () => resolve(void 0));
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
clearLine() {
|
|
3020
|
+
return new Promise((resolve) => {
|
|
3021
|
+
this.output.clearLine?.(1, () => resolve(void 0));
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
async flush() {
|
|
1778
3025
|
clearTimeout(this.timeout);
|
|
3026
|
+
if (this.flushing) {
|
|
3027
|
+
this.update();
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
1779
3030
|
const walk = (fragment) => {
|
|
1780
3031
|
if (typeof fragment === "string") {
|
|
1781
3032
|
return fragment;
|
|
@@ -1791,34 +3042,40 @@ var Renderer = class {
|
|
|
1791
3042
|
this.unsubs.forEach((unsub) => unsub());
|
|
1792
3043
|
this.unsubs = [];
|
|
1793
3044
|
const screen = walk(this.fragments).split("\n");
|
|
3045
|
+
const height = this.height();
|
|
1794
3046
|
const oldSize = this.screen.length;
|
|
1795
3047
|
const newSize = screen.length;
|
|
1796
3048
|
const size = Math.max(oldSize, newSize);
|
|
1797
|
-
const height = this.height();
|
|
1798
3049
|
const start = Math.max(oldSize - height, 0);
|
|
3050
|
+
this.flushing = true;
|
|
1799
3051
|
for (let y = start; y < size; y++) {
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
3052
|
+
const newLine = screen[y];
|
|
3053
|
+
const oldLine = this.screen[y];
|
|
3054
|
+
if (newLine !== oldLine) {
|
|
3055
|
+
if (y >= oldSize && y !== 0) {
|
|
3056
|
+
const p = y - start - 1;
|
|
3057
|
+
const x = screen[y - 1]?.length || 0;
|
|
3058
|
+
await this.setCursor(x, p);
|
|
3059
|
+
await this.writeString("\n" + newLine);
|
|
1806
3060
|
} else {
|
|
1807
|
-
this.
|
|
1808
|
-
this.
|
|
3061
|
+
await this.setCursor(0, y - start);
|
|
3062
|
+
await this.writeString(newLine);
|
|
3063
|
+
await this.clearLine();
|
|
1809
3064
|
}
|
|
1810
|
-
this.output.clearLine?.(1);
|
|
1811
3065
|
}
|
|
1812
3066
|
}
|
|
1813
3067
|
this.screen = screen;
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
3068
|
+
this.flushing = false;
|
|
3069
|
+
}
|
|
3070
|
+
async clear() {
|
|
3071
|
+
await this.setCursor(0, 0);
|
|
3072
|
+
await this.writeString("\n".repeat(this.height()));
|
|
3073
|
+
await this.setCursor(0, 0);
|
|
3074
|
+
if (this.output.clearScreenDown) {
|
|
3075
|
+
await new Promise((resolve) => {
|
|
3076
|
+
this.output.clearScreenDown(() => resolve(void 0));
|
|
3077
|
+
});
|
|
1819
3078
|
}
|
|
1820
|
-
this.output.cursorTo?.(0, 0);
|
|
1821
|
-
this.output.clearScreenDown?.();
|
|
1822
3079
|
}
|
|
1823
3080
|
};
|
|
1824
3081
|
|
|
@@ -1834,22 +3091,62 @@ var logo = () => {
|
|
|
1834
3091
|
return [
|
|
1835
3092
|
style.warning("\u26A1\uFE0F "),
|
|
1836
3093
|
style.primary("AWS"),
|
|
1837
|
-
style.primary.dim("LESS")
|
|
1838
|
-
br()
|
|
3094
|
+
style.primary.dim("LESS")
|
|
1839
3095
|
];
|
|
1840
3096
|
};
|
|
1841
3097
|
|
|
3098
|
+
// src/cli/ui/layout/logs.ts
|
|
3099
|
+
import wrapAnsi2 from "wrap-ansi";
|
|
3100
|
+
var previous = /* @__PURE__ */ new Date();
|
|
3101
|
+
var logs = () => {
|
|
3102
|
+
if (!process.env.VERBOSE) {
|
|
3103
|
+
return [];
|
|
3104
|
+
}
|
|
3105
|
+
const logs2 = flushDebug();
|
|
3106
|
+
return (term) => {
|
|
3107
|
+
term.out.gap();
|
|
3108
|
+
term.out.write([
|
|
3109
|
+
hr(),
|
|
3110
|
+
br(),
|
|
3111
|
+
" ".repeat(3),
|
|
3112
|
+
style.label("Debug Logs:"),
|
|
3113
|
+
br(),
|
|
3114
|
+
br(),
|
|
3115
|
+
logs2.map((log) => {
|
|
3116
|
+
const diff = log.date.getTime() - previous.getTime();
|
|
3117
|
+
const time = `+${diff}`.padStart(8);
|
|
3118
|
+
previous = log.date;
|
|
3119
|
+
return wrapAnsi2([
|
|
3120
|
+
style.attr(`${time}${style.attr.dim("ms")}`),
|
|
3121
|
+
" [ ",
|
|
3122
|
+
log.type,
|
|
3123
|
+
" ] ",
|
|
3124
|
+
log.message,
|
|
3125
|
+
br(),
|
|
3126
|
+
log.type === "error" ? br() : ""
|
|
3127
|
+
].join(""), term.out.width(), { hard: true, trim: false });
|
|
3128
|
+
}),
|
|
3129
|
+
br(),
|
|
3130
|
+
hr()
|
|
3131
|
+
]);
|
|
3132
|
+
};
|
|
3133
|
+
};
|
|
3134
|
+
|
|
1842
3135
|
// src/cli/ui/layout/layout.ts
|
|
1843
3136
|
var layout = async (cb) => {
|
|
1844
3137
|
const term = createTerminal();
|
|
1845
|
-
term.out.clear();
|
|
3138
|
+
await term.out.clear();
|
|
3139
|
+
term.out.write(br());
|
|
1846
3140
|
term.out.write(logo());
|
|
3141
|
+
term.out.gap();
|
|
1847
3142
|
try {
|
|
1848
3143
|
const options = program.optsWithGlobals();
|
|
1849
3144
|
const config = await importConfig(options);
|
|
1850
3145
|
term.out.write(header(config));
|
|
3146
|
+
term.out.gap();
|
|
1851
3147
|
await cb(config, term.out.write.bind(term.out), term);
|
|
1852
3148
|
} catch (error) {
|
|
3149
|
+
term.out.gap();
|
|
1853
3150
|
if (error instanceof Error) {
|
|
1854
3151
|
term.out.write(dialog("error", [error.message]));
|
|
1855
3152
|
} else if (typeof error === "string") {
|
|
@@ -1860,7 +3157,9 @@ var layout = async (cb) => {
|
|
|
1860
3157
|
debugError(error);
|
|
1861
3158
|
} finally {
|
|
1862
3159
|
debug("Exit");
|
|
1863
|
-
term.out.
|
|
3160
|
+
term.out.gap();
|
|
3161
|
+
term.out.write(logs());
|
|
3162
|
+
await term.out.end();
|
|
1864
3163
|
term.in.unref();
|
|
1865
3164
|
setTimeout(() => {
|
|
1866
3165
|
process.exit(0);
|
|
@@ -1868,6 +3167,9 @@ var layout = async (cb) => {
|
|
|
1868
3167
|
}
|
|
1869
3168
|
};
|
|
1870
3169
|
|
|
3170
|
+
// src/cli/ui/complex/builder.ts
|
|
3171
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
3172
|
+
|
|
1871
3173
|
// src/cli/ui/layout/flex-line.ts
|
|
1872
3174
|
var stripEscapeCode = (str) => {
|
|
1873
3175
|
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
@@ -1888,34 +3190,52 @@ var flexLine = (term, left, right, reserveSpace = 0) => {
|
|
|
1888
3190
|
]);
|
|
1889
3191
|
};
|
|
1890
3192
|
|
|
1891
|
-
// src/cli/ui/complex/
|
|
1892
|
-
|
|
3193
|
+
// src/cli/ui/complex/builder.ts
|
|
3194
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
3195
|
+
var assetBuilder = (app) => {
|
|
1893
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
|
+
}
|
|
1894
3210
|
const done = term.out.write(loadingDialog("Building stack assets..."));
|
|
1895
|
-
const groups = new Signal([
|
|
3211
|
+
const groups = new Signal([""]);
|
|
3212
|
+
term.out.gap();
|
|
1896
3213
|
term.out.write(groups);
|
|
1897
|
-
const stackNameSize = Math.max(...
|
|
1898
|
-
const
|
|
1899
|
-
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) => {
|
|
1900
3217
|
const group = new Signal([]);
|
|
1901
3218
|
groups.update((groups2) => [...groups2, group]);
|
|
1902
|
-
await Promise.all(
|
|
3219
|
+
await Promise.all([...stack.assets].map(async (asset) => {
|
|
3220
|
+
if (!asset.build) {
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
1903
3223
|
const [icon, stop] = createSpinner();
|
|
1904
3224
|
const details = new Signal({});
|
|
1905
3225
|
const line = flexLine(term, [
|
|
1906
3226
|
icon,
|
|
1907
3227
|
" ",
|
|
1908
|
-
style.label(
|
|
1909
|
-
" ".repeat(stackNameSize -
|
|
3228
|
+
style.label(stack.name),
|
|
3229
|
+
" ".repeat(stackNameSize - stack.name.length),
|
|
1910
3230
|
" ",
|
|
1911
3231
|
style.placeholder(symbol.pointerSmall),
|
|
1912
3232
|
" ",
|
|
1913
|
-
style.warning(asset.
|
|
1914
|
-
" ".repeat(
|
|
3233
|
+
style.warning(asset.type),
|
|
3234
|
+
" ".repeat(assetTypeSize - asset.type.length),
|
|
1915
3235
|
" ",
|
|
1916
3236
|
style.placeholder(symbol.pointerSmall),
|
|
1917
3237
|
" ",
|
|
1918
|
-
style.info(asset.
|
|
3238
|
+
style.info(asset.id),
|
|
1919
3239
|
" "
|
|
1920
3240
|
], [
|
|
1921
3241
|
" ",
|
|
@@ -1928,7 +3248,14 @@ var assetBuilder = (assets) => {
|
|
|
1928
3248
|
]);
|
|
1929
3249
|
group.update((group2) => [...group2, line]);
|
|
1930
3250
|
const timer = createTimer();
|
|
1931
|
-
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
|
+
});
|
|
1932
3259
|
details.set({
|
|
1933
3260
|
...data,
|
|
1934
3261
|
time: timer()
|
|
@@ -1938,15 +3265,16 @@ var assetBuilder = (assets) => {
|
|
|
1938
3265
|
}));
|
|
1939
3266
|
}));
|
|
1940
3267
|
done("Done building stack assets");
|
|
3268
|
+
term.out.gap();
|
|
1941
3269
|
};
|
|
1942
3270
|
};
|
|
1943
3271
|
|
|
1944
3272
|
// src/util/cleanup.ts
|
|
1945
|
-
import { mkdir as
|
|
3273
|
+
import { mkdir as mkdir3, rm } from "fs/promises";
|
|
1946
3274
|
var cleanUp = async () => {
|
|
1947
|
-
debug("Clean up
|
|
3275
|
+
debug("Clean up template, cache, and asset files");
|
|
1948
3276
|
const paths = [
|
|
1949
|
-
|
|
3277
|
+
templateDir,
|
|
1950
3278
|
assetDir,
|
|
1951
3279
|
cacheDir
|
|
1952
3280
|
];
|
|
@@ -1955,113 +3283,181 @@ var cleanUp = async () => {
|
|
|
1955
3283
|
force: true,
|
|
1956
3284
|
maxRetries: 2
|
|
1957
3285
|
})));
|
|
1958
|
-
await Promise.all(paths.map((path) =>
|
|
3286
|
+
await Promise.all(paths.map((path) => mkdir3(path, {
|
|
1959
3287
|
recursive: true
|
|
1960
3288
|
})));
|
|
1961
3289
|
};
|
|
1962
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
|
+
|
|
1963
3308
|
// src/cli/command/build.ts
|
|
1964
3309
|
var build = (program2) => {
|
|
1965
3310
|
program2.command("build").argument("[stack...]", "Optionally filter stacks to build").description("Build your app").action(async (filters) => {
|
|
1966
3311
|
await layout(async (config, write) => {
|
|
1967
|
-
const { app
|
|
3312
|
+
const { app } = await toApp(config, filters);
|
|
1968
3313
|
await cleanUp();
|
|
1969
|
-
await write(assetBuilder(
|
|
1970
|
-
app
|
|
3314
|
+
await write(assetBuilder(app));
|
|
3315
|
+
await write(templateBuilder(app));
|
|
1971
3316
|
});
|
|
1972
3317
|
});
|
|
1973
3318
|
};
|
|
1974
3319
|
|
|
1975
|
-
// 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
|
|
1976
3348
|
import { CloudFormationClient, CreateStackCommand, DeleteStackCommand, DescribeStacksCommand, GetTemplateCommand, OnFailure, TemplateStage, UpdateStackCommand, ValidateTemplateCommand, waitUntilStackCreateComplete, waitUntilStackDeleteComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
|
|
1977
|
-
import { S3Client
|
|
3349
|
+
import { S3Client, PutObjectCommand, ObjectCannedACL, StorageClass } from "@aws-sdk/client-s3";
|
|
3350
|
+
import { paramCase as paramCase4 } from "change-case";
|
|
1978
3351
|
var StackClient = class {
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
this.
|
|
1982
|
-
this.
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
});
|
|
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);
|
|
1986
3358
|
}
|
|
1987
|
-
client;
|
|
1988
3359
|
maxWaitTime = 60 * 30;
|
|
1989
3360
|
// 30 minutes
|
|
1990
3361
|
maxDelay = 30;
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
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");
|
|
1994
3372
|
return size > 5e4;
|
|
1995
3373
|
}
|
|
1996
3374
|
templateProp(stack) {
|
|
1997
|
-
|
|
1998
|
-
|
|
3375
|
+
const template = stack.toString();
|
|
3376
|
+
return this.shouldUploadTemplate(template) ? {
|
|
3377
|
+
TemplateUrl: assetBucketUrl(this.account, this.region, stack)
|
|
1999
3378
|
} : {
|
|
2000
|
-
TemplateBody:
|
|
3379
|
+
TemplateBody: template
|
|
2001
3380
|
};
|
|
2002
3381
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
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
|
|
2008
3397
|
});
|
|
2009
|
-
await client.send(new
|
|
2010
|
-
Bucket:
|
|
2011
|
-
Key: `${stack.
|
|
2012
|
-
Body:
|
|
2013
|
-
ACL:
|
|
2014
|
-
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
|
|
2015
3404
|
}));
|
|
2016
3405
|
}
|
|
2017
3406
|
async create(stack, capabilities) {
|
|
2018
|
-
debug("Create the", style.info(stack.
|
|
2019
|
-
|
|
2020
|
-
|
|
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),
|
|
2021
3411
|
EnableTerminationProtection: false,
|
|
2022
3412
|
OnFailure: OnFailure.DELETE,
|
|
2023
3413
|
Capabilities: capabilities,
|
|
3414
|
+
Tags: this.tags(stack),
|
|
2024
3415
|
...this.templateProp(stack)
|
|
2025
3416
|
}));
|
|
2026
3417
|
await waitUntilStackCreateComplete({
|
|
2027
|
-
client
|
|
3418
|
+
client,
|
|
2028
3419
|
maxWaitTime: this.maxWaitTime,
|
|
2029
3420
|
maxDelay: this.maxDelay
|
|
2030
3421
|
}, {
|
|
2031
|
-
StackName: stack.
|
|
3422
|
+
StackName: this.stackName(stack.name)
|
|
2032
3423
|
});
|
|
2033
3424
|
}
|
|
2034
3425
|
async update(stack, capabilities) {
|
|
2035
|
-
debug("Update the", style.info(stack.
|
|
2036
|
-
|
|
2037
|
-
|
|
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),
|
|
2038
3430
|
Capabilities: capabilities,
|
|
3431
|
+
Tags: this.tags(stack),
|
|
2039
3432
|
...this.templateProp(stack)
|
|
2040
3433
|
}));
|
|
2041
3434
|
await waitUntilStackUpdateComplete({
|
|
2042
|
-
client
|
|
3435
|
+
client,
|
|
2043
3436
|
maxWaitTime: this.maxWaitTime,
|
|
2044
3437
|
maxDelay: this.maxDelay
|
|
2045
3438
|
}, {
|
|
2046
|
-
StackName: stack.
|
|
3439
|
+
StackName: this.stackName(stack.name)
|
|
2047
3440
|
});
|
|
2048
3441
|
}
|
|
2049
3442
|
async validate(stack) {
|
|
2050
|
-
debug("Validate the", style.info(stack.
|
|
2051
|
-
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({
|
|
2052
3446
|
...this.templateProp(stack)
|
|
2053
3447
|
}));
|
|
2054
3448
|
return result.Capabilities;
|
|
2055
3449
|
}
|
|
2056
|
-
async get(name) {
|
|
3450
|
+
async get(name, region) {
|
|
2057
3451
|
debug("Get stack info for:", style.info(name));
|
|
3452
|
+
const client = this.getClient(region);
|
|
2058
3453
|
let result;
|
|
2059
3454
|
try {
|
|
2060
|
-
result = await
|
|
2061
|
-
StackName: name
|
|
3455
|
+
result = await client.send(new DescribeStacksCommand({
|
|
3456
|
+
StackName: this.stackName(name)
|
|
2062
3457
|
}));
|
|
2063
3458
|
} catch (error) {
|
|
2064
3459
|
if (error instanceof Error && error.name === "ValidationError" && error.message.includes("does not exist")) {
|
|
3460
|
+
debug("Stack not found");
|
|
2065
3461
|
return;
|
|
2066
3462
|
}
|
|
2067
3463
|
throw error;
|
|
@@ -2071,8 +3467,8 @@ var StackClient = class {
|
|
|
2071
3467
|
debug("Stack not found");
|
|
2072
3468
|
return;
|
|
2073
3469
|
}
|
|
2074
|
-
const resultTemplate = await
|
|
2075
|
-
StackName: name,
|
|
3470
|
+
const resultTemplate = await client.send(new GetTemplateCommand({
|
|
3471
|
+
StackName: this.stackName(name),
|
|
2076
3472
|
TemplateStage: TemplateStage.Original
|
|
2077
3473
|
}));
|
|
2078
3474
|
const outputs = {};
|
|
@@ -2090,14 +3486,15 @@ var StackClient = class {
|
|
|
2090
3486
|
};
|
|
2091
3487
|
}
|
|
2092
3488
|
async deploy(stack) {
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
|
|
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) {
|
|
2096
3493
|
debug("No stack changes");
|
|
2097
3494
|
return false;
|
|
2098
3495
|
}
|
|
2099
|
-
if (this.shouldUploadTemplate(
|
|
2100
|
-
await this.upload(stack);
|
|
3496
|
+
if (this.shouldUploadTemplate(template)) {
|
|
3497
|
+
await this.upload(stack, template);
|
|
2101
3498
|
}
|
|
2102
3499
|
const capabilities = await this.validate(stack);
|
|
2103
3500
|
if (!data) {
|
|
@@ -2109,22 +3506,23 @@ var StackClient = class {
|
|
|
2109
3506
|
}
|
|
2110
3507
|
return true;
|
|
2111
3508
|
}
|
|
2112
|
-
async delete(name) {
|
|
2113
|
-
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);
|
|
2114
3512
|
debug("Delete the", style.info(name), "stack");
|
|
2115
3513
|
if (!data) {
|
|
2116
3514
|
debug("Already deleted");
|
|
2117
3515
|
return;
|
|
2118
3516
|
}
|
|
2119
|
-
await
|
|
2120
|
-
StackName: name
|
|
3517
|
+
await client.send(new DeleteStackCommand({
|
|
3518
|
+
StackName: this.stackName(name)
|
|
2121
3519
|
}));
|
|
2122
3520
|
await waitUntilStackDeleteComplete({
|
|
2123
|
-
client
|
|
3521
|
+
client,
|
|
2124
3522
|
maxWaitTime: this.maxWaitTime,
|
|
2125
3523
|
maxDelay: this.maxDelay
|
|
2126
3524
|
}, {
|
|
2127
|
-
StackName: name
|
|
3525
|
+
StackName: this.stackName(name)
|
|
2128
3526
|
});
|
|
2129
3527
|
}
|
|
2130
3528
|
};
|
|
@@ -2213,10 +3611,9 @@ var confirmPrompt = (label, options = {}) => {
|
|
|
2213
3611
|
var bootstrapDeployer = (config) => {
|
|
2214
3612
|
return async (term) => {
|
|
2215
3613
|
debug("Initializing bootstrap");
|
|
2216
|
-
const app =
|
|
2217
|
-
const client = new StackClient(config);
|
|
2218
|
-
const
|
|
2219
|
-
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);
|
|
2220
3617
|
if (shouldDeploy) {
|
|
2221
3618
|
term.out.write(dialog("warning", [`Your app hasn't been bootstrapped yet`]));
|
|
2222
3619
|
const confirmed = await term.out.write(confirmPrompt("Would you like to bootstrap?"));
|
|
@@ -2224,8 +3621,7 @@ var bootstrapDeployer = (config) => {
|
|
|
2224
3621
|
throw new Cancelled();
|
|
2225
3622
|
}
|
|
2226
3623
|
const done = term.out.write(loadingDialog("Bootstrapping..."));
|
|
2227
|
-
|
|
2228
|
-
await client.deploy(assembly.stacks[0]);
|
|
3624
|
+
await client.deploy(stack);
|
|
2229
3625
|
done("Done deploying the bootstrap stack");
|
|
2230
3626
|
} else {
|
|
2231
3627
|
term.out.write(dialog("success", [
|
|
@@ -2251,8 +3647,8 @@ var stackTree = (nodes, statuses) => {
|
|
|
2251
3647
|
const render = (nodes2, deep = 0, parents = []) => {
|
|
2252
3648
|
const size = nodes2.length - 1;
|
|
2253
3649
|
nodes2.forEach((node, i) => {
|
|
2254
|
-
const
|
|
2255
|
-
const status2 = statuses[
|
|
3650
|
+
const name = node.stack.name;
|
|
3651
|
+
const status2 = statuses[name];
|
|
2256
3652
|
const first = i === 0 && deep === 0;
|
|
2257
3653
|
const last = i === size;
|
|
2258
3654
|
const more = i < size;
|
|
@@ -2266,7 +3662,7 @@ var stackTree = (nodes, statuses) => {
|
|
|
2266
3662
|
first && size === 0 ? " " : first ? "\u250C\u2500" : last ? "\u2514\u2500" : "\u251C\u2500"
|
|
2267
3663
|
),
|
|
2268
3664
|
" ",
|
|
2269
|
-
style.info(
|
|
3665
|
+
style.info(name),
|
|
2270
3666
|
" "
|
|
2271
3667
|
], [
|
|
2272
3668
|
" ",
|
|
@@ -2277,7 +3673,9 @@ var stackTree = (nodes, statuses) => {
|
|
|
2277
3673
|
render(node.children, deep + 1, [...parents, more]);
|
|
2278
3674
|
});
|
|
2279
3675
|
};
|
|
3676
|
+
term.out.gap();
|
|
2280
3677
|
render(nodes);
|
|
3678
|
+
term.out.gap();
|
|
2281
3679
|
};
|
|
2282
3680
|
};
|
|
2283
3681
|
|
|
@@ -2285,31 +3683,27 @@ var stackTree = (nodes, statuses) => {
|
|
|
2285
3683
|
var status = (program2) => {
|
|
2286
3684
|
program2.command("status").argument("[stacks...]", "Optionally filter stacks to lookup status").description("View the application status").action(async (filters) => {
|
|
2287
3685
|
await layout(async (config, write) => {
|
|
2288
|
-
const { app,
|
|
3686
|
+
const { app, dependencyTree } = await toApp(config, filters);
|
|
2289
3687
|
await cleanUp();
|
|
2290
|
-
await write(assetBuilder(
|
|
2291
|
-
write(
|
|
2292
|
-
const assembly = app.synth();
|
|
3688
|
+
await write(assetBuilder(app));
|
|
3689
|
+
await write(templateBuilder(app));
|
|
2293
3690
|
const doneLoading = write(loadingDialog("Loading stack information..."));
|
|
2294
|
-
const client = new StackClient(config);
|
|
3691
|
+
const client = new StackClient(app, config.account, config.region, config.credentials);
|
|
2295
3692
|
const statuses = [];
|
|
2296
3693
|
const stackStatuses = {};
|
|
2297
|
-
|
|
2298
|
-
stackStatuses[stack.
|
|
2299
|
-
}
|
|
2300
|
-
write(br());
|
|
3694
|
+
for (const stack of app) {
|
|
3695
|
+
stackStatuses[stack.name] = new Signal(style.info("Loading..."));
|
|
3696
|
+
}
|
|
2301
3697
|
write(stackTree(dependencyTree, stackStatuses));
|
|
2302
|
-
write(br());
|
|
2303
3698
|
debug("Load metadata for all deployed stacks on AWS");
|
|
2304
|
-
await Promise.all(
|
|
2305
|
-
const info = await client.get(stack.
|
|
2306
|
-
const
|
|
2307
|
-
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];
|
|
2308
3702
|
await new Promise((resolve) => setTimeout(resolve, i * 1e3));
|
|
2309
3703
|
if (!info) {
|
|
2310
3704
|
signal.set(style.error("non-existent"));
|
|
2311
3705
|
statuses.push("non-existent");
|
|
2312
|
-
} else if (info.template !==
|
|
3706
|
+
} else if (info.template !== stack.toString()) {
|
|
2313
3707
|
signal.set(style.warning("out-of-date"));
|
|
2314
3708
|
statuses.push("out-of-date");
|
|
2315
3709
|
} else {
|
|
@@ -2328,12 +3722,77 @@ var status = (program2) => {
|
|
|
2328
3722
|
});
|
|
2329
3723
|
};
|
|
2330
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
|
+
|
|
2331
3789
|
// src/cli/command/deploy.ts
|
|
2332
3790
|
var deploy = (program2) => {
|
|
2333
3791
|
program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
|
|
2334
3792
|
await layout(async (config, write) => {
|
|
2335
3793
|
await write(bootstrapDeployer(config));
|
|
2336
|
-
const { app,
|
|
3794
|
+
const { app, dependencyTree } = await toApp(config, filters);
|
|
3795
|
+
const stackNames = app.stacks.map((stack) => stack.name);
|
|
2337
3796
|
const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
|
|
2338
3797
|
debug("Stacks to deploy", formattedFilter);
|
|
2339
3798
|
const deployAll = filters.length === 0;
|
|
@@ -2343,34 +3802,23 @@ var deploy = (program2) => {
|
|
|
2343
3802
|
throw new Cancelled();
|
|
2344
3803
|
}
|
|
2345
3804
|
await cleanUp();
|
|
2346
|
-
await write(assetBuilder(
|
|
2347
|
-
write(
|
|
2348
|
-
write(
|
|
2349
|
-
const donePublishing = write(loadingDialog("Publishing stack assets to AWS..."));
|
|
2350
|
-
await Promise.all(assets.map(async (_, assets2) => {
|
|
2351
|
-
await Promise.all(assets2.map(async (asset) => {
|
|
2352
|
-
await asset.publish?.();
|
|
2353
|
-
}));
|
|
2354
|
-
}));
|
|
2355
|
-
donePublishing("Done publishing stack assets to AWS");
|
|
2356
|
-
const assembly = app.synth();
|
|
3805
|
+
await write(assetBuilder(app));
|
|
3806
|
+
await write(assetPublisher(config, app));
|
|
3807
|
+
await write(templateBuilder(app));
|
|
2357
3808
|
const statuses = {};
|
|
2358
|
-
|
|
2359
|
-
statuses[stack.
|
|
2360
|
-
}
|
|
3809
|
+
for (const stack of app) {
|
|
3810
|
+
statuses[stack.name] = new Signal(style.info("waiting"));
|
|
3811
|
+
}
|
|
2361
3812
|
const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
|
|
2362
|
-
write(br());
|
|
2363
3813
|
write(stackTree(dependencyTree, statuses));
|
|
2364
|
-
|
|
2365
|
-
const client = new StackClient(config);
|
|
3814
|
+
const client = new StackClient(app, config.account, config.region, config.credentials);
|
|
2366
3815
|
const deploymentLine = createDeploymentLine(dependencyTree);
|
|
2367
3816
|
for (const stacks of deploymentLine) {
|
|
2368
3817
|
const results = await Promise.allSettled(stacks.map(async (stack) => {
|
|
2369
|
-
const signal = statuses[stack.
|
|
2370
|
-
const stackArtifect = assembly.stacks.find((item) => item.id === stack.artifactId);
|
|
3818
|
+
const signal = statuses[stack.name];
|
|
2371
3819
|
signal.set(style.warning("deploying"));
|
|
2372
3820
|
try {
|
|
2373
|
-
await client.deploy(
|
|
3821
|
+
await client.deploy(stack);
|
|
2374
3822
|
} catch (error) {
|
|
2375
3823
|
debugError(error);
|
|
2376
3824
|
signal.set(style.error("failed"));
|
|
@@ -2459,7 +3907,6 @@ var set = (program2) => {
|
|
|
2459
3907
|
write(list({
|
|
2460
3908
|
"Set secret parameter": style.info(name)
|
|
2461
3909
|
}));
|
|
2462
|
-
write(br());
|
|
2463
3910
|
const value = await write(textPrompt("Enter secret value"));
|
|
2464
3911
|
if (value === "") {
|
|
2465
3912
|
write(dialog("error", [`Provided secret value can't be empty`]));
|
|
@@ -2480,7 +3927,6 @@ var get = (program2) => {
|
|
|
2480
3927
|
const done = write(loadingDialog(`Getting remote secret parameter`));
|
|
2481
3928
|
const value = await params.get(name);
|
|
2482
3929
|
done(`Done getting remote secret parameter`);
|
|
2483
|
-
write(br());
|
|
2484
3930
|
write(list({
|
|
2485
3931
|
Name: name,
|
|
2486
3932
|
Value: value || style.error("(empty)")
|
|
@@ -2503,7 +3949,6 @@ var del = (program2) => {
|
|
|
2503
3949
|
const value = await params.get(name);
|
|
2504
3950
|
await params.delete(name);
|
|
2505
3951
|
done(`Done deleting remote secret parameter`);
|
|
2506
|
-
write(br());
|
|
2507
3952
|
write(list({
|
|
2508
3953
|
Name: name,
|
|
2509
3954
|
Value: value || style.error("(empty)")
|
|
@@ -2521,7 +3966,6 @@ var list2 = (program2) => {
|
|
|
2521
3966
|
const values = await params.list();
|
|
2522
3967
|
done("Done loading secret values");
|
|
2523
3968
|
if (Object.keys(values).length > 0) {
|
|
2524
|
-
write(br());
|
|
2525
3969
|
write(list(values));
|
|
2526
3970
|
} else {
|
|
2527
3971
|
write(dialog("warning", ["No secret parameters found"]));
|
|
@@ -2542,15 +3986,28 @@ var secrets = (program2) => {
|
|
|
2542
3986
|
commands.forEach((cb) => cb(command));
|
|
2543
3987
|
};
|
|
2544
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
|
+
|
|
2545
3999
|
// src/cli/program.ts
|
|
2546
4000
|
var program = new Command();
|
|
2547
|
-
program.name("
|
|
4001
|
+
program.name(logo().join("").replace(/\s+/, ""));
|
|
2548
4002
|
program.option("--config-file <string>", "The config file location");
|
|
2549
4003
|
program.option("--stage <string>", "The stage to use, defaults to prod stage", "prod");
|
|
2550
4004
|
program.option("--profile <string>", "The AWS profile to use");
|
|
2551
4005
|
program.option("--region <string>", "The AWS region to use");
|
|
2552
4006
|
program.option("-m --mute", "Mute sound effects");
|
|
2553
4007
|
program.option("-v --verbose", "Print verbose logs");
|
|
4008
|
+
program.exitOverride(() => {
|
|
4009
|
+
process.exit(0);
|
|
4010
|
+
});
|
|
2554
4011
|
program.on("option:verbose", () => {
|
|
2555
4012
|
process.env.VERBOSE = program.opts().verbose ? "1" : void 0;
|
|
2556
4013
|
});
|
|
@@ -2559,13 +4016,12 @@ var commands2 = [
|
|
|
2559
4016
|
status,
|
|
2560
4017
|
build,
|
|
2561
4018
|
deploy,
|
|
2562
|
-
secrets
|
|
4019
|
+
secrets,
|
|
4020
|
+
test
|
|
2563
4021
|
// diff,
|
|
2564
4022
|
// remove,
|
|
2565
|
-
// test,
|
|
2566
|
-
// test2,
|
|
2567
4023
|
];
|
|
2568
|
-
commands2.forEach((
|
|
4024
|
+
commands2.forEach((fn) => fn(program));
|
|
2569
4025
|
|
|
2570
4026
|
// src/bin.ts
|
|
2571
4027
|
program.parse(process.argv);
|