@awsless/awsless 0.0.14 → 0.0.15

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