@iacmp/cli 1.1.0

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.
@@ -0,0 +1,4458 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../providers/aws/dist/synth/cloudformation.js
34
+ var require_cloudformation = __commonJS({
35
+ "../providers/aws/dist/synth/cloudformation.js"(exports2) {
36
+ "use strict";
37
+ Object.defineProperty(exports2, "__esModule", { value: true });
38
+ exports2.synthesize = synthesize;
39
+ var INSTANCE_TYPE_MAP = {
40
+ small: "t3.small",
41
+ medium: "t3.medium",
42
+ large: "t3.large"
43
+ };
44
+ var AMI_MAP = {
45
+ "ubuntu": "{{resolve:ssm:/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id}}",
46
+ "ubuntu-22.04": "{{resolve:ssm:/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id}}",
47
+ "ubuntu-20.04": "{{resolve:ssm:/aws/service/canonical/ubuntu/server/20.04/stable/current/amd64/hvm/ebs-gp2/ami-id}}",
48
+ "amazon-linux-2": "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}",
49
+ "amazon-linux-2023": "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}",
50
+ "windows-2022": "{{resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base}}",
51
+ "windows-2019": "{{resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base}}",
52
+ "windows-2016": "{{resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base}}"
53
+ };
54
+ var CACHE_NODE_TYPE_MAP = {
55
+ small: "cache.t3.micro",
56
+ medium: "cache.t3.medium",
57
+ large: "cache.r6g.large"
58
+ };
59
+ var K8S_NODE_TYPE_MAP = {
60
+ small: "t3.medium",
61
+ medium: "m5.large",
62
+ large: "m5.2xlarge"
63
+ };
64
+ function synthesizeConstruct(construct) {
65
+ const logicalId = construct.id.replace(/[^a-zA-Z0-9]/g, "");
66
+ const props = construct.props;
67
+ switch (construct.type) {
68
+ // ── Compute ──────────────────────────────────────────────────────────
69
+ case "Compute.Instance":
70
+ return [[logicalId, {
71
+ Type: "AWS::EC2::Instance",
72
+ Properties: {
73
+ InstanceType: INSTANCE_TYPE_MAP[props.instanceType] ?? "t3.small",
74
+ ImageId: AMI_MAP[props.image] ?? props.image
75
+ }
76
+ }]];
77
+ case "Compute.AutoScaling": {
78
+ const lcId = `${logicalId}LC`;
79
+ const asgId = `${logicalId}ASG`;
80
+ const spId = `${logicalId}ScalingPolicy`;
81
+ const lc = {
82
+ Type: "AWS::AutoScaling::LaunchConfiguration",
83
+ Properties: {
84
+ ImageId: AMI_MAP[props.image] ?? props.image,
85
+ InstanceType: INSTANCE_TYPE_MAP[props.instanceType] ?? "t3.small",
86
+ ...props.securityGroupIds ? { SecurityGroups: props.securityGroupIds } : {}
87
+ }
88
+ };
89
+ const asg = {
90
+ Type: "AWS::AutoScaling::AutoScalingGroup",
91
+ Properties: {
92
+ LaunchConfigurationName: { Ref: lcId },
93
+ MinSize: String(props.minCapacity ?? 1),
94
+ MaxSize: String(props.maxCapacity ?? 3),
95
+ DesiredCapacity: String(props.desiredCapacity ?? props.minCapacity ?? 1),
96
+ ...props.subnetIds ? { VPCZoneIdentifier: props.subnetIds } : {},
97
+ Tags: [{ Key: "Name", Value: logicalId, PropagateAtLaunch: true }]
98
+ }
99
+ };
100
+ const entries = [[lcId, lc], [asgId, asg]];
101
+ if (props.targetCpuUtilization) {
102
+ entries.push([spId, {
103
+ Type: "AWS::AutoScaling::ScalingPolicy",
104
+ Properties: {
105
+ AutoScalingGroupName: { Ref: asgId },
106
+ PolicyType: "TargetTrackingScaling",
107
+ TargetTrackingConfiguration: {
108
+ PredefinedMetricSpecification: { PredefinedMetricType: "ASGAverageCPUUtilization" },
109
+ TargetValue: props.targetCpuUtilization
110
+ }
111
+ }
112
+ }]);
113
+ }
114
+ return entries;
115
+ }
116
+ case "Compute.Container": {
117
+ const clusterLogicalId = `${logicalId}Cluster`;
118
+ const tdLogicalId = `${logicalId}TaskDef`;
119
+ const svcLogicalId = `${logicalId}Service`;
120
+ const environment = props.environment;
121
+ return [
122
+ [clusterLogicalId, {
123
+ Type: "AWS::ECS::Cluster",
124
+ Properties: { ClusterName: construct.id }
125
+ }],
126
+ [tdLogicalId, {
127
+ Type: "AWS::ECS::TaskDefinition",
128
+ Properties: {
129
+ Family: construct.id,
130
+ NetworkMode: "awsvpc",
131
+ RequiresCompatibilities: ["FARGATE"],
132
+ Cpu: String(props.cpu ?? 256),
133
+ Memory: String(props.memory ?? 512),
134
+ ExecutionRoleArn: { "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole" },
135
+ ContainerDefinitions: [{
136
+ Name: construct.id,
137
+ Image: props.image,
138
+ PortMappings: props.port ? [{ ContainerPort: props.port, Protocol: "tcp" }] : [],
139
+ Environment: environment ? Object.entries(environment).map(([k, v]) => ({ Name: k, Value: v })) : [],
140
+ LogConfiguration: {
141
+ LogDriver: "awslogs",
142
+ Options: {
143
+ "awslogs-group": `/ecs/${construct.id}`,
144
+ "awslogs-region": { Ref: "AWS::Region" },
145
+ "awslogs-stream-prefix": "ecs"
146
+ }
147
+ }
148
+ }]
149
+ }
150
+ }],
151
+ [svcLogicalId, {
152
+ Type: "AWS::ECS::Service",
153
+ Properties: {
154
+ Cluster: { Ref: clusterLogicalId },
155
+ TaskDefinition: { Ref: tdLogicalId },
156
+ DesiredCount: props.desiredCount ?? 1,
157
+ LaunchType: "FARGATE",
158
+ NetworkConfiguration: {
159
+ AwsvpcConfiguration: {
160
+ AssignPublicIp: props.publicIp ? "ENABLED" : "DISABLED",
161
+ Subnets: []
162
+ }
163
+ }
164
+ }
165
+ }]
166
+ ];
167
+ }
168
+ case "Compute.Kubernetes": {
169
+ return [
170
+ [logicalId, {
171
+ Type: "AWS::EKS::Cluster",
172
+ Properties: {
173
+ Name: construct.id,
174
+ Version: props.version ?? "1.29",
175
+ ResourcesVpcConfig: {
176
+ SubnetIds: [],
177
+ EndpointPrivateAccess: props.privateCluster ?? false,
178
+ EndpointPublicAccess: !props.privateCluster
179
+ },
180
+ RoleArn: { "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/EKSClusterRole" }
181
+ }
182
+ }],
183
+ [`${logicalId}NodeGroup`, {
184
+ Type: "AWS::EKS::Nodegroup",
185
+ Properties: {
186
+ ClusterName: { Ref: logicalId },
187
+ NodegroupName: `${construct.id}-ng`,
188
+ ScalingConfig: {
189
+ MinSize: props.minNodes ?? 1,
190
+ MaxSize: props.maxNodes ?? 3,
191
+ DesiredSize: props.desiredNodes ?? 2
192
+ },
193
+ InstanceTypes: [K8S_NODE_TYPE_MAP[props.nodeInstanceType ?? "medium"] ?? "m5.large"],
194
+ NodeRole: { "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/EKSNodeRole" },
195
+ Subnets: []
196
+ }
197
+ }]
198
+ ];
199
+ }
200
+ // ── Storage ───────────────────────────────────────────────────────────
201
+ case "Storage.Bucket": {
202
+ const lifecycleRules = props.lifecycleRules ?? [];
203
+ return [[logicalId, {
204
+ Type: "AWS::S3::Bucket",
205
+ Properties: {
206
+ VersioningConfiguration: props.versioning ? { Status: "Enabled" } : { Status: "Suspended" },
207
+ PublicAccessBlockConfiguration: {
208
+ BlockPublicAcls: !props.publicAccess,
209
+ BlockPublicPolicy: !props.publicAccess,
210
+ IgnorePublicAcls: !props.publicAccess,
211
+ RestrictPublicBuckets: !props.publicAccess
212
+ },
213
+ ...lifecycleRules.length > 0 ? {
214
+ LifecycleConfiguration: {
215
+ Rules: lifecycleRules.map((r, i) => ({
216
+ Id: `rule-${i}`,
217
+ Status: "Enabled",
218
+ ...r.prefix ? { Prefix: r.prefix } : {},
219
+ ...r.expireAfterDays ? { ExpirationInDays: r.expireAfterDays } : {},
220
+ ...r.transitionToGlacierDays ? {
221
+ Transitions: [{ TransitionInDays: r.transitionToGlacierDays, StorageClass: "GLACIER" }]
222
+ } : {}
223
+ }))
224
+ }
225
+ } : {}
226
+ }
227
+ }]];
228
+ }
229
+ case "Storage.FileSystem": {
230
+ const accessPoints = props.accessPoints ?? [];
231
+ const entries = [[logicalId, {
232
+ Type: "AWS::EFS::FileSystem",
233
+ Properties: {
234
+ PerformanceMode: props.performanceMode ?? "generalPurpose",
235
+ ThroughputMode: props.throughputMode ?? "bursting",
236
+ Encrypted: props.encrypted ?? true,
237
+ LifecyclePolicies: [{ TransitionToIA: "AFTER_30_DAYS" }],
238
+ FileSystemTags: [{ Key: "Name", Value: construct.id }]
239
+ }
240
+ }]];
241
+ for (const ap of accessPoints) {
242
+ const apId = `${logicalId}AP${ap.name.replace(/[^a-zA-Z0-9]/g, "")}`;
243
+ entries.push([apId, {
244
+ Type: "AWS::EFS::AccessPoint",
245
+ Properties: {
246
+ FileSystemId: { Ref: logicalId },
247
+ RootDirectory: { Path: ap.path },
248
+ ...ap.uid ? { PosixUser: { Uid: String(ap.uid), Gid: String(ap.gid ?? ap.uid) } } : {},
249
+ AccessPointTags: [{ Key: "Name", Value: ap.name }]
250
+ }
251
+ }]);
252
+ }
253
+ return entries;
254
+ }
255
+ case "Storage.Archive": {
256
+ return [[logicalId, {
257
+ Type: "AWS::S3::Bucket",
258
+ Properties: {
259
+ LifecycleConfiguration: {
260
+ Rules: [{
261
+ Id: "archive-rule",
262
+ Status: "Enabled",
263
+ Transitions: [{ TransitionInDays: 0, StorageClass: "DEEP_ARCHIVE" }],
264
+ ...props.retentionDays ? { ExpirationInDays: props.retentionDays } : {}
265
+ }]
266
+ },
267
+ ObjectLockEnabled: props.lockEnabled ?? false,
268
+ PublicAccessBlockConfiguration: {
269
+ BlockPublicAcls: true,
270
+ BlockPublicPolicy: true,
271
+ IgnorePublicAcls: true,
272
+ RestrictPublicBuckets: true
273
+ }
274
+ }
275
+ }]];
276
+ }
277
+ // ── Network ───────────────────────────────────────────────────────────
278
+ case "Network.VPC":
279
+ return [[logicalId, {
280
+ Type: "AWS::EC2::VPC",
281
+ Properties: {
282
+ CidrBlock: props.cidr ?? "10.0.0.0/16",
283
+ EnableDnsHostnames: true,
284
+ EnableDnsSupport: true,
285
+ Tags: [{ Key: "Name", Value: logicalId }]
286
+ }
287
+ }]];
288
+ case "Network.Subnet": {
289
+ const isPublic = props.public ?? false;
290
+ return [[logicalId, {
291
+ Type: "AWS::EC2::Subnet",
292
+ Properties: {
293
+ VpcId: props.vpcId,
294
+ CidrBlock: props.cidr,
295
+ ...props.availabilityZone ? { AvailabilityZone: props.availabilityZone } : {},
296
+ MapPublicIpOnLaunch: isPublic,
297
+ Tags: [{ Key: "Name", Value: logicalId }, { Key: "Type", Value: isPublic ? "public" : "private" }]
298
+ }
299
+ }]];
300
+ }
301
+ case "Network.SecurityGroup": {
302
+ const ingress = props.ingressRules ?? [];
303
+ const egress = props.egressRules ?? [];
304
+ return [[logicalId, {
305
+ Type: "AWS::EC2::SecurityGroup",
306
+ Properties: {
307
+ GroupDescription: props.description ?? `Security group ${logicalId}`,
308
+ VpcId: props.vpcId,
309
+ SecurityGroupIngress: ingress.map((r, i) => {
310
+ if (r.cidr === void 0) {
311
+ console.warn(`[aws] Security group rule sem CIDR; usando 0.0.0.0/0 \u2014 defina props.cidr explicitamente (${construct.id} ingress[${i}])`);
312
+ }
313
+ return {
314
+ IpProtocol: r.protocol,
315
+ FromPort: r.fromPort,
316
+ ToPort: r.toPort,
317
+ CidrIp: r.cidr ?? "0.0.0.0/0",
318
+ ...r.description ? { Description: r.description } : {}
319
+ };
320
+ }),
321
+ SecurityGroupEgress: egress.length > 0 ? egress.map((r) => ({
322
+ IpProtocol: r.protocol,
323
+ FromPort: r.fromPort,
324
+ ToPort: r.toPort,
325
+ CidrIp: r.cidr ?? "0.0.0.0/0",
326
+ ...r.description ? { Description: r.description } : {}
327
+ })) : [{ IpProtocol: "-1", CidrIp: "0.0.0.0/0", Description: "Allow all egress" }],
328
+ Tags: [{ Key: "Name", Value: logicalId }]
329
+ }
330
+ }]];
331
+ }
332
+ case "Network.WAF": {
333
+ const rules = props.rules ?? [];
334
+ const defaultAction = props.defaultAction ?? "allow";
335
+ return [[logicalId, {
336
+ Type: "AWS::WAFv2::WebACL",
337
+ Properties: {
338
+ Name: logicalId,
339
+ Scope: props.scope ?? "REGIONAL",
340
+ DefaultAction: { [defaultAction === "block" ? "Block" : "Allow"]: {} },
341
+ Description: props.description ?? `WAF ${logicalId}`,
342
+ Rules: rules.map((r, i) => ({
343
+ Name: r.name ?? `rule-${i}`,
344
+ Priority: r.priority ?? i + 1,
345
+ Action: { [r.action === "block" ? "Block" : r.action === "count" ? "Count" : "Allow"]: {} },
346
+ VisibilityConfig: {
347
+ SampledRequestsEnabled: true,
348
+ CloudWatchMetricsEnabled: true,
349
+ MetricName: (r.name ?? `rule${i}`).replace(/[^a-zA-Z0-9]/g, "")
350
+ },
351
+ Statement: r.managedGroup ? { ManagedRuleGroupStatement: { VendorName: "AWS", Name: r.managedGroup } } : {
352
+ ByteMatchStatement: {
353
+ SearchString: (r.matchValues ?? ["BadBot"])[0],
354
+ FieldToMatch: { SingleHeader: { Name: "user-agent" } },
355
+ TextTransformations: [{ Priority: 0, Type: "NONE" }],
356
+ PositionalConstraint: "CONTAINS"
357
+ }
358
+ }
359
+ })),
360
+ VisibilityConfig: {
361
+ SampledRequestsEnabled: true,
362
+ CloudWatchMetricsEnabled: true,
363
+ MetricName: logicalId
364
+ }
365
+ }
366
+ }]];
367
+ }
368
+ case "Network.LoadBalancer": {
369
+ const lbType = props.type ?? "application";
370
+ const listeners = props.listeners ?? [];
371
+ const targetGroups = props.targetGroups ?? [];
372
+ const entries = [[logicalId, {
373
+ Type: "AWS::ElasticLoadBalancingV2::LoadBalancer",
374
+ Properties: {
375
+ Name: construct.id,
376
+ Type: lbType,
377
+ Scheme: props.scheme ?? "internet-facing",
378
+ Subnets: props.subnetIds ?? [],
379
+ ...lbType === "application" && props.securityGroupIds ? { SecurityGroups: props.securityGroupIds } : {},
380
+ LoadBalancerAttributes: [
381
+ { Key: "deletion_protection.enabled", Value: String(props.deletionProtection ?? false) }
382
+ ]
383
+ }
384
+ }]];
385
+ for (const tg of targetGroups) {
386
+ const tgId = `${logicalId}TG${tg.name.replace(/[^a-zA-Z0-9]/g, "")}`;
387
+ entries.push([tgId, {
388
+ Type: "AWS::ElasticLoadBalancingV2::TargetGroup",
389
+ Properties: {
390
+ Name: tg.name,
391
+ Port: tg.port,
392
+ Protocol: tg.protocol,
393
+ VpcId: "",
394
+ HealthCheckPath: tg.healthCheckPath ?? "/",
395
+ HealthCheckPort: String(tg.healthCheckPort ?? tg.port),
396
+ TargetType: "ip"
397
+ }
398
+ }]);
399
+ }
400
+ for (let i = 0; i < listeners.length; i++) {
401
+ const l = listeners[i];
402
+ entries.push([`${logicalId}Listener${i + 1}`, {
403
+ Type: "AWS::ElasticLoadBalancingV2::Listener",
404
+ Properties: {
405
+ LoadBalancerArn: { Ref: logicalId },
406
+ Port: l.port,
407
+ Protocol: l.protocol,
408
+ ...l.certificateArn ? { Certificates: [{ CertificateArn: l.certificateArn }] } : {},
409
+ DefaultActions: l.redirectToHttps ? [{ Type: "redirect", RedirectConfig: { Protocol: "HTTPS", Port: "443", StatusCode: "HTTP_301" } }] : [{ Type: "fixed-response", FixedResponseConfig: { StatusCode: "404", MessageBody: "Not found", ContentType: "text/plain" } }]
410
+ }
411
+ }]);
412
+ }
413
+ return entries;
414
+ }
415
+ case "Network.CDN": {
416
+ const origins = props.origins ?? [];
417
+ const cachePolicies = props.cachePolicies ?? [];
418
+ return [[logicalId, {
419
+ Type: "AWS::CloudFront::Distribution",
420
+ Properties: {
421
+ DistributionConfig: {
422
+ Enabled: true,
423
+ HttpVersion: props.httpVersion ?? "http2",
424
+ PriceClass: props.priceClass ?? "PriceClass_100",
425
+ DefaultRootObject: props.defaultRootObject ?? "index.html",
426
+ ...props.aliases ? { Aliases: props.aliases } : {},
427
+ ...props.certificateArn ? { ViewerCertificate: { AcmCertificateArn: props.certificateArn, SslSupportMethod: "sni-only", MinimumProtocolVersion: "TLSv1.2_2021" } } : { ViewerCertificate: { CloudFrontDefaultCertificate: true } },
428
+ ...props.wafAclId ? { WebACLId: props.wafAclId } : {},
429
+ Origins: origins.map((o) => ({
430
+ Id: o.id,
431
+ DomainName: o.domainName,
432
+ OriginPath: o.path ?? "",
433
+ CustomOriginConfig: { HTTPSPort: 443, OriginProtocolPolicy: o.protocol ?? "https-only" }
434
+ })),
435
+ DefaultCacheBehavior: {
436
+ TargetOriginId: origins[0].id,
437
+ ViewerProtocolPolicy: "redirect-to-https",
438
+ AllowedMethods: ["GET", "HEAD", "OPTIONS"],
439
+ CachedMethods: ["GET", "HEAD"],
440
+ Compress: true,
441
+ ForwardedValues: { QueryString: false, Cookies: { Forward: "none" } }
442
+ },
443
+ CacheBehaviors: cachePolicies.map((cp) => ({
444
+ PathPattern: cp.pathPattern,
445
+ TargetOriginId: origins[0].id,
446
+ ViewerProtocolPolicy: "redirect-to-https",
447
+ DefaultTTL: cp.ttlSeconds ?? 86400,
448
+ MaxTTL: (cp.ttlSeconds ?? 86400) * 2,
449
+ Compress: cp.compress ?? true,
450
+ ForwardedValues: { QueryString: true, Cookies: { Forward: "all" } },
451
+ AllowedMethods: ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"],
452
+ CachedMethods: ["GET", "HEAD"]
453
+ }))
454
+ }
455
+ }
456
+ }]];
457
+ }
458
+ case "Network.Dns": {
459
+ const records = props.records ?? [];
460
+ const hostedZoneId = `${logicalId}Zone`;
461
+ const entries = [[hostedZoneId, {
462
+ Type: "AWS::Route53::HostedZone",
463
+ Properties: {
464
+ Name: props.zoneName,
465
+ HostedZoneConfig: { Comment: `Zone for ${props.zoneName}` }
466
+ }
467
+ }]];
468
+ for (const r of records) {
469
+ const recId = `${logicalId}${r.name.replace(/[^a-zA-Z0-9]/g, "")}${r.type}`;
470
+ entries.push([recId, {
471
+ Type: "AWS::Route53::RecordSet",
472
+ Properties: {
473
+ HostedZoneId: { Ref: hostedZoneId },
474
+ Name: r.name,
475
+ Type: r.type,
476
+ TTL: String(r.ttl ?? 300),
477
+ ...r.aliasTarget ? { AliasTarget: { DNSName: r.aliasTarget, HostedZoneId: "Z35SXDOTRQ7X7K" } } : { ResourceRecords: r.values }
478
+ }
479
+ }]);
480
+ }
481
+ return entries;
482
+ }
483
+ // ── Database ──────────────────────────────────────────────────────────
484
+ case "Database.SQL": {
485
+ const engine = props.engine;
486
+ const edition = props.edition ?? "";
487
+ const engineMap = {
488
+ mysql: { Engine: "mysql", EngineVersion: "8.0.36" },
489
+ postgres: { Engine: "postgres", EngineVersion: "15.4" },
490
+ mariadb: { Engine: "mariadb", EngineVersion: "10.11.6" },
491
+ oracle: { Engine: `oracle-${edition || "se2"}`, EngineVersion: "19.0.0.0.ru-2024-01.rur-2024-01.r1" },
492
+ sqlserver: { Engine: `sqlserver-${edition || "ex"}`, EngineVersion: "15.00.4365.2.v1" }
493
+ };
494
+ const mapped = engineMap[engine] ?? engineMap["mysql"];
495
+ const isOracle = engine === "oracle";
496
+ const isSqlServer = engine === "sqlserver";
497
+ const licenseModel = props.licenseModel ?? (isOracle || isSqlServer ? "license-included" : void 0);
498
+ const masterUser = isSqlServer ? "sqladmin" : "dbadmin";
499
+ const defaultInstance = isOracle || isSqlServer ? "db.t3.small" : "db.t3.micro";
500
+ const rdsProps = {
501
+ DBInstanceClass: props.instanceType ?? defaultInstance,
502
+ Engine: mapped.Engine,
503
+ EngineVersion: mapped.EngineVersion,
504
+ AllocatedStorage: String(props.storageGb ?? 20),
505
+ MultiAZ: props.multiAz ?? false,
506
+ MasterUsername: masterUser,
507
+ MasterUserPassword: { "Fn::Sub": "{{resolve:ssm:/iacmp/${AWS::StackName}/db-password}}" },
508
+ StorageEncrypted: true,
509
+ BackupRetentionPeriod: props.backupRetentionDays ?? 7,
510
+ DeletionProtection: props.deletionProtection ?? false
511
+ };
512
+ if (licenseModel)
513
+ rdsProps["LicenseModel"] = licenseModel;
514
+ return [[logicalId, {
515
+ Type: "AWS::RDS::DBInstance",
516
+ DeletionPolicy: props.deletionProtection ? "Retain" : "Snapshot",
517
+ Properties: rdsProps
518
+ }]];
519
+ }
520
+ case "Database.DocumentDB": {
521
+ const instances = props.instances ?? 1;
522
+ const clusterLogicalId = `${logicalId}Cluster`;
523
+ const entries = [[clusterLogicalId, {
524
+ Type: "AWS::DocDB::DBCluster",
525
+ DeletionPolicy: props.deletionProtection ? "Retain" : "Snapshot",
526
+ Properties: {
527
+ DBClusterIdentifier: construct.id.toLowerCase(),
528
+ MasterUsername: "docdbadmin",
529
+ MasterUserPassword: { "Fn::Sub": "{{resolve:ssm:/iacmp/${AWS::StackName}/docdb-password}}" },
530
+ StorageEncrypted: true,
531
+ BackupRetentionPeriod: 7,
532
+ DeletionProtection: props.deletionProtection ?? false
533
+ }
534
+ }]];
535
+ for (let i = 0; i < instances; i++) {
536
+ entries.push([`${logicalId}Instance${i + 1}`, {
537
+ Type: "AWS::DocDB::DBInstance",
538
+ Properties: {
539
+ DBClusterIdentifier: { Ref: clusterLogicalId },
540
+ DBInstanceClass: props.instanceType ?? "db.t3.medium",
541
+ DBInstanceIdentifier: `${construct.id.toLowerCase()}-${i + 1}`
542
+ }
543
+ }]);
544
+ }
545
+ return entries;
546
+ }
547
+ case "Database.DynamoDB": {
548
+ const billingMode = props.billingMode ?? "PAY_PER_REQUEST";
549
+ const gsis = props.globalSecondaryIndexes ?? [];
550
+ const attrDefs = [
551
+ { AttributeName: props.partitionKey, AttributeType: "S" },
552
+ ...props.sortKey ? [{ AttributeName: props.sortKey, AttributeType: "S" }] : [],
553
+ ...gsis.map((g) => ({ AttributeName: g.partitionKey, AttributeType: "S" })),
554
+ ...gsis.filter((g) => g.sortKey).map((g) => ({ AttributeName: g.sortKey, AttributeType: "S" }))
555
+ ].filter((v, i, a) => a.findIndex((x) => x.AttributeName === v.AttributeName) === i);
556
+ return [[logicalId, {
557
+ Type: "AWS::DynamoDB::Table",
558
+ DeletionPolicy: "Retain",
559
+ Properties: {
560
+ TableName: construct.id,
561
+ BillingMode: billingMode,
562
+ ...billingMode === "PROVISIONED" ? {
563
+ ProvisionedThroughput: { ReadCapacityUnits: props.readCapacity ?? 5, WriteCapacityUnits: props.writeCapacity ?? 5 }
564
+ } : {},
565
+ AttributeDefinitions: attrDefs,
566
+ KeySchema: [
567
+ { AttributeName: props.partitionKey, KeyType: "HASH" },
568
+ ...props.sortKey ? [{ AttributeName: props.sortKey, KeyType: "RANGE" }] : []
569
+ ],
570
+ ...gsis.length > 0 ? {
571
+ GlobalSecondaryIndexes: gsis.map((g) => ({
572
+ IndexName: g.name,
573
+ KeySchema: [
574
+ { AttributeName: g.partitionKey, KeyType: "HASH" },
575
+ ...g.sortKey ? [{ AttributeName: g.sortKey, KeyType: "RANGE" }] : []
576
+ ],
577
+ Projection: { ProjectionType: "ALL" },
578
+ ...billingMode === "PROVISIONED" ? {
579
+ ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }
580
+ } : {}
581
+ }))
582
+ } : {},
583
+ ...props.ttlAttribute ? { TimeToLiveSpecification: { AttributeName: props.ttlAttribute, Enabled: true } } : {},
584
+ PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: props.pointInTimeRecovery ?? true },
585
+ ...props.streamEnabled ? { StreamSpecification: { StreamViewType: "NEW_AND_OLD_IMAGES" } } : {}
586
+ }
587
+ }]];
588
+ }
589
+ // ── Cache ─────────────────────────────────────────────────────────────
590
+ case "Cache.Redis": {
591
+ const numNodes = props.numCacheNodes ?? 1;
592
+ const autoFailover = props.automaticFailoverEnabled ?? false;
593
+ return [[logicalId, {
594
+ Type: "AWS::ElastiCache::ReplicationGroup",
595
+ Properties: {
596
+ ReplicationGroupDescription: `Redis ${construct.id}`,
597
+ ReplicationGroupId: construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 40),
598
+ CacheNodeType: CACHE_NODE_TYPE_MAP[props.nodeType ?? "small"] ?? "cache.t3.micro",
599
+ Engine: "redis",
600
+ EngineVersion: props.version ?? "7.0",
601
+ NumCacheClusters: numNodes,
602
+ AutomaticFailoverEnabled: autoFailover && numNodes > 1,
603
+ AtRestEncryptionEnabled: props.atRestEncryptionEnabled ?? true,
604
+ TransitEncryptionEnabled: props.transitEncryptionEnabled ?? true,
605
+ ...props.subnetGroupName ? { CacheSubnetGroupName: props.subnetGroupName } : {},
606
+ ...props.securityGroupIds ? { SecurityGroupIds: props.securityGroupIds } : {}
607
+ }
608
+ }]];
609
+ }
610
+ case "Cache.Memcached": {
611
+ return [[logicalId, {
612
+ Type: "AWS::ElastiCache::CacheCluster",
613
+ Properties: {
614
+ ClusterName: construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 40),
615
+ Engine: "memcached",
616
+ CacheNodeType: CACHE_NODE_TYPE_MAP[props.nodeType ?? "small"] ?? "cache.t3.micro",
617
+ NumCacheNodes: props.numCacheNodes ?? 2,
618
+ ...props.subnetGroupName ? { CacheSubnetGroupName: props.subnetGroupName } : {}
619
+ }
620
+ }]];
621
+ }
622
+ // ── Function ──────────────────────────────────────────────────────────
623
+ case "Function.Lambda": {
624
+ const environment = props.environment;
625
+ const runtimeMap = {
626
+ "nodejs20": "nodejs20.x",
627
+ "nodejs18": "nodejs18.x",
628
+ "python3.12": "python3.12",
629
+ "python3.11": "python3.11",
630
+ "java21": "java21",
631
+ "go1.x": "go1.x",
632
+ "dotnet8": "dotnet8"
633
+ };
634
+ return [[logicalId, {
635
+ Type: "AWS::Lambda::Function",
636
+ Properties: {
637
+ FunctionName: construct.id,
638
+ Runtime: runtimeMap[props.runtime ?? "nodejs20"] ?? "nodejs20.x",
639
+ Handler: props.handler,
640
+ Code: { ZipFile: props.code },
641
+ MemorySize: props.memory ?? 128,
642
+ Timeout: props.timeout ?? 30,
643
+ Role: { "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/LambdaExecutionRole" },
644
+ ...props.reservedConcurrency !== void 0 ? { ReservedConcurrentExecutions: props.reservedConcurrency } : {},
645
+ ...environment && Object.keys(environment).length > 0 ? { Environment: { Variables: environment } } : {},
646
+ ...props.vpcId ? {
647
+ VpcConfig: {
648
+ SubnetIds: props.subnetIds ?? [],
649
+ SecurityGroupIds: props.securityGroupIds ?? []
650
+ }
651
+ } : {}
652
+ }
653
+ }]];
654
+ }
655
+ case "Function.ApiGateway": {
656
+ const apigwType = props.type ?? "HTTP";
657
+ const routes = props.routes ?? [];
658
+ const stageName = props.stageName ?? "$default";
659
+ const entries = [[logicalId, {
660
+ Type: apigwType === "REST" ? "AWS::ApiGateway::RestApi" : "AWS::ApiGatewayV2::Api",
661
+ Properties: {
662
+ Name: props.name,
663
+ Description: props.description ?? "",
664
+ ...apigwType !== "REST" ? { ProtocolType: apigwType } : {},
665
+ ...props.cors ? { CorsConfiguration: { AllowOrigins: ["*"], AllowMethods: ["*"], AllowHeaders: ["*"] } } : {}
666
+ }
667
+ }]];
668
+ entries.push([`${logicalId}Stage`, {
669
+ Type: apigwType === "REST" ? "AWS::ApiGateway::Stage" : "AWS::ApiGatewayV2::Stage",
670
+ Properties: {
671
+ ...apigwType === "REST" ? { RestApiId: { Ref: logicalId } } : { ApiId: { Ref: logicalId } },
672
+ StageName: stageName,
673
+ AutoDeploy: apigwType !== "REST",
674
+ ...props.throttlingBurstLimit ? {
675
+ DefaultRouteSettings: {
676
+ ThrottlingBurstLimit: props.throttlingBurstLimit,
677
+ ThrottlingRateLimit: props.throttlingRateLimit ?? 1e3
678
+ }
679
+ } : {}
680
+ }
681
+ }]);
682
+ const authorizerLambdaId = props.authorizerLambdaId;
683
+ const authorizerId = authorizerLambdaId ? `${logicalId}Authorizer` : void 0;
684
+ if (authorizerLambdaId) {
685
+ entries.push([authorizerId, {
686
+ Type: "AWS::ApiGatewayV2::Authorizer",
687
+ Properties: {
688
+ ApiId: { Ref: logicalId },
689
+ AuthorizerType: "REQUEST",
690
+ Name: `${props.name}-authorizer`,
691
+ AuthorizerUri: { "Fn::Sub": `arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${authorizerLambdaId}.Arn}/invocations` },
692
+ AuthorizerPayloadFormatVersion: "2.0",
693
+ IdentitySource: ["$request.header.Authorization"]
694
+ }
695
+ }]);
696
+ }
697
+ for (const r of routes) {
698
+ const routeId = `${logicalId}${r.method}${r.path.replace(/[^a-zA-Z0-9]/g, "")}Route`;
699
+ entries.push([routeId, {
700
+ Type: "AWS::ApiGatewayV2::Route",
701
+ Properties: {
702
+ ApiId: { Ref: logicalId },
703
+ RouteKey: `${r.method} ${r.path}`,
704
+ ...r.lambdaId ? { Target: { "Fn::Sub": `integrations/\${${routeId}Integration}` } } : {},
705
+ ...authorizerId ? { AuthorizationType: "CUSTOM", AuthorizerId: { Ref: authorizerId } } : {}
706
+ }
707
+ }]);
708
+ if (r.lambdaId) {
709
+ entries.push([`${routeId}Integration`, {
710
+ Type: "AWS::ApiGatewayV2::Integration",
711
+ Properties: {
712
+ ApiId: { Ref: logicalId },
713
+ IntegrationType: "AWS_PROXY",
714
+ IntegrationUri: { "Fn::Sub": `arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${r.lambdaId}.Arn}/invocations` },
715
+ PayloadFormatVersion: "2.0"
716
+ }
717
+ }]);
718
+ }
719
+ }
720
+ return entries;
721
+ }
722
+ // ── Policy ────────────────────────────────────────────────────────────
723
+ case "Policy.IAM": {
724
+ const statements = props.statements ?? [];
725
+ const attachType = props.attachType;
726
+ const attachTo = props.attachTo.replace(/[^a-zA-Z0-9]/g, "");
727
+ const principalService = attachType === "lambda" ? "lambda.amazonaws.com" : "ec2.amazonaws.com";
728
+ const managedPolicies = attachType === "lambda" ? ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"] : attachType === "compute" ? ["arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"] : [];
729
+ const roleLogicalId = `${logicalId}Role`;
730
+ const roleResource = {
731
+ Type: "AWS::IAM::Role",
732
+ Properties: {
733
+ RoleName: { "Fn::Sub": `${attachTo}-role-\${AWS::StackName}` },
734
+ AssumeRolePolicyDocument: {
735
+ Version: "2012-10-17",
736
+ Statement: [{ Effect: "Allow", Principal: { Service: principalService }, Action: "sts:AssumeRole" }]
737
+ },
738
+ ManagedPolicyArns: managedPolicies,
739
+ Policies: [{
740
+ PolicyName: logicalId,
741
+ PolicyDocument: {
742
+ Version: "2012-10-17",
743
+ Statement: statements.map((s) => ({
744
+ Effect: s.effect,
745
+ Action: s.actions,
746
+ Resource: s.resources ?? ["*"],
747
+ ...s.conditions ? { Condition: s.conditions } : {}
748
+ }))
749
+ }
750
+ }],
751
+ Tags: [{ Key: "Name", Value: roleLogicalId }]
752
+ }
753
+ };
754
+ if (attachType === "compute") {
755
+ return [[roleLogicalId, roleResource], [`${logicalId}InstanceProfile`, {
756
+ Type: "AWS::IAM::InstanceProfile",
757
+ Properties: {
758
+ InstanceProfileName: { "Fn::Sub": `${attachTo}-profile-\${AWS::StackName}` },
759
+ Roles: [{ Ref: roleLogicalId }]
760
+ }
761
+ }]];
762
+ }
763
+ return [[roleLogicalId, roleResource]];
764
+ }
765
+ // ── Events ────────────────────────────────────────────────────────────
766
+ case "Events.EventBridge": {
767
+ const rules = props.rules ?? [];
768
+ const busName = props.busName ?? "default";
769
+ const entries = [];
770
+ if (busName !== "default") {
771
+ entries.push([`${logicalId}Bus`, { Type: "AWS::Events::EventBus", Properties: { Name: busName } }]);
772
+ }
773
+ for (const r of rules) {
774
+ const ruleName = (r.name ?? "rule").replace(/[^a-zA-Z0-9]/g, "");
775
+ const pattern = {};
776
+ if (r.source)
777
+ pattern["source"] = r.source;
778
+ if (r.detailTypes)
779
+ pattern["detail-type"] = r.detailTypes;
780
+ entries.push([`${logicalId}${ruleName}Rule`, {
781
+ Type: "AWS::Events::Rule",
782
+ Properties: {
783
+ Name: r.name,
784
+ EventBusName: busName,
785
+ EventPattern: pattern,
786
+ State: "ENABLED",
787
+ ...r.targetArn ? { Targets: [{ Id: `${ruleName}Target`, Arn: r.targetArn }] } : {}
788
+ }
789
+ }]);
790
+ }
791
+ return entries;
792
+ }
793
+ // ── Workflow ──────────────────────────────────────────────────────────
794
+ case "Workflow.StepFunctions": {
795
+ const steps = props.steps ?? [];
796
+ const definition = {
797
+ Comment: props.description ?? `Workflow ${construct.id}`,
798
+ StartAt: steps[0]?.name ?? "Start",
799
+ States: Object.fromEntries(steps.map((s, i) => [s.name, {
800
+ Type: s.type ?? "Task",
801
+ Resource: s.resource ?? "",
802
+ ...s.description ? { Comment: s.description } : {},
803
+ ...i < steps.length - 1 ? { Next: steps[i + 1].name } : { End: true }
804
+ }]))
805
+ };
806
+ return [[logicalId, {
807
+ Type: "AWS::StepFunctions::StateMachine",
808
+ Properties: {
809
+ StateMachineName: construct.id,
810
+ StateMachineType: props.type ?? "STANDARD",
811
+ DefinitionString: { "Fn::Sub": JSON.stringify(definition) },
812
+ RoleArn: { "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/StepFunctionsExecutionRole" },
813
+ LoggingConfiguration: { Level: "ERROR", IncludeExecutionData: false }
814
+ }
815
+ }]];
816
+ }
817
+ // ── Messaging ─────────────────────────────────────────────────────────
818
+ case "Messaging.Queue": {
819
+ const fifo = props.fifo ?? false;
820
+ return [[logicalId, {
821
+ Type: "AWS::SQS::Queue",
822
+ Properties: {
823
+ QueueName: fifo ? `${construct.id}.fifo` : construct.id,
824
+ VisibilityTimeout: props.visibilityTimeoutSeconds ?? 30,
825
+ MessageRetentionPeriod: props.messageRetentionSeconds ?? 345600,
826
+ DelaySeconds: props.delaySeconds ?? 0,
827
+ FifoQueue: fifo,
828
+ SqsManagedSseEnabled: props.encrypted ?? true,
829
+ ...props.dlqArn ? { RedrivePolicy: { deadLetterTargetArn: props.dlqArn, maxReceiveCount: props.maxReceiveCount ?? 3 } } : {}
830
+ }
831
+ }]];
832
+ }
833
+ case "Messaging.Topic": {
834
+ const fifo = props.fifo ?? false;
835
+ const subscriptions = props.subscriptions ?? [];
836
+ return [[logicalId, {
837
+ Type: "AWS::SNS::Topic",
838
+ Properties: {
839
+ TopicName: fifo ? `${construct.id}.fifo` : construct.id,
840
+ DisplayName: props.displayName ?? construct.id,
841
+ FifoTopic: fifo,
842
+ ...props.encrypted ? { KmsMasterKeyId: "alias/aws/sns" } : {},
843
+ Subscription: subscriptions.map((s) => ({ Protocol: s.protocol, Endpoint: s.endpoint }))
844
+ }
845
+ }]];
846
+ }
847
+ // ── Secret / Certificate ──────────────────────────────────────────────
848
+ case "Secret.Vault": {
849
+ return [[logicalId, {
850
+ Type: "AWS::SecretsManager::Secret",
851
+ Properties: {
852
+ Name: construct.id,
853
+ Description: props.description ?? `Secret ${construct.id}`,
854
+ ...props.kmsKeyId ? { KmsKeyId: props.kmsKeyId } : {},
855
+ ...props.replicaRegions ? { ReplicaRegions: props.replicaRegions.map((r) => ({ Region: r })) } : {}
856
+ }
857
+ }]];
858
+ }
859
+ case "Certificate.TLS": {
860
+ const sans = props.subjectAlternativeNames ?? [];
861
+ return [[logicalId, {
862
+ Type: "AWS::CertificateManager::Certificate",
863
+ Properties: {
864
+ DomainName: props.domainName,
865
+ ValidationMethod: props.validationMethod ?? "DNS",
866
+ ...sans.length > 0 ? { SubjectAlternativeNames: sans } : {},
867
+ Tags: [{ Key: "Name", Value: construct.id }]
868
+ }
869
+ }]];
870
+ }
871
+ // ── Monitoring ────────────────────────────────────────────────────────
872
+ case "Monitoring.Alarm": {
873
+ const dimensions = props.dimensions;
874
+ return [[logicalId, {
875
+ Type: "AWS::CloudWatch::Alarm",
876
+ Properties: {
877
+ AlarmName: construct.id,
878
+ MetricName: props.metricName,
879
+ Namespace: props.namespace ?? "AWS/Lambda",
880
+ Threshold: props.threshold,
881
+ EvaluationPeriods: props.evaluationPeriods ?? 2,
882
+ Period: props.periodSeconds ?? 60,
883
+ ComparisonOperator: props.comparisonOperator ?? "GreaterThanThreshold",
884
+ Statistic: props.statistic ?? "Average",
885
+ TreatMissingData: props.treatMissingData ?? "notBreaching",
886
+ ...props.alarmActions ? { AlarmActions: props.alarmActions } : {},
887
+ ...props.okActions ? { OKActions: props.okActions } : {},
888
+ ...dimensions ? { Dimensions: Object.entries(dimensions).map(([k, v]) => ({ Name: k, Value: v })) } : {}
889
+ }
890
+ }]];
891
+ }
892
+ case "Monitoring.Dashboard": {
893
+ const widgets = props.widgets ?? [];
894
+ const dashBody = {
895
+ widgets: widgets.map((w, i) => ({
896
+ type: w.type === "text" ? "text" : "metric",
897
+ x: i % 3 * 8,
898
+ y: Math.floor(i / 3) * 6,
899
+ width: 8,
900
+ height: 6,
901
+ properties: w.type === "text" ? { markdown: w.markdown ?? w.title } : {
902
+ title: w.title,
903
+ metrics: [[
904
+ w.namespace ?? "AWS/Lambda",
905
+ w.metricName,
906
+ ...w.dimensions ? Object.entries(w.dimensions).flat() : []
907
+ ]],
908
+ period: w.period ?? 60,
909
+ stat: w.stat ?? "Average",
910
+ view: "timeSeries"
911
+ }
912
+ }))
913
+ };
914
+ return [[logicalId, {
915
+ Type: "AWS::CloudWatch::Dashboard",
916
+ Properties: {
917
+ DashboardName: construct.id,
918
+ DashboardBody: JSON.stringify(dashBody)
919
+ }
920
+ }]];
921
+ }
922
+ case "Logging.Stream": {
923
+ const filters = props.subscriptionFilters ?? [];
924
+ const entries = [[logicalId, {
925
+ Type: "AWS::Logs::LogGroup",
926
+ Properties: {
927
+ LogGroupName: `/iacmp/${construct.id}`,
928
+ RetentionInDays: props.retentionDays ?? 30,
929
+ ...props.kmsKeyId ? { KmsKeyId: props.kmsKeyId } : {}
930
+ }
931
+ }]];
932
+ for (const f of filters) {
933
+ entries.push([`${logicalId}${f.name.replace(/[^a-zA-Z0-9]/g, "")}Filter`, {
934
+ Type: "AWS::Logs::SubscriptionFilter",
935
+ Properties: {
936
+ LogGroupName: { Ref: logicalId },
937
+ FilterName: f.name,
938
+ FilterPattern: f.filterPattern,
939
+ DestinationArn: f.destinationArn
940
+ }
941
+ }]);
942
+ }
943
+ return entries;
944
+ }
945
+ case "Custom.Resource": {
946
+ const cfn = props.cloudformation;
947
+ if (!cfn)
948
+ return [];
949
+ return [[logicalId, { Type: cfn.type, Properties: cfn.properties }]];
950
+ }
951
+ default:
952
+ console.warn(`[aws] Construct type '${construct.type}' nao suportado \u2014 descartado.`);
953
+ return [];
954
+ }
955
+ }
956
+ function synthesizeVPCChildren(logicalId, cidr, maxAzs, resources) {
957
+ if (!maxAzs || maxAzs <= 0)
958
+ return;
959
+ const azLetters = ["a", "b", "c", "d", "e", "f"].slice(0, maxAzs);
960
+ const cidrBase = cidr.split(".").slice(0, 2).join(".");
961
+ const igwId = `${logicalId}IGW`;
962
+ resources[igwId] = { Type: "AWS::EC2::InternetGateway", Properties: { Tags: [{ Key: "Name", Value: igwId }] } };
963
+ resources[`${igwId}Attachment`] = {
964
+ Type: "AWS::EC2::VPCGatewayAttachment",
965
+ Properties: { VpcId: { Ref: logicalId }, InternetGatewayId: { Ref: igwId } }
966
+ };
967
+ const pubRTId = `${logicalId}PublicRT`;
968
+ resources[pubRTId] = { Type: "AWS::EC2::RouteTable", Properties: { VpcId: { Ref: logicalId }, Tags: [{ Key: "Name", Value: pubRTId }] } };
969
+ resources[`${pubRTId}DefaultRoute`] = {
970
+ Type: "AWS::EC2::Route",
971
+ Properties: { RouteTableId: { Ref: pubRTId }, DestinationCidrBlock: "0.0.0.0/0", GatewayId: { Ref: igwId } }
972
+ };
973
+ azLetters.forEach((az, i) => {
974
+ const pubSubnetId = `${logicalId}PublicSubnet${az.toUpperCase()}`;
975
+ const privSubnetId = `${logicalId}PrivateSubnet${az.toUpperCase()}`;
976
+ resources[pubSubnetId] = {
977
+ Type: "AWS::EC2::Subnet",
978
+ Properties: {
979
+ VpcId: { Ref: logicalId },
980
+ CidrBlock: `${cidrBase}.${i * 2}.0/24`,
981
+ AvailabilityZone: { "Fn::Select": [i, { "Fn::GetAZs": "" }] },
982
+ MapPublicIpOnLaunch: true,
983
+ Tags: [{ Key: "Name", Value: pubSubnetId }]
984
+ }
985
+ };
986
+ resources[`${pubSubnetId}RTAssoc`] = {
987
+ Type: "AWS::EC2::SubnetRouteTableAssociation",
988
+ Properties: { SubnetId: { Ref: pubSubnetId }, RouteTableId: { Ref: pubRTId } }
989
+ };
990
+ resources[privSubnetId] = {
991
+ Type: "AWS::EC2::Subnet",
992
+ Properties: {
993
+ VpcId: { Ref: logicalId },
994
+ CidrBlock: `${cidrBase}.${i * 2 + 1}.0/24`,
995
+ AvailabilityZone: { "Fn::Select": [i, { "Fn::GetAZs": "" }] },
996
+ Tags: [{ Key: "Name", Value: privSubnetId }]
997
+ }
998
+ };
999
+ });
1000
+ }
1001
+ function synthesize(stack) {
1002
+ const resources = {};
1003
+ for (const construct of stack.constructs) {
1004
+ const entries = synthesizeConstruct(construct);
1005
+ for (const [id, resource] of entries) {
1006
+ resources[id] = resource;
1007
+ }
1008
+ if (construct.type === "Network.VPC") {
1009
+ const p = construct.props;
1010
+ const logicalId = construct.id.replace(/[^a-zA-Z0-9]/g, "");
1011
+ synthesizeVPCChildren(logicalId, p.cidr ?? "10.0.0.0/16", p.maxAzs ?? 0, resources);
1012
+ }
1013
+ }
1014
+ return {
1015
+ AWSTemplateFormatVersion: "2010-09-09",
1016
+ Description: `Stack ${stack.name} \u2014 gerada pelo iacmp`,
1017
+ Resources: resources
1018
+ };
1019
+ }
1020
+ }
1021
+ });
1022
+
1023
+ // ../providers/aws/dist/provider.js
1024
+ var require_provider = __commonJS({
1025
+ "../providers/aws/dist/provider.js"(exports2) {
1026
+ "use strict";
1027
+ Object.defineProperty(exports2, "__esModule", { value: true });
1028
+ exports2.AWSProvider = void 0;
1029
+ var cloudformation_1 = require_cloudformation();
1030
+ var AWSProvider2 = class {
1031
+ name = "aws";
1032
+ synthesize(stack) {
1033
+ return (0, cloudformation_1.synthesize)(stack);
1034
+ }
1035
+ };
1036
+ exports2.AWSProvider = AWSProvider2;
1037
+ }
1038
+ });
1039
+
1040
+ // ../providers/aws/dist/index.js
1041
+ var require_dist = __commonJS({
1042
+ "../providers/aws/dist/index.js"(exports2) {
1043
+ "use strict";
1044
+ Object.defineProperty(exports2, "__esModule", { value: true });
1045
+ exports2.synthesize = exports2.AWSProvider = void 0;
1046
+ var provider_1 = require_provider();
1047
+ Object.defineProperty(exports2, "AWSProvider", { enumerable: true, get: function() {
1048
+ return provider_1.AWSProvider;
1049
+ } });
1050
+ var cloudformation_1 = require_cloudformation();
1051
+ Object.defineProperty(exports2, "synthesize", { enumerable: true, get: function() {
1052
+ return cloudformation_1.synthesize;
1053
+ } });
1054
+ }
1055
+ });
1056
+
1057
+ // ../providers/azure/dist/synth/arm.js
1058
+ var require_arm = __commonJS({
1059
+ "../providers/azure/dist/synth/arm.js"(exports2) {
1060
+ "use strict";
1061
+ Object.defineProperty(exports2, "__esModule", { value: true });
1062
+ exports2.synthesize = synthesize;
1063
+ var INSTANCE_TYPE_MAP = {
1064
+ small: "Standard_B1s",
1065
+ medium: "Standard_B2s",
1066
+ large: "Standard_B4ms"
1067
+ };
1068
+ var IMAGE_MAP = {
1069
+ "ubuntu": { publisher: "Canonical", offer: "UbuntuServer", sku: "22_04-lts", version: "latest", isWindows: false },
1070
+ "ubuntu-22.04": { publisher: "Canonical", offer: "UbuntuServer", sku: "22_04-lts", version: "latest", isWindows: false },
1071
+ "ubuntu-20.04": { publisher: "Canonical", offer: "UbuntuServer", sku: "20_04-lts", version: "latest", isWindows: false },
1072
+ "windows-2022": { publisher: "MicrosoftWindowsServer", offer: "WindowsServer", sku: "2022-Datacenter", version: "latest", isWindows: true },
1073
+ "windows-2019": { publisher: "MicrosoftWindowsServer", offer: "WindowsServer", sku: "2019-Datacenter", version: "latest", isWindows: true },
1074
+ "windows-2016": { publisher: "MicrosoftWindowsServer", offer: "WindowsServer", sku: "2016-Datacenter", version: "latest", isWindows: true }
1075
+ };
1076
+ function resolveAzureImage(image) {
1077
+ const mapped = IMAGE_MAP[image];
1078
+ if (mapped) {
1079
+ const { isWindows, ...ref } = mapped;
1080
+ return { imageReference: ref, isWindows };
1081
+ }
1082
+ return { imageReference: { offer: image }, isWindows: false };
1083
+ }
1084
+ var CACHE_SKU_MAP = {
1085
+ small: { name: "Standard", family: "C", capacity: 1 },
1086
+ medium: { name: "Standard", family: "C", capacity: 2 },
1087
+ large: { name: "Premium", family: "P", capacity: 1 }
1088
+ };
1089
+ function tag(name) {
1090
+ return { Name: name };
1091
+ }
1092
+ function synthesizeConstruct(construct) {
1093
+ const props = construct.props;
1094
+ const location = props.region ?? "[resourceGroup().location]";
1095
+ switch (construct.type) {
1096
+ // ── Compute ──────────────────────────────────────────────────────────
1097
+ case "Compute.Instance": {
1098
+ const { imageReference, isWindows } = resolveAzureImage(props.image ?? "ubuntu");
1099
+ return [{
1100
+ type: "Microsoft.Compute/virtualMachines",
1101
+ apiVersion: "2023-03-01",
1102
+ name: construct.id,
1103
+ location,
1104
+ tags: tag(construct.id),
1105
+ properties: {
1106
+ hardwareProfile: { vmSize: INSTANCE_TYPE_MAP[props.instanceType] ?? "Standard_B1s" },
1107
+ storageProfile: { imageReference },
1108
+ osProfile: {
1109
+ computerName: construct.id,
1110
+ adminUsername: isWindows ? "adminuser" : "azureuser",
1111
+ ...isWindows ? { windowsConfiguration: { provisionVMAgent: true, enableAutomaticUpdates: true } } : { linuxConfiguration: { disablePasswordAuthentication: true } }
1112
+ }
1113
+ }
1114
+ }];
1115
+ }
1116
+ case "Compute.AutoScaling": {
1117
+ const { imageReference: asImageRef, isWindows: asIsWindows } = resolveAzureImage(props.image ?? "ubuntu");
1118
+ const vmssBody = {
1119
+ type: "Microsoft.Compute/virtualMachineScaleSets",
1120
+ apiVersion: "2023-03-01",
1121
+ name: construct.id,
1122
+ location,
1123
+ tags: tag(construct.id),
1124
+ sku: {
1125
+ name: INSTANCE_TYPE_MAP[props.instanceType] ?? "Standard_B1s",
1126
+ tier: "Standard",
1127
+ capacity: props.desiredCapacity ?? props.minCapacity
1128
+ },
1129
+ properties: {
1130
+ overprovision: true,
1131
+ upgradePolicy: { mode: "Automatic" },
1132
+ virtualMachineProfile: {
1133
+ storageProfile: { imageReference: asImageRef },
1134
+ osProfile: {
1135
+ computerNamePrefix: construct.id.slice(0, 9),
1136
+ adminUsername: asIsWindows ? "adminuser" : "azureuser",
1137
+ ...asIsWindows ? { windowsConfiguration: { provisionVMAgent: true, enableAutomaticUpdates: true } } : { linuxConfiguration: { disablePasswordAuthentication: true } }
1138
+ }
1139
+ }
1140
+ }
1141
+ };
1142
+ const autoscaleBody = {
1143
+ type: "Microsoft.Insights/autoscaleSettings",
1144
+ apiVersion: "2022-10-01",
1145
+ name: `${construct.id}-autoscale`,
1146
+ location,
1147
+ properties: {
1148
+ enabled: true,
1149
+ targetResourceUri: `[resourceId('Microsoft.Compute/virtualMachineScaleSets', '${construct.id}')]`,
1150
+ profiles: [{
1151
+ name: "default",
1152
+ capacity: {
1153
+ minimum: String(props.minCapacity ?? 1),
1154
+ maximum: String(props.maxCapacity ?? 10),
1155
+ default: String(props.desiredCapacity ?? props.minCapacity ?? 1)
1156
+ },
1157
+ rules: props.targetCpuUtilization ? [{
1158
+ metricTrigger: {
1159
+ metricName: "Percentage CPU",
1160
+ metricResourceUri: `[resourceId('Microsoft.Compute/virtualMachineScaleSets', '${construct.id}')]`,
1161
+ timeGrain: "PT1M",
1162
+ statistic: "Average",
1163
+ timeWindow: "PT5M",
1164
+ timeAggregation: "Average",
1165
+ operator: "GreaterThan",
1166
+ threshold: props.targetCpuUtilization
1167
+ },
1168
+ scaleAction: { direction: "Increase", type: "ChangeCount", value: "1", cooldown: "PT5M" }
1169
+ }] : []
1170
+ }]
1171
+ }
1172
+ };
1173
+ return [vmssBody, autoscaleBody];
1174
+ }
1175
+ case "Compute.Container": {
1176
+ const environment = props.environment ?? {};
1177
+ const envVars = Object.entries(environment).map(([k, v]) => ({ name: k, value: v }));
1178
+ return [{
1179
+ type: "Microsoft.ContainerInstance/containerGroups",
1180
+ apiVersion: "2023-05-01",
1181
+ name: construct.id,
1182
+ location,
1183
+ tags: tag(construct.id),
1184
+ properties: {
1185
+ containers: [{
1186
+ name: construct.id,
1187
+ properties: {
1188
+ image: props.image,
1189
+ resources: {
1190
+ requests: {
1191
+ cpu: Math.round((props.cpu ?? 256) / 1024 * 10) / 10,
1192
+ memoryInGB: Math.round((props.memory ?? 512) / 1024 * 10) / 10
1193
+ }
1194
+ },
1195
+ ports: props.port ? [{ port: props.port, protocol: "TCP" }] : [],
1196
+ environmentVariables: envVars
1197
+ }
1198
+ }],
1199
+ osType: "Linux",
1200
+ ipAddress: {
1201
+ type: "Public",
1202
+ ports: props.port ? [{ port: props.port, protocol: "TCP" }] : []
1203
+ },
1204
+ restartPolicy: "OnFailure"
1205
+ }
1206
+ }];
1207
+ }
1208
+ case "Compute.Kubernetes": {
1209
+ const nodeType = INSTANCE_TYPE_MAP[props.nodeInstanceType] ?? "Standard_B2s";
1210
+ return [{
1211
+ type: "Microsoft.ContainerService/managedClusters",
1212
+ apiVersion: "2023-05-01",
1213
+ name: construct.id,
1214
+ location,
1215
+ tags: tag(construct.id),
1216
+ identity: { type: "SystemAssigned" },
1217
+ properties: {
1218
+ kubernetesVersion: props.version ?? "1.29",
1219
+ dnsPrefix: construct.id,
1220
+ enableRBAC: true,
1221
+ agentPoolProfiles: [{
1222
+ name: "nodepool1",
1223
+ count: props.desiredNodes ?? 2,
1224
+ minCount: props.minNodes ?? 1,
1225
+ maxCount: props.maxNodes ?? 3,
1226
+ enableAutoScaling: true,
1227
+ vmSize: nodeType,
1228
+ mode: "System"
1229
+ }],
1230
+ apiServerAccessProfile: {
1231
+ enablePrivateCluster: props.privateCluster ?? false
1232
+ },
1233
+ networkProfile: { networkPlugin: "kubenet", loadBalancerSku: "standard" }
1234
+ }
1235
+ }];
1236
+ }
1237
+ // ── Storage ───────────────────────────────────────────────────────────
1238
+ case "Storage.Bucket": {
1239
+ const resources = [{
1240
+ type: "Microsoft.Storage/storageAccounts",
1241
+ apiVersion: "2023-01-01",
1242
+ name: construct.id.toLowerCase().replace(/[^a-z0-9]/g, ""),
1243
+ location,
1244
+ kind: "StorageV2",
1245
+ sku: { name: "Standard_LRS" },
1246
+ tags: tag(construct.id),
1247
+ properties: {
1248
+ allowBlobPublicAccess: props.publicAccess ?? false,
1249
+ supportsHttpsTrafficOnly: true,
1250
+ minimumTlsVersion: "TLS1_2"
1251
+ }
1252
+ }];
1253
+ if (props.versioning) {
1254
+ resources.push({
1255
+ type: "Microsoft.Storage/storageAccounts/blobServices",
1256
+ apiVersion: "2023-01-01",
1257
+ name: `${construct.id.toLowerCase().replace(/[^a-z0-9]/g, "")}/default`,
1258
+ location,
1259
+ properties: { isVersioningEnabled: true }
1260
+ });
1261
+ }
1262
+ return resources;
1263
+ }
1264
+ case "Storage.FileSystem":
1265
+ return [{
1266
+ type: "Microsoft.Storage/storageAccounts/fileServices/shares",
1267
+ apiVersion: "2023-01-01",
1268
+ name: `${construct.id.toLowerCase().replace(/[^a-z0-9]/g, "")}share/default/${construct.id}`,
1269
+ location,
1270
+ properties: {
1271
+ shareQuota: 100,
1272
+ enabledProtocols: "SMB",
1273
+ accessTier: "Hot"
1274
+ }
1275
+ }];
1276
+ case "Storage.Archive":
1277
+ return [{
1278
+ type: "Microsoft.Storage/storageAccounts",
1279
+ apiVersion: "2023-01-01",
1280
+ name: `${construct.id.toLowerCase().replace(/[^a-z0-9]/g, "")}arc`,
1281
+ location,
1282
+ kind: "BlobStorage",
1283
+ sku: { name: "Standard_LRS" },
1284
+ tags: tag(construct.id),
1285
+ properties: {
1286
+ accessTier: "Archive",
1287
+ allowBlobPublicAccess: false,
1288
+ supportsHttpsTrafficOnly: true,
1289
+ minimumTlsVersion: "TLS1_2"
1290
+ }
1291
+ }];
1292
+ // ── Network ───────────────────────────────────────────────────────────
1293
+ case "Network.VPC":
1294
+ return [{
1295
+ type: "Microsoft.Network/virtualNetworks",
1296
+ apiVersion: "2023-04-01",
1297
+ name: construct.id,
1298
+ location,
1299
+ tags: tag(construct.id),
1300
+ properties: {
1301
+ addressSpace: { addressPrefixes: [props.cidr ?? "10.0.0.0/16"] },
1302
+ dhcpOptions: { dnsServers: [] }
1303
+ }
1304
+ }];
1305
+ case "Network.Subnet": {
1306
+ const vnetName = props.vpcId;
1307
+ return [{
1308
+ type: "Microsoft.Network/virtualNetworks/subnets",
1309
+ apiVersion: "2023-04-01",
1310
+ name: `${vnetName}/${construct.id}`,
1311
+ location,
1312
+ dependsOn: [`[resourceId('Microsoft.Network/virtualNetworks', '${vnetName}')]`],
1313
+ properties: {
1314
+ addressPrefix: props.cidr,
1315
+ privateEndpointNetworkPolicies: props.public ? "Disabled" : "Enabled"
1316
+ }
1317
+ }];
1318
+ }
1319
+ case "Network.SecurityGroup": {
1320
+ const ingress = props.ingressRules ?? [];
1321
+ const egress = props.egressRules ?? [];
1322
+ const protocolMap = {
1323
+ tcp: "Tcp",
1324
+ udp: "Udp",
1325
+ icmp: "Icmp",
1326
+ "-1": "*"
1327
+ };
1328
+ const mapProtocol = (raw) => {
1329
+ if (raw === void 0 || raw === null)
1330
+ return "*";
1331
+ const key = String(raw).toLowerCase();
1332
+ return protocolMap[key] ?? "*";
1333
+ };
1334
+ const secRules = [
1335
+ ...ingress.map((r, i) => {
1336
+ if (r.cidr === void 0) {
1337
+ console.warn(`[azure] Security group rule sem CIDR; usando * \u2014 defina props.cidr explicitamente (${construct.id} ingress[${i}])`);
1338
+ }
1339
+ return {
1340
+ name: `ingress-rule-${i}`,
1341
+ properties: {
1342
+ priority: 100 + i,
1343
+ direction: "Inbound",
1344
+ access: "Allow",
1345
+ protocol: mapProtocol(r.protocol),
1346
+ sourcePortRange: "*",
1347
+ destinationPortRange: r.fromPort === r.toPort ? String(r.fromPort) : `${r.fromPort}-${r.toPort}`,
1348
+ sourceAddressPrefix: r.cidr ?? "*",
1349
+ destinationAddressPrefix: "*",
1350
+ description: r.description ?? ""
1351
+ }
1352
+ };
1353
+ }),
1354
+ ...egress.map((r, i) => ({
1355
+ name: `egress-rule-${i}`,
1356
+ properties: {
1357
+ priority: 200 + i,
1358
+ direction: "Outbound",
1359
+ access: "Allow",
1360
+ protocol: mapProtocol(r.protocol),
1361
+ sourcePortRange: "*",
1362
+ destinationPortRange: r.fromPort === r.toPort ? String(r.fromPort) : `${r.fromPort}-${r.toPort}`,
1363
+ sourceAddressPrefix: "*",
1364
+ destinationAddressPrefix: r.cidr ?? "*",
1365
+ description: r.description ?? ""
1366
+ }
1367
+ }))
1368
+ ];
1369
+ return [{
1370
+ type: "Microsoft.Network/networkSecurityGroups",
1371
+ apiVersion: "2023-04-01",
1372
+ name: construct.id,
1373
+ location,
1374
+ tags: tag(construct.id),
1375
+ properties: { securityRules: secRules }
1376
+ }];
1377
+ }
1378
+ case "Network.WAF": {
1379
+ const rules = props.rules ?? [];
1380
+ const customRules = rules.filter((r) => !r.managedGroup).map((r, i) => ({
1381
+ name: r.name ?? `custom-rule-${i}`,
1382
+ priority: r.priority ?? i + 1,
1383
+ ruleType: "MatchRule",
1384
+ action: r.action ?? "Block",
1385
+ matchConditions: [{
1386
+ matchVariables: [{ variableName: "RequestHeaders", selector: "User-Agent" }],
1387
+ operator: "Contains",
1388
+ matchValues: r.matchValues ?? ["BadBot"]
1389
+ }]
1390
+ }));
1391
+ const managedRules = rules.filter((r) => r.managedGroup).map((r) => ({
1392
+ ruleSetType: r.managedGroup ?? "OWASP",
1393
+ ruleSetVersion: "3.2"
1394
+ }));
1395
+ return [{
1396
+ type: "Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies",
1397
+ apiVersion: "2023-04-01",
1398
+ name: construct.id,
1399
+ location,
1400
+ tags: tag(construct.id),
1401
+ properties: {
1402
+ policySettings: {
1403
+ requestBodyCheck: true,
1404
+ maxRequestBodySizeInKb: 128,
1405
+ fileUploadLimitInMb: 100,
1406
+ state: "Enabled",
1407
+ mode: props.mode ?? "Prevention"
1408
+ },
1409
+ customRules,
1410
+ managedRules: {
1411
+ managedRuleSets: managedRules.length > 0 ? managedRules : [{ ruleSetType: "OWASP", ruleSetVersion: "3.2" }]
1412
+ }
1413
+ }
1414
+ }];
1415
+ }
1416
+ case "Network.LoadBalancer": {
1417
+ const lbType = props.type ?? "application";
1418
+ const isInternal = props.scheme === "internal";
1419
+ const listeners = props.listeners ?? [];
1420
+ const targetGroups = props.targetGroups ?? [];
1421
+ if (lbType === "application") {
1422
+ return [{
1423
+ type: "Microsoft.Network/applicationGateways",
1424
+ apiVersion: "2023-04-01",
1425
+ name: construct.id,
1426
+ location,
1427
+ tags: tag(construct.id),
1428
+ properties: {
1429
+ sku: { name: "Standard_v2", tier: "Standard_v2", capacity: 2 },
1430
+ frontendIPConfigurations: [{ name: "appGatewayFrontendIP", properties: { publicIPAddress: null } }],
1431
+ frontendPorts: listeners.map((l, i) => ({ name: `port${i}`, properties: { port: l.port } })),
1432
+ backendAddressPools: targetGroups.map((tg) => ({ name: tg.name, properties: {} })),
1433
+ httpListeners: listeners.map((l, i) => ({
1434
+ name: `listener${i}`,
1435
+ properties: {
1436
+ frontendPort: { id: `port${i}` },
1437
+ protocol: l.protocol.toLowerCase() === "https" ? "Https" : "Http"
1438
+ }
1439
+ })),
1440
+ requestRoutingRules: [{
1441
+ name: "rule1",
1442
+ properties: { ruleType: "Basic", priority: 100 }
1443
+ }]
1444
+ }
1445
+ }];
1446
+ }
1447
+ return [{
1448
+ type: "Microsoft.Network/loadBalancers",
1449
+ apiVersion: "2023-04-01",
1450
+ name: construct.id,
1451
+ location,
1452
+ tags: tag(construct.id),
1453
+ sku: { name: "Standard" },
1454
+ properties: {
1455
+ frontendIPConfigurations: [{ name: "loadBalancerFrontEnd", properties: {} }],
1456
+ backendAddressPools: targetGroups.map((tg) => ({ name: tg.name })),
1457
+ loadBalancingRules: listeners.map((l, i) => ({
1458
+ name: `rule${i}`,
1459
+ properties: {
1460
+ frontendPort: l.port,
1461
+ backendPort: l.port,
1462
+ protocol: l.protocol.toLowerCase() === "tcp" ? "Tcp" : "Udp",
1463
+ enableFloatingIP: false
1464
+ }
1465
+ }))
1466
+ }
1467
+ }];
1468
+ }
1469
+ case "Network.CDN": {
1470
+ const origins = props.origins ?? [];
1471
+ return [{
1472
+ type: "Microsoft.Cdn/profiles/endpoints",
1473
+ apiVersion: "2023-05-01",
1474
+ name: `${construct.id}-profile/${construct.id}`,
1475
+ location,
1476
+ tags: tag(construct.id),
1477
+ properties: {
1478
+ originHostHeader: origins[0]?.domainName ?? "",
1479
+ isHttpAllowed: false,
1480
+ isHttpsAllowed: true,
1481
+ origins: origins.map((o) => ({
1482
+ name: o.id ?? "origin1",
1483
+ properties: { hostName: o.domainName, httpPort: 80, httpsPort: 443 }
1484
+ })),
1485
+ deliveryPolicy: {
1486
+ rules: [{
1487
+ name: "enforceHttps",
1488
+ order: 1,
1489
+ conditions: [{ name: "RequestScheme", parameters: { operator: "Equal", matchValues: ["HTTP"] } }],
1490
+ actions: [{ name: "UrlRedirect", parameters: { redirectType: "Moved", destinationProtocol: "Https" } }]
1491
+ }]
1492
+ }
1493
+ }
1494
+ }];
1495
+ }
1496
+ case "Network.Dns": {
1497
+ const records = props.records ?? [];
1498
+ const zoneName = props.zoneName;
1499
+ const resources = [{
1500
+ type: "Microsoft.Network/dnsZones",
1501
+ apiVersion: "2018-05-01",
1502
+ name: zoneName,
1503
+ location: "global",
1504
+ tags: tag(construct.id),
1505
+ properties: {}
1506
+ }];
1507
+ for (const r of records) {
1508
+ const recordType = r.type.toLowerCase();
1509
+ resources.push({
1510
+ type: `Microsoft.Network/dnsZones/${recordType}`,
1511
+ apiVersion: "2018-05-01",
1512
+ name: `${zoneName}/${r.name}`,
1513
+ location: "global",
1514
+ dependsOn: [`[resourceId('Microsoft.Network/dnsZones', '${zoneName}')]`],
1515
+ properties: {
1516
+ TTL: r.ttl ?? 300,
1517
+ [`${recordType.toUpperCase()}Records`]: r.values.map((v) => ({ value: v }))
1518
+ }
1519
+ });
1520
+ }
1521
+ return resources;
1522
+ }
1523
+ // ── Database ──────────────────────────────────────────────────────────
1524
+ case "Database.SQL": {
1525
+ const engine = props.engine ?? "mysql";
1526
+ const serverName = `${construct.id.toLowerCase()}-server`;
1527
+ const storageBytes = (props.storageGb ?? 20) * 1024 * 1024 * 1024;
1528
+ const zoneRedundant = props.multiAz ?? false;
1529
+ if (engine === "mysql") {
1530
+ return [
1531
+ {
1532
+ type: "Microsoft.DBforMySQL/flexibleServers",
1533
+ apiVersion: "2023-06-30",
1534
+ name: serverName,
1535
+ location,
1536
+ tags: tag(construct.id),
1537
+ sku: { name: "Standard_D2ds_v4", tier: "GeneralPurpose" },
1538
+ properties: {
1539
+ administratorLogin: "mysqladmin",
1540
+ administratorLoginPassword: "[parameters('adminPassword')]",
1541
+ version: "8.0.21",
1542
+ storage: { storageSizeGB: props.storageGb ?? 20, autoGrow: "Enabled" },
1543
+ backup: { backupRetentionDays: props.backupRetentionDays ?? 7, geoRedundantBackup: "Disabled" },
1544
+ highAvailability: { mode: zoneRedundant ? "ZoneRedundant" : "Disabled" }
1545
+ }
1546
+ }
1547
+ ];
1548
+ }
1549
+ if (engine === "postgres") {
1550
+ return [
1551
+ {
1552
+ type: "Microsoft.DBforPostgreSQL/flexibleServers",
1553
+ apiVersion: "2023-06-01-preview",
1554
+ name: serverName,
1555
+ location,
1556
+ tags: tag(construct.id),
1557
+ sku: { name: "Standard_D2ds_v5", tier: "GeneralPurpose" },
1558
+ properties: {
1559
+ administratorLogin: "pgadmin",
1560
+ administratorLoginPassword: "[parameters('adminPassword')]",
1561
+ version: "15",
1562
+ storage: { storageSizeGB: props.storageGb ?? 32 },
1563
+ backup: { backupRetentionDays: props.backupRetentionDays ?? 7, geoRedundantBackup: "Disabled" },
1564
+ highAvailability: { mode: zoneRedundant ? "ZoneRedundant" : "Disabled" }
1565
+ }
1566
+ }
1567
+ ];
1568
+ }
1569
+ if (engine === "mariadb") {
1570
+ return [
1571
+ {
1572
+ type: "Microsoft.DBforMariaDB/servers",
1573
+ apiVersion: "2018-06-01",
1574
+ name: serverName,
1575
+ location,
1576
+ tags: tag(construct.id),
1577
+ sku: { name: "GP_Gen5_2", tier: "GeneralPurpose", capacity: 2, family: "Gen5" },
1578
+ properties: {
1579
+ administratorLogin: "mariadbadmin",
1580
+ administratorLoginPassword: "[parameters('adminPassword')]",
1581
+ version: "10.3",
1582
+ storageProfile: {
1583
+ storageMB: (props.storageGb ?? 20) * 1024,
1584
+ backupRetentionDays: props.backupRetentionDays ?? 7,
1585
+ geoRedundantBackup: "Disabled"
1586
+ }
1587
+ }
1588
+ }
1589
+ ];
1590
+ }
1591
+ if (engine === "oracle") {
1592
+ return [
1593
+ {
1594
+ type: "Oracle.Database/cloudExadataInfrastructures",
1595
+ apiVersion: "2023-09-01",
1596
+ name: serverName,
1597
+ location,
1598
+ tags: tag(construct.id),
1599
+ properties: {
1600
+ displayName: construct.id,
1601
+ shape: "Exadata.X9M",
1602
+ computeCount: 2,
1603
+ storageCount: 3
1604
+ }
1605
+ }
1606
+ ];
1607
+ }
1608
+ const edition = props.edition ?? "Standard";
1609
+ return [
1610
+ {
1611
+ type: "Microsoft.Sql/servers",
1612
+ apiVersion: "2023-02-01-preview",
1613
+ name: serverName,
1614
+ location,
1615
+ tags: tag(construct.id),
1616
+ properties: {
1617
+ administratorLogin: "sqladmin",
1618
+ administratorLoginPassword: "[parameters('adminPassword')]",
1619
+ version: "12.0"
1620
+ }
1621
+ },
1622
+ {
1623
+ type: "Microsoft.Sql/servers/databases",
1624
+ apiVersion: "2023-02-01-preview",
1625
+ name: `${serverName}/${construct.id}`,
1626
+ location,
1627
+ dependsOn: [`[resourceId('Microsoft.Sql/servers', '${serverName}')]`],
1628
+ sku: { name: edition === "ee" ? "BusinessCritical" : "Standard", tier: edition === "ee" ? "BusinessCritical" : "Standard" },
1629
+ properties: {
1630
+ collation: "SQL_Latin1_General_CP1_CI_AS",
1631
+ maxSizeBytes: storageBytes,
1632
+ zoneRedundant
1633
+ }
1634
+ }
1635
+ ];
1636
+ }
1637
+ case "Database.DocumentDB":
1638
+ return [{
1639
+ type: "Microsoft.DocumentDB/databaseAccounts",
1640
+ apiVersion: "2023-04-15",
1641
+ name: construct.id.toLowerCase(),
1642
+ location,
1643
+ tags: tag(construct.id),
1644
+ properties: {
1645
+ databaseAccountOfferType: "Standard",
1646
+ kind: "MongoDB",
1647
+ locations: [{ locationName: location, failoverPriority: 0, isZoneRedundant: false }],
1648
+ backupPolicy: { type: "Periodic", periodicModeProperties: { backupIntervalInMinutes: 1440, backupRetentionIntervalInHours: 168 } },
1649
+ enableAutomaticFailover: props.deletionProtection ?? false
1650
+ }
1651
+ }];
1652
+ case "Database.DynamoDB":
1653
+ return [{
1654
+ type: "Microsoft.DocumentDB/databaseAccounts",
1655
+ apiVersion: "2023-04-15",
1656
+ name: construct.id.toLowerCase(),
1657
+ location,
1658
+ tags: tag(construct.id),
1659
+ properties: {
1660
+ databaseAccountOfferType: "Standard",
1661
+ kind: "GlobalDocumentDB",
1662
+ capabilities: [{ name: "EnableTable" }],
1663
+ locations: [{ locationName: location, failoverPriority: 0, isZoneRedundant: false }],
1664
+ backupPolicy: { type: "Continuous", continuousModeProperties: { tier: "Continuous30Days" } }
1665
+ }
1666
+ }];
1667
+ // ── Cache ─────────────────────────────────────────────────────────────
1668
+ case "Cache.Redis": {
1669
+ const skuInfo = CACHE_SKU_MAP[props.nodeType ?? "small"];
1670
+ return [{
1671
+ type: "Microsoft.Cache/redis",
1672
+ apiVersion: "2023-08-01",
1673
+ name: construct.id,
1674
+ location,
1675
+ tags: tag(construct.id),
1676
+ sku: { name: skuInfo.name, family: skuInfo.family, capacity: skuInfo.capacity },
1677
+ properties: {
1678
+ enableNonSslPort: false,
1679
+ minimumTlsVersion: "1.2",
1680
+ redisVersion: props.version ?? "7.0",
1681
+ redisConfiguration: {
1682
+ "maxmemory-policy": "volatile-lru"
1683
+ }
1684
+ }
1685
+ }];
1686
+ }
1687
+ case "Cache.Memcached":
1688
+ return [{
1689
+ type: "Microsoft.Cache/redis",
1690
+ apiVersion: "2023-08-01",
1691
+ name: `${construct.id}-cache`,
1692
+ location,
1693
+ tags: tag(construct.id),
1694
+ sku: { name: "Standard", family: "C", capacity: props.numCacheNodes ?? 2 },
1695
+ properties: {
1696
+ enableNonSslPort: false,
1697
+ minimumTlsVersion: "1.2",
1698
+ redisConfiguration: {}
1699
+ }
1700
+ }];
1701
+ // ── Function ──────────────────────────────────────────────────────────
1702
+ case "Function.Lambda": {
1703
+ const environment = props.environment ?? {};
1704
+ const runtimeMap = {
1705
+ "nodejs20": "node|20",
1706
+ "nodejs18": "node|18",
1707
+ "python3.12": "python|3.12",
1708
+ "python3.11": "python|3.11",
1709
+ "java21": "java|21",
1710
+ "go1.x": "go|1",
1711
+ "dotnet8": "dotnet|8"
1712
+ };
1713
+ const baseSettings = [
1714
+ { name: "FUNCTIONS_EXTENSION_VERSION", value: "~4" },
1715
+ { name: "FUNCTIONS_WORKER_RUNTIME", value: props.runtime?.startsWith("nodejs") ? "node" : props.runtime?.startsWith("python") ? "python" : "dotnet" }
1716
+ ];
1717
+ const envSettings = Object.entries(environment).map(([k, v]) => ({ name: k, value: v }));
1718
+ return [{
1719
+ type: "Microsoft.Web/sites",
1720
+ apiVersion: "2023-01-01",
1721
+ name: construct.id,
1722
+ location,
1723
+ kind: "functionapp",
1724
+ tags: tag(construct.id),
1725
+ properties: {
1726
+ siteConfig: {
1727
+ linuxFxVersion: runtimeMap[props.runtime ?? "nodejs20"] ?? "node|20",
1728
+ appSettings: [...baseSettings, ...envSettings],
1729
+ ...props.memory ? { memoryAllocation: props.memory } : {}
1730
+ },
1731
+ httpsOnly: true
1732
+ }
1733
+ }];
1734
+ }
1735
+ case "Function.ApiGateway": {
1736
+ const apimName = props.name ?? construct.id;
1737
+ const authorizerLambdaId = props.authorizerLambdaId;
1738
+ const resources = [{
1739
+ type: "Microsoft.ApiManagement/service",
1740
+ apiVersion: "2023-05-01-preview",
1741
+ name: apimName,
1742
+ location,
1743
+ tags: tag(construct.id),
1744
+ sku: { name: "Consumption", capacity: 0 },
1745
+ properties: {
1746
+ publisherEmail: "admin@example.com",
1747
+ publisherName: construct.id,
1748
+ virtualNetworkType: "None",
1749
+ customProperties: {
1750
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10": "false",
1751
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11": "false"
1752
+ }
1753
+ }
1754
+ }];
1755
+ if (authorizerLambdaId) {
1756
+ resources.push({
1757
+ type: "Microsoft.ApiManagement/service/backends",
1758
+ apiVersion: "2023-05-01-preview",
1759
+ name: `${apimName}/authorizer-backend`,
1760
+ location,
1761
+ properties: {
1762
+ description: `Lambda authorizer backend (${authorizerLambdaId})`,
1763
+ url: `[reference(resourceId('Microsoft.Web/sites', '${authorizerLambdaId}')).defaultHostName]`,
1764
+ protocol: "http"
1765
+ },
1766
+ dependsOn: [
1767
+ `[resourceId('Microsoft.ApiManagement/service', '${apimName}')]`,
1768
+ `[resourceId('Microsoft.Web/sites', '${authorizerLambdaId}')]`
1769
+ ]
1770
+ });
1771
+ }
1772
+ return resources;
1773
+ }
1774
+ // ── Policy ────────────────────────────────────────────────────────────
1775
+ case "Policy.IAM": {
1776
+ const statements = props.statements ?? [];
1777
+ const attachType = props.attachType;
1778
+ const attachTo = props.attachTo;
1779
+ const actions = [];
1780
+ const notActions = [];
1781
+ for (const s of statements) {
1782
+ const stmtActions = s.actions;
1783
+ if (s.effect === "Allow")
1784
+ actions.push(...stmtActions);
1785
+ else
1786
+ notActions.push(...stmtActions);
1787
+ }
1788
+ const principalType = attachType === "lambda" ? "FunctionApp" : "VirtualMachine";
1789
+ return [
1790
+ {
1791
+ type: "Microsoft.Authorization/roleDefinitions",
1792
+ apiVersion: "2022-04-01",
1793
+ name: `[guid(resourceGroup().id, '${construct.id}')]`,
1794
+ location,
1795
+ properties: {
1796
+ roleName: `${construct.id}-role`,
1797
+ description: props.description ?? `Custom role for ${attachTo}`,
1798
+ type: "CustomRole",
1799
+ permissions: [{
1800
+ actions,
1801
+ notActions,
1802
+ dataActions: [],
1803
+ notDataActions: []
1804
+ }],
1805
+ assignableScopes: [`[resourceGroup().id]`]
1806
+ }
1807
+ },
1808
+ {
1809
+ type: "Microsoft.Authorization/roleAssignments",
1810
+ apiVersion: "2022-04-01",
1811
+ name: `[guid(resourceGroup().id, '${attachTo}', '${construct.id}')]`,
1812
+ location,
1813
+ dependsOn: [`[resourceId('Microsoft.Authorization/roleDefinitions', guid(resourceGroup().id, '${construct.id}'))]`],
1814
+ properties: {
1815
+ roleDefinitionId: `[resourceId('Microsoft.Authorization/roleDefinitions', guid(resourceGroup().id, '${construct.id}'))]`,
1816
+ principalType,
1817
+ description: `Role assignment for ${attachTo} (${principalType})`
1818
+ }
1819
+ }
1820
+ ];
1821
+ }
1822
+ // ── Events ────────────────────────────────────────────────────────────
1823
+ case "Events.EventBridge": {
1824
+ const rules = props.rules ?? [];
1825
+ return [{
1826
+ type: "Microsoft.EventGrid/namespaces",
1827
+ apiVersion: "2023-06-01-preview",
1828
+ name: props.busName ?? construct.id,
1829
+ location,
1830
+ tags: tag(construct.id),
1831
+ sku: { name: "Standard", capacity: 1 },
1832
+ properties: {
1833
+ topicsConfiguration: {},
1834
+ topicSpacesConfiguration: {
1835
+ state: "Enabled"
1836
+ }
1837
+ }
1838
+ }, ...rules.map((r) => ({
1839
+ type: "Microsoft.EventGrid/eventSubscriptions",
1840
+ apiVersion: "2022-06-15",
1841
+ name: r.name ?? construct.id,
1842
+ location,
1843
+ properties: {
1844
+ destination: {
1845
+ endpointType: "WebHook",
1846
+ properties: { endpointUrl: r.targetArn ?? "" }
1847
+ },
1848
+ filter: {
1849
+ includedEventTypes: r.detailTypes ?? ["*"]
1850
+ }
1851
+ }
1852
+ }))];
1853
+ }
1854
+ // ── Workflow ──────────────────────────────────────────────────────────
1855
+ case "Workflow.StepFunctions": {
1856
+ const steps = props.steps ?? [];
1857
+ const definition = {
1858
+ definition: {
1859
+ "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
1860
+ contentVersion: "1.0.0.0",
1861
+ triggers: {},
1862
+ actions: Object.fromEntries(steps.map((s, i) => [s.name, {
1863
+ type: "Http",
1864
+ inputs: { method: "POST", uri: s.resource ?? "" },
1865
+ runAfter: i > 0 ? { [steps[i - 1].name]: ["Succeeded"] } : {}
1866
+ }]))
1867
+ }
1868
+ };
1869
+ return [{
1870
+ type: "Microsoft.Logic/workflows",
1871
+ apiVersion: "2019-05-01",
1872
+ name: construct.id,
1873
+ location,
1874
+ tags: tag(construct.id),
1875
+ properties: definition
1876
+ }];
1877
+ }
1878
+ // ── Messaging ─────────────────────────────────────────────────────────
1879
+ case "Messaging.Queue": {
1880
+ const nsName = `${construct.id}-ns`;
1881
+ return [
1882
+ {
1883
+ type: "Microsoft.ServiceBus/namespaces",
1884
+ apiVersion: "2022-10-01-preview",
1885
+ name: nsName,
1886
+ location,
1887
+ tags: tag(construct.id),
1888
+ sku: { name: "Standard", tier: "Standard" },
1889
+ properties: {}
1890
+ },
1891
+ {
1892
+ type: "Microsoft.ServiceBus/namespaces/queues",
1893
+ apiVersion: "2022-10-01-preview",
1894
+ name: `${nsName}/${construct.id}`,
1895
+ location,
1896
+ dependsOn: [`[resourceId('Microsoft.ServiceBus/namespaces', '${nsName}')]`],
1897
+ properties: {
1898
+ lockDuration: `PT${props.visibilityTimeoutSeconds ?? 30}S`,
1899
+ maxSizeInMegabytes: 1024,
1900
+ requiresDuplicateDetection: false,
1901
+ requiresSession: false,
1902
+ defaultMessageTimeToLive: `P${Math.floor((props.messageRetentionSeconds ?? 345600) / 86400)}D`,
1903
+ deadLetteringOnMessageExpiration: false
1904
+ }
1905
+ }
1906
+ ];
1907
+ }
1908
+ case "Messaging.Topic": {
1909
+ const nsName = `${construct.id}-ns`;
1910
+ const subscriptions = props.subscriptions ?? [];
1911
+ const resources = [
1912
+ {
1913
+ type: "Microsoft.ServiceBus/namespaces",
1914
+ apiVersion: "2022-10-01-preview",
1915
+ name: nsName,
1916
+ location,
1917
+ tags: tag(construct.id),
1918
+ sku: { name: "Standard", tier: "Standard" },
1919
+ properties: {}
1920
+ },
1921
+ {
1922
+ type: "Microsoft.ServiceBus/namespaces/topics",
1923
+ apiVersion: "2022-10-01-preview",
1924
+ name: `${nsName}/${construct.id}`,
1925
+ location,
1926
+ dependsOn: [`[resourceId('Microsoft.ServiceBus/namespaces', '${nsName}')]`],
1927
+ properties: {
1928
+ defaultMessageTimeToLive: "P14D",
1929
+ requiresDuplicateDetection: false
1930
+ }
1931
+ }
1932
+ ];
1933
+ subscriptions.forEach((s, i) => {
1934
+ resources.push({
1935
+ type: "Microsoft.ServiceBus/namespaces/topics/subscriptions",
1936
+ apiVersion: "2022-10-01-preview",
1937
+ name: `${nsName}/${construct.id}/sub-${i}`,
1938
+ location,
1939
+ dependsOn: [`[resourceId('Microsoft.ServiceBus/namespaces/topics', '${nsName}', '${construct.id}')]`],
1940
+ properties: {
1941
+ lockDuration: "PT30S",
1942
+ deadLetteringOnMessageExpiration: false,
1943
+ forwardTo: s.endpoint
1944
+ }
1945
+ });
1946
+ });
1947
+ return resources;
1948
+ }
1949
+ // ── Secret / Certificate ──────────────────────────────────────────────
1950
+ case "Secret.Vault": {
1951
+ const kvName = `${construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 24)}-kv`;
1952
+ return [{
1953
+ type: "Microsoft.KeyVault/vaults",
1954
+ apiVersion: "2023-02-01",
1955
+ name: kvName,
1956
+ location,
1957
+ tags: tag(construct.id),
1958
+ properties: {
1959
+ sku: { family: "A", name: "standard" },
1960
+ tenantId: "[subscription().tenantId]",
1961
+ enableSoftDelete: true,
1962
+ softDeleteRetentionInDays: 90,
1963
+ enablePurgeProtection: true,
1964
+ enabledForDeployment: false,
1965
+ accessPolicies: []
1966
+ }
1967
+ }];
1968
+ }
1969
+ case "Certificate.TLS": {
1970
+ const kvName = `${construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 20)}-kv`;
1971
+ return [{
1972
+ type: "Microsoft.KeyVault/vaults/certificates",
1973
+ apiVersion: "2023-02-01",
1974
+ name: `${kvName}/${construct.id.replace(/[^a-zA-Z0-9-]/g, "-")}`,
1975
+ location,
1976
+ properties: {
1977
+ properties: {
1978
+ x509CertificateProperties: {
1979
+ subject: `CN=${props.domainName}`,
1980
+ subjectAlternativeNames: {
1981
+ dnsNames: [props.domainName, ...props.subjectAlternativeNames ?? []]
1982
+ },
1983
+ validityInMonths: 12
1984
+ },
1985
+ issuerParameters: { name: "Self", issuerName: "Self" },
1986
+ keyProperties: { keyType: "RSA", keySize: 2048, exportable: true }
1987
+ }
1988
+ }
1989
+ }];
1990
+ }
1991
+ // ── Monitoring ────────────────────────────────────────────────────────
1992
+ case "Monitoring.Alarm": {
1993
+ const dimensions = props.dimensions;
1994
+ const operatorMap = {
1995
+ GreaterThanThreshold: "GreaterThan",
1996
+ LessThanThreshold: "LessThan",
1997
+ GreaterThanOrEqualToThreshold: "GreaterThanOrEqual",
1998
+ LessThanOrEqualToThreshold: "LessThanOrEqual"
1999
+ };
2000
+ return [{
2001
+ type: "Microsoft.Insights/metricAlerts",
2002
+ apiVersion: "2018-03-01",
2003
+ name: construct.id,
2004
+ location: "global",
2005
+ tags: tag(construct.id),
2006
+ properties: {
2007
+ description: `Alarm for ${props.metricName}`,
2008
+ severity: 2,
2009
+ enabled: true,
2010
+ evaluationFrequency: `PT${props.periodSeconds ?? 60}S`,
2011
+ windowSize: `PT${(props.periodSeconds ?? 60) * (props.evaluationPeriods ?? 2)}S`,
2012
+ criteria: {
2013
+ "odata.type": "Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria",
2014
+ allOf: [{
2015
+ name: "criterion1",
2016
+ metricName: props.metricName,
2017
+ metricNamespace: props.namespace ?? "Microsoft.Web/sites",
2018
+ operator: operatorMap[props.comparisonOperator ?? "GreaterThanThreshold"] ?? "GreaterThan",
2019
+ threshold: props.threshold,
2020
+ timeAggregation: props.statistic ?? "Average",
2021
+ dimensions: dimensions ? Object.entries(dimensions).map(([k, v]) => ({ name: k, operator: "Include", values: [v] })) : []
2022
+ }]
2023
+ },
2024
+ actions: (props.alarmActions ?? []).map((a) => ({ actionGroupId: a }))
2025
+ }
2026
+ }];
2027
+ }
2028
+ case "Monitoring.Dashboard": {
2029
+ const widgets = props.widgets ?? [];
2030
+ return [{
2031
+ type: "Microsoft.Portal/dashboards",
2032
+ apiVersion: "2020-09-01-preview",
2033
+ name: construct.id,
2034
+ location,
2035
+ tags: {
2036
+ "hidden-title": construct.id
2037
+ },
2038
+ properties: {
2039
+ lenses: [{
2040
+ order: 0,
2041
+ parts: widgets.map((w, i) => ({
2042
+ position: { x: i % 3 * 4, y: Math.floor(i / 3) * 4, colSpan: 4, rowSpan: 4 },
2043
+ metadata: {
2044
+ type: "Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart",
2045
+ settings: {
2046
+ content: {
2047
+ options: { chart: { metrics: [{ name: w.metricName, resourceMetadata: {} }] } },
2048
+ title: w.title
2049
+ }
2050
+ }
2051
+ }
2052
+ }))
2053
+ }]
2054
+ }
2055
+ }];
2056
+ }
2057
+ case "Logging.Stream": {
2058
+ const wsName = `${construct.id}-law`;
2059
+ return [{
2060
+ type: "Microsoft.OperationalInsights/workspaces",
2061
+ apiVersion: "2022-10-01",
2062
+ name: wsName,
2063
+ location,
2064
+ tags: tag(construct.id),
2065
+ properties: {
2066
+ sku: { name: "PerGB2018" },
2067
+ retentionInDays: props.retentionDays ?? 30,
2068
+ features: { enableLogAccessUsingOnlyResourcePermissions: true }
2069
+ }
2070
+ }];
2071
+ }
2072
+ case "Custom.Resource": {
2073
+ const arm = props.arm;
2074
+ if (!arm)
2075
+ return [];
2076
+ return [{
2077
+ type: arm.type,
2078
+ apiVersion: arm.apiVersion,
2079
+ name: props.name ?? construct.id,
2080
+ location,
2081
+ tags: tag(construct.id),
2082
+ properties: arm.properties,
2083
+ ...arm.sku ? { sku: arm.sku } : {},
2084
+ ...arm.kind ? { kind: arm.kind } : {}
2085
+ }];
2086
+ }
2087
+ default:
2088
+ console.warn(`[azure] Construct type '${construct.type}' nao suportado \u2014 descartado.`);
2089
+ return [];
2090
+ }
2091
+ }
2092
+ function synthesize(stack) {
2093
+ const resources = [];
2094
+ for (const construct of stack.constructs) {
2095
+ const result = synthesizeConstruct(construct);
2096
+ resources.push(...result);
2097
+ }
2098
+ return {
2099
+ $schema: "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
2100
+ contentVersion: "1.0.0.0",
2101
+ resources
2102
+ };
2103
+ }
2104
+ }
2105
+ });
2106
+
2107
+ // ../providers/azure/dist/provider.js
2108
+ var require_provider2 = __commonJS({
2109
+ "../providers/azure/dist/provider.js"(exports2) {
2110
+ "use strict";
2111
+ Object.defineProperty(exports2, "__esModule", { value: true });
2112
+ exports2.AzureProvider = void 0;
2113
+ var arm_1 = require_arm();
2114
+ var AzureProvider2 = class {
2115
+ name = "azure";
2116
+ synthesize(stack) {
2117
+ return (0, arm_1.synthesize)(stack);
2118
+ }
2119
+ };
2120
+ exports2.AzureProvider = AzureProvider2;
2121
+ }
2122
+ });
2123
+
2124
+ // ../providers/azure/dist/index.js
2125
+ var require_dist2 = __commonJS({
2126
+ "../providers/azure/dist/index.js"(exports2) {
2127
+ "use strict";
2128
+ Object.defineProperty(exports2, "__esModule", { value: true });
2129
+ exports2.synthesize = exports2.AzureProvider = void 0;
2130
+ var provider_1 = require_provider2();
2131
+ Object.defineProperty(exports2, "AzureProvider", { enumerable: true, get: function() {
2132
+ return provider_1.AzureProvider;
2133
+ } });
2134
+ var arm_1 = require_arm();
2135
+ Object.defineProperty(exports2, "synthesize", { enumerable: true, get: function() {
2136
+ return arm_1.synthesize;
2137
+ } });
2138
+ }
2139
+ });
2140
+
2141
+ // ../providers/gcp/dist/synth/deployment-manager.js
2142
+ var require_deployment_manager = __commonJS({
2143
+ "../providers/gcp/dist/synth/deployment-manager.js"(exports2) {
2144
+ "use strict";
2145
+ Object.defineProperty(exports2, "__esModule", { value: true });
2146
+ exports2.synthesize = synthesize;
2147
+ var INSTANCE_TYPE_MAP = {
2148
+ small: "e2-small",
2149
+ medium: "e2-medium",
2150
+ large: "e2-standard-4"
2151
+ };
2152
+ var GCP_IMAGE_MAP = {
2153
+ "ubuntu": "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts",
2154
+ "ubuntu-22.04": "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts",
2155
+ "ubuntu-20.04": "projects/ubuntu-os-cloud/global/images/family/ubuntu-2004-lts",
2156
+ "windows-2022": "projects/windows-cloud/global/images/family/windows-2022",
2157
+ "windows-2019": "projects/windows-cloud/global/images/family/windows-2019",
2158
+ "windows-2016": "projects/windows-cloud/global/images/family/windows-2016"
2159
+ };
2160
+ function resolveGcpImage(image) {
2161
+ if (!image)
2162
+ return "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts";
2163
+ return GCP_IMAGE_MAP[image] ?? `global/images/${image}`;
2164
+ }
2165
+ var CACHE_TIER_MAP = {
2166
+ small: "BASIC",
2167
+ medium: "STANDARD_HA",
2168
+ large: "STANDARD_HA"
2169
+ };
2170
+ var CACHE_CAPACITY_MAP = {
2171
+ small: 1,
2172
+ medium: 5,
2173
+ large: 16
2174
+ };
2175
+ var K8S_MACHINE_MAP = {
2176
+ small: "e2-medium",
2177
+ medium: "e2-standard-4",
2178
+ large: "n2-standard-8"
2179
+ };
2180
+ function gcpRegion(regionOrZone) {
2181
+ if (!regionOrZone)
2182
+ return "us-central1";
2183
+ const parts = regionOrZone.split("-");
2184
+ if (parts.length >= 3 && parts[parts.length - 1].match(/^[a-z]$/)) {
2185
+ return parts.slice(0, -1).join("-");
2186
+ }
2187
+ return regionOrZone;
2188
+ }
2189
+ function synthesizeConstruct(construct) {
2190
+ const props = construct.props;
2191
+ const zone = props.region ?? "us-central1-a";
2192
+ const region = gcpRegion(props.region);
2193
+ switch (construct.type) {
2194
+ // ── Compute ──────────────────────────────────────────────────────────
2195
+ case "Compute.Instance":
2196
+ return [{
2197
+ name: construct.id,
2198
+ type: "compute.v1.instance",
2199
+ properties: {
2200
+ zone,
2201
+ machineType: `zones/${zone}/machineTypes/${INSTANCE_TYPE_MAP[props.instanceType] ?? "e2-small"}`,
2202
+ disks: [{
2203
+ boot: true,
2204
+ autoDelete: true,
2205
+ initializeParams: {
2206
+ sourceImage: resolveGcpImage(props.image)
2207
+ }
2208
+ }],
2209
+ networkInterfaces: [{ network: "global/networks/default" }]
2210
+ }
2211
+ }];
2212
+ case "Compute.AutoScaling": {
2213
+ const machineType = INSTANCE_TYPE_MAP[props.instanceType] ?? "e2-small";
2214
+ const templateName = `${construct.id}-template`;
2215
+ const resources = [
2216
+ {
2217
+ name: templateName,
2218
+ type: "compute.v1.instanceTemplate",
2219
+ properties: {
2220
+ properties: {
2221
+ machineType,
2222
+ disks: [{
2223
+ boot: true,
2224
+ autoDelete: true,
2225
+ initializeParams: {
2226
+ sourceImage: resolveGcpImage(props.image)
2227
+ }
2228
+ }],
2229
+ networkInterfaces: [{ network: "global/networks/default" }]
2230
+ }
2231
+ }
2232
+ },
2233
+ {
2234
+ name: construct.id,
2235
+ type: "compute.v1.regionInstanceGroupManager",
2236
+ properties: {
2237
+ region,
2238
+ baseInstanceName: construct.id,
2239
+ instanceTemplate: `global/instanceTemplates/${templateName}`,
2240
+ targetSize: props.desiredCapacity ?? props.minCapacity ?? 1
2241
+ }
2242
+ }
2243
+ ];
2244
+ if (props.minCapacity !== void 0 || props.targetCpuUtilization) {
2245
+ resources.push({
2246
+ name: `${construct.id}-autoscaler`,
2247
+ type: "compute.v1.regionAutoscaler",
2248
+ properties: {
2249
+ region,
2250
+ target: `regions/${region}/instanceGroupManagers/${construct.id}`,
2251
+ autoscalingPolicy: {
2252
+ minNumReplicas: props.minCapacity ?? 1,
2253
+ maxNumReplicas: props.maxCapacity ?? 10,
2254
+ coolDownPeriodSec: 60,
2255
+ ...props.targetCpuUtilization ? {
2256
+ cpuUtilization: { utilizationTarget: props.targetCpuUtilization / 100 }
2257
+ } : {}
2258
+ }
2259
+ }
2260
+ });
2261
+ }
2262
+ return resources;
2263
+ }
2264
+ case "Compute.Container": {
2265
+ const environment = props.environment ?? {};
2266
+ const envVars = Object.entries(environment).map(([k, v]) => ({ name: k, value: v }));
2267
+ return [{
2268
+ name: construct.id,
2269
+ type: "run.v2.service",
2270
+ properties: {
2271
+ location: region,
2272
+ template: {
2273
+ containers: [{
2274
+ image: props.image,
2275
+ resources: {
2276
+ limits: {
2277
+ cpu: String(Math.round((props.cpu ?? 256) / 1e3)),
2278
+ memory: `${props.memory ?? 512}Mi`
2279
+ }
2280
+ },
2281
+ ports: props.port ? [{ containerPort: props.port }] : [],
2282
+ env: envVars
2283
+ }],
2284
+ scaling: {
2285
+ minInstanceCount: props.minInstances ?? 0,
2286
+ maxInstanceCount: props.desiredCount ?? 10
2287
+ }
2288
+ },
2289
+ ingress: props.publicIp ? "INGRESS_TRAFFIC_ALL" : "INGRESS_TRAFFIC_INTERNAL_ONLY"
2290
+ }
2291
+ }];
2292
+ }
2293
+ case "Compute.Kubernetes": {
2294
+ const machineType = K8S_MACHINE_MAP[props.nodeInstanceType ?? "medium"] ?? "e2-standard-4";
2295
+ return [{
2296
+ name: construct.id,
2297
+ type: "container.v1.cluster",
2298
+ properties: {
2299
+ location: region,
2300
+ cluster: {
2301
+ name: construct.id,
2302
+ currentMasterVersion: props.version ?? "1.29",
2303
+ initialNodeCount: props.desiredNodes ?? 2,
2304
+ nodePools: [{
2305
+ name: "default-pool",
2306
+ config: {
2307
+ machineType,
2308
+ diskSizeGb: 100,
2309
+ oauthScopes: ["https://www.googleapis.com/auth/cloud-platform"]
2310
+ },
2311
+ autoscaling: {
2312
+ enabled: true,
2313
+ minNodeCount: props.minNodes ?? 1,
2314
+ maxNodeCount: props.maxNodes ?? 3
2315
+ },
2316
+ initialNodeCount: props.desiredNodes ?? 2
2317
+ }],
2318
+ masterAuth: { clientCertificateConfig: { issueClientCertificate: false } },
2319
+ networkConfig: {
2320
+ enableIntraNodeVisibility: true,
2321
+ datapathProvider: "ADVANCED_DATAPATH"
2322
+ },
2323
+ privateClusterConfig: props.privateCluster ? {
2324
+ enablePrivateNodes: true,
2325
+ enablePrivateEndpoint: true,
2326
+ masterIpv4CidrBlock: "172.16.0.32/28"
2327
+ } : {}
2328
+ }
2329
+ }
2330
+ }];
2331
+ }
2332
+ // ── Storage ───────────────────────────────────────────────────────────
2333
+ case "Storage.Bucket": {
2334
+ const lifecycleRules = props.lifecycleRules ?? [];
2335
+ const gcsRules = [];
2336
+ for (const r of lifecycleRules) {
2337
+ const prefixCond = r.prefix ? { matchesPrefix: [r.prefix] } : {};
2338
+ if (r.transitionToGlacierDays) {
2339
+ gcsRules.push({
2340
+ action: { type: "SetStorageClass", storageClass: "ARCHIVE" },
2341
+ condition: { age: r.transitionToGlacierDays, ...prefixCond }
2342
+ });
2343
+ }
2344
+ if (r.expireAfterDays) {
2345
+ gcsRules.push({
2346
+ action: { type: "Delete" },
2347
+ condition: { age: r.expireAfterDays, ...prefixCond }
2348
+ });
2349
+ }
2350
+ }
2351
+ return [{
2352
+ name: construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
2353
+ type: "storage.v1.bucket",
2354
+ properties: {
2355
+ location: props.location ?? "US",
2356
+ versioning: { enabled: props.versioning ?? false },
2357
+ iamConfiguration: {
2358
+ uniformBucketLevelAccess: { enabled: !props.publicAccess }
2359
+ },
2360
+ ...gcsRules.length > 0 ? { lifecycle: { rule: gcsRules } } : {}
2361
+ }
2362
+ }];
2363
+ }
2364
+ case "Storage.FileSystem":
2365
+ return [{
2366
+ name: construct.id.toLowerCase(),
2367
+ type: "file.v1.instance",
2368
+ properties: {
2369
+ location: `${region}-a`,
2370
+ tier: props.performanceMode === "maxIO" ? "HIGH_SCALE_SSD" : "STANDARD",
2371
+ networks: [{ network: "default", modes: ["MODE_IPV4"] }],
2372
+ fileShares: [{
2373
+ name: construct.id,
2374
+ capacityGb: 1024
2375
+ }]
2376
+ }
2377
+ }];
2378
+ case "Storage.Archive":
2379
+ return [{
2380
+ name: `${construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-")}-archive`,
2381
+ type: "storage.v1.bucket",
2382
+ properties: {
2383
+ location: props.location ?? "US",
2384
+ storageClass: "ARCHIVE",
2385
+ iamConfiguration: { uniformBucketLevelAccess: { enabled: true } },
2386
+ lifecycle: {
2387
+ rule: props.retentionDays ? [{
2388
+ action: { type: "Delete" },
2389
+ condition: { age: props.retentionDays }
2390
+ }] : []
2391
+ }
2392
+ }
2393
+ }];
2394
+ // ── Network ───────────────────────────────────────────────────────────
2395
+ case "Network.VPC":
2396
+ return [{
2397
+ name: construct.id,
2398
+ type: "compute.v1.network",
2399
+ properties: {
2400
+ autoCreateSubnetworks: false,
2401
+ routingConfig: { routingMode: "REGIONAL" },
2402
+ description: `VPC ${construct.id}`
2403
+ }
2404
+ }];
2405
+ case "Network.Subnet":
2406
+ return [{
2407
+ name: construct.id,
2408
+ type: "compute.v1.subnetwork",
2409
+ properties: {
2410
+ network: `global/networks/${props.vpcId}`,
2411
+ ipCidrRange: props.cidr,
2412
+ region,
2413
+ privateIpGoogleAccess: !props.public,
2414
+ ...props.availabilityZone ? { description: `AZ: ${props.availabilityZone}` } : {}
2415
+ }
2416
+ }];
2417
+ case "Network.SecurityGroup": {
2418
+ const ingress = props.ingressRules ?? [];
2419
+ const egress = props.egressRules ?? [];
2420
+ const ingressResources = ingress.map((r, i) => {
2421
+ if (r.cidr === void 0) {
2422
+ console.warn(`[gcp] Security group rule sem CIDR; usando 0.0.0.0/0 \u2014 defina props.cidr explicitamente (${construct.id} ingress[${i}])`);
2423
+ }
2424
+ return {
2425
+ name: `${construct.id}-ingress-${i}`,
2426
+ type: "compute.v1.firewall",
2427
+ properties: {
2428
+ network: `global/networks/${props.vpcId}`,
2429
+ direction: "INGRESS",
2430
+ priority: 1e3 + i,
2431
+ allowed: [{
2432
+ IPProtocol: r.protocol === "-1" ? "all" : r.protocol,
2433
+ ...r.protocol !== "-1" ? {
2434
+ ports: r.fromPort === r.toPort ? [`${r.fromPort}`] : [`${r.fromPort}-${r.toPort}`]
2435
+ } : {}
2436
+ }],
2437
+ sourceRanges: [r.cidr ?? "0.0.0.0/0"],
2438
+ description: r.description ?? ""
2439
+ }
2440
+ };
2441
+ });
2442
+ const egressList = egress.length > 0 ? egress : [{
2443
+ protocol: "-1",
2444
+ fromPort: 0,
2445
+ toPort: 0,
2446
+ cidr: "0.0.0.0/0",
2447
+ description: "Allow all egress"
2448
+ }];
2449
+ const egressResources = egressList.map((r, i) => ({
2450
+ name: `${construct.id}-egress-${i}`,
2451
+ type: "compute.v1.firewall",
2452
+ properties: {
2453
+ network: `global/networks/${props.vpcId}`,
2454
+ direction: "EGRESS",
2455
+ priority: 1e3 + i,
2456
+ allowed: [{
2457
+ IPProtocol: r.protocol === "-1" ? "all" : r.protocol,
2458
+ ...r.protocol !== "-1" && r.fromPort !== 0 ? {
2459
+ ports: r.fromPort === r.toPort ? [`${r.fromPort}`] : [`${r.fromPort}-${r.toPort}`]
2460
+ } : {}
2461
+ }],
2462
+ destinationRanges: [r.cidr ?? "0.0.0.0/0"],
2463
+ description: r.description ?? ""
2464
+ }
2465
+ }));
2466
+ return [...ingressResources, ...egressResources];
2467
+ }
2468
+ case "Network.WAF": {
2469
+ const rules = props.rules ?? [];
2470
+ const securityRules = rules.map((r, i) => ({
2471
+ priority: r.priority ?? i + 1,
2472
+ action: r.action ?? "allow",
2473
+ match: r.managedGroup ? { expr: { expression: 'evaluatePreconfiguredExpr("sqli-stable")' } } : {
2474
+ versionedExpr: "SRC_IPS_V1",
2475
+ config: { srcIpRanges: r.sourceIps ?? ["*"] }
2476
+ },
2477
+ description: r.description ?? ""
2478
+ }));
2479
+ return [{
2480
+ name: construct.id,
2481
+ type: "compute.v1.securityPolicy",
2482
+ properties: {
2483
+ description: props.description ?? `WAF ${construct.id}`,
2484
+ rules: [
2485
+ ...securityRules,
2486
+ {
2487
+ priority: 2147483647,
2488
+ action: props.defaultAction ?? "allow",
2489
+ match: { versionedExpr: "SRC_IPS_V1", config: { srcIpRanges: ["*"] } },
2490
+ description: "Default rule"
2491
+ }
2492
+ ]
2493
+ }
2494
+ }];
2495
+ }
2496
+ case "Network.LoadBalancer": {
2497
+ const targetGroups = props.targetGroups ?? [];
2498
+ const lbType = props.type ?? "application";
2499
+ const resources = [{
2500
+ name: `${construct.id}-backend`,
2501
+ type: "compute.v1.backendService",
2502
+ properties: {
2503
+ backends: [],
2504
+ healthChecks: [],
2505
+ protocol: lbType === "network" ? "TCP" : "HTTP",
2506
+ loadBalancingScheme: props.scheme === "internal" ? "INTERNAL" : "EXTERNAL"
2507
+ }
2508
+ }];
2509
+ if (lbType === "application") {
2510
+ resources.push({
2511
+ name: `${construct.id}-url-map`,
2512
+ type: "compute.v1.urlMap",
2513
+ properties: {
2514
+ defaultService: `global/backendServices/${construct.id}-backend`
2515
+ }
2516
+ });
2517
+ resources.push({
2518
+ name: `${construct.id}-http-proxy`,
2519
+ type: "compute.v1.targetHttpProxy",
2520
+ properties: {
2521
+ urlMap: `global/urlMaps/${construct.id}-url-map`
2522
+ }
2523
+ });
2524
+ resources.push({
2525
+ name: `${construct.id}-forwarding-rule`,
2526
+ type: "compute.v1.globalForwardingRule",
2527
+ properties: {
2528
+ target: `global/targetHttpProxies/${construct.id}-http-proxy`,
2529
+ portRange: "80",
2530
+ loadBalancingScheme: "EXTERNAL"
2531
+ }
2532
+ });
2533
+ }
2534
+ return resources;
2535
+ }
2536
+ case "Network.CDN": {
2537
+ const origins = props.origins ?? [];
2538
+ return [
2539
+ {
2540
+ name: `${construct.id}-backend-bucket`,
2541
+ type: "compute.v1.backendBucket",
2542
+ properties: {
2543
+ bucketName: origins[0]?.bucketName ?? origins[0]?.domainName ?? construct.id,
2544
+ enableCdn: true,
2545
+ cdnPolicy: {
2546
+ cacheMode: "CACHE_ALL_STATIC",
2547
+ defaultTtl: 3600,
2548
+ maxTtl: 86400
2549
+ }
2550
+ }
2551
+ },
2552
+ {
2553
+ name: `${construct.id}-url-map`,
2554
+ type: "compute.v1.urlMap",
2555
+ properties: {
2556
+ defaultService: `global/backendBuckets/${construct.id}-backend-bucket`
2557
+ }
2558
+ }
2559
+ ];
2560
+ }
2561
+ case "Network.Dns": {
2562
+ const records = props.records ?? [];
2563
+ const zoneName = props.zoneName.replace(/\./g, "-").replace(/-+$/, "");
2564
+ const resources = [{
2565
+ name: `${zoneName}-zone`,
2566
+ type: "dns.v1.managedZone",
2567
+ properties: {
2568
+ dnsName: `${props.zoneName}.`,
2569
+ description: `DNS zone for ${props.zoneName}`,
2570
+ visibility: "public"
2571
+ }
2572
+ }];
2573
+ if (records.length > 0) {
2574
+ resources.push({
2575
+ name: `${zoneName}-records`,
2576
+ type: "dns.v1.resourceRecordSet",
2577
+ properties: {
2578
+ managedZone: `${zoneName}-zone`,
2579
+ name: `${records[0].name}.`,
2580
+ type: records[0].type,
2581
+ ttl: records[0].ttl ?? 300,
2582
+ rrdatas: records[0].values
2583
+ }
2584
+ });
2585
+ }
2586
+ return resources;
2587
+ }
2588
+ // ── Database ──────────────────────────────────────────────────────────
2589
+ case "Database.SQL": {
2590
+ const engine = props.engine ?? "mysql";
2591
+ const edition = props.edition ?? "";
2592
+ const dbVersionMap = {
2593
+ mysql: "MYSQL_8_0",
2594
+ postgres: "POSTGRES_15",
2595
+ mariadb: "MYSQL_8_0",
2596
+ // MariaDB → MySQL no Cloud SQL (mais compatível)
2597
+ sqlserver: `SQLSERVER_2019_${(edition || "EXPRESS").toUpperCase()}`,
2598
+ oracle: "POSTGRES_15"
2599
+ // Oracle → AlloyDB/PostgreSQL (postgres-compatible)
2600
+ };
2601
+ const dbVersion = dbVersionMap[engine] ?? "MYSQL_8_0";
2602
+ const defaultTier = engine === "sqlserver" ? "db-custom-2-4096" : "db-f1-micro";
2603
+ return [{
2604
+ name: construct.id,
2605
+ type: "sqladmin.v1beta4.instance",
2606
+ properties: {
2607
+ databaseVersion: dbVersion,
2608
+ region,
2609
+ settings: {
2610
+ tier: props.instanceType ?? defaultTier,
2611
+ backupConfiguration: {
2612
+ enabled: true,
2613
+ binaryLogEnabled: engine === "mysql" || engine === "mariadb"
2614
+ },
2615
+ storageAutoResize: true,
2616
+ storageAutoResizeLimit: props.storageGb ?? 20,
2617
+ availabilityType: props.multiAz ? "REGIONAL" : "ZONAL"
2618
+ }
2619
+ }
2620
+ }];
2621
+ }
2622
+ case "Database.DocumentDB":
2623
+ return [{
2624
+ name: construct.id,
2625
+ type: "firestore.v1.database",
2626
+ properties: {
2627
+ locationId: region,
2628
+ type: "FIRESTORE_NATIVE",
2629
+ concurrencyMode: "PESSIMISTIC",
2630
+ appEngineIntegrationMode: "DISABLED",
2631
+ deleteProtectionState: props.deletionProtection ? "DELETE_PROTECTION_ENABLED" : "DELETE_PROTECTION_DISABLED"
2632
+ }
2633
+ }];
2634
+ case "Database.DynamoDB":
2635
+ return [{
2636
+ name: construct.id.toLowerCase(),
2637
+ type: "bigtableadmin.v2.instance",
2638
+ properties: {
2639
+ parent: "projects/PROJECT_ID",
2640
+ instanceId: construct.id.toLowerCase(),
2641
+ instance: {
2642
+ displayName: construct.id,
2643
+ type: "PRODUCTION"
2644
+ },
2645
+ clusters: {
2646
+ "cluster-1": {
2647
+ location: `projects/PROJECT_ID/locations/${region}-a`,
2648
+ serveNodes: 1,
2649
+ defaultStorageType: "SSD"
2650
+ }
2651
+ }
2652
+ }
2653
+ }];
2654
+ // ── Cache ─────────────────────────────────────────────────────────────
2655
+ case "Cache.Redis": {
2656
+ const nodeType = props.nodeType ?? "small";
2657
+ return [{
2658
+ name: construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
2659
+ type: "redis.v1.instance",
2660
+ properties: {
2661
+ region,
2662
+ tier: CACHE_TIER_MAP[nodeType] ?? "BASIC",
2663
+ memorySizeGb: CACHE_CAPACITY_MAP[nodeType] ?? 1,
2664
+ redisVersion: `REDIS_${(props.version ?? "7.0").replace(".", "_")}`,
2665
+ authEnabled: props.transitEncryptionEnabled ?? true,
2666
+ transitEncryptionMode: props.transitEncryptionEnabled ? "SERVER_AUTHENTICATION" : "DISABLED",
2667
+ ...props.subnetGroupName ? { authorizedNetwork: `projects/PROJECT_ID/global/networks/${props.subnetGroupName}` } : {}
2668
+ }
2669
+ }];
2670
+ }
2671
+ case "Cache.Memcached": {
2672
+ const numNodes = props.numCacheNodes ?? 2;
2673
+ return [{
2674
+ name: construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
2675
+ type: "memcache.v1.instance",
2676
+ properties: {
2677
+ region,
2678
+ nodeCount: numNodes,
2679
+ nodeConfig: {
2680
+ cpuCount: 1,
2681
+ memorySizeMb: 1024
2682
+ },
2683
+ ...props.subnetGroupName ? { authorizedNetwork: `projects/PROJECT_ID/global/networks/${props.subnetGroupName}` } : {}
2684
+ }
2685
+ }];
2686
+ }
2687
+ // ── Function ──────────────────────────────────────────────────────────
2688
+ case "Function.Lambda": {
2689
+ const environment = props.environment ?? {};
2690
+ const runtimeMap = {
2691
+ "nodejs20": "nodejs20",
2692
+ "nodejs18": "nodejs18",
2693
+ "python3.12": "python312",
2694
+ "python3.11": "python311",
2695
+ "java21": "java21",
2696
+ "go1.x": "go121",
2697
+ "dotnet8": "dotnet8"
2698
+ };
2699
+ return [{
2700
+ name: construct.id,
2701
+ type: "cloudfunctions.v2.function",
2702
+ properties: {
2703
+ location: region,
2704
+ description: `Function ${construct.id}`,
2705
+ buildConfig: {
2706
+ runtime: runtimeMap[props.runtime ?? "nodejs20"] ?? "nodejs20",
2707
+ entryPoint: props.handler ?? "handler"
2708
+ },
2709
+ serviceConfig: {
2710
+ availableMemory: `${props.memory ?? 128}Mi`,
2711
+ timeoutSeconds: props.timeout ?? 30,
2712
+ ...props.reservedConcurrency ? { maxInstanceCount: props.reservedConcurrency } : {},
2713
+ ...Object.keys(environment).length > 0 ? { environmentVariables: environment } : {}
2714
+ }
2715
+ }
2716
+ }];
2717
+ }
2718
+ case "Function.ApiGateway": {
2719
+ const routes = props.routes ?? [];
2720
+ const authorizerLambdaId = props.authorizerLambdaId;
2721
+ const securityDefinitions = authorizerLambdaId ? {
2722
+ lambdaAuthorizer: {
2723
+ type: "apiKey",
2724
+ name: "Authorization",
2725
+ in: "header",
2726
+ "x-google-authorizer-backend": { address: `https://${region}-PROJECT_ID.cloudfunctions.net/${authorizerLambdaId}` }
2727
+ }
2728
+ } : void 0;
2729
+ return [
2730
+ {
2731
+ name: props.name ?? construct.id,
2732
+ type: "apigateway.v1.api",
2733
+ properties: {
2734
+ displayName: props.name ?? construct.id
2735
+ }
2736
+ },
2737
+ {
2738
+ name: `${construct.id}-config`,
2739
+ type: "apigateway.v1.apiConfig",
2740
+ properties: {
2741
+ api: props.name ?? construct.id,
2742
+ displayName: `${construct.id} config`,
2743
+ openapiDocuments: [{
2744
+ document: {
2745
+ path: "openapi.yaml",
2746
+ contents: Buffer.from(JSON.stringify({
2747
+ openapi: "3.0.0",
2748
+ info: { title: props.name ?? construct.id, version: "1.0" },
2749
+ ...securityDefinitions ? { securityDefinitions } : {},
2750
+ paths: Object.fromEntries(routes.map((r) => [r.path, {
2751
+ [r.method.toLowerCase()]: {
2752
+ "x-google-backend": { address: `https://${region}-PROJECT_ID.cloudfunctions.net/${r.lambdaId ?? "function"}` },
2753
+ ...securityDefinitions ? { security: [{ lambdaAuthorizer: [] }] } : {},
2754
+ responses: { "200": { description: "OK" } }
2755
+ }
2756
+ }]))
2757
+ })).toString("base64")
2758
+ }
2759
+ }]
2760
+ }
2761
+ }
2762
+ ];
2763
+ }
2764
+ // ── Policy ────────────────────────────────────────────────────────────
2765
+ case "Policy.IAM": {
2766
+ const statements = props.statements ?? [];
2767
+ const attachTo = props.attachTo;
2768
+ const serviceAccount = `${attachTo.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 30)}@PROJECT_ID.iam.gserviceaccount.com`;
2769
+ return [
2770
+ {
2771
+ name: `${construct.id}-sa`,
2772
+ type: "iam.v1.serviceAccount",
2773
+ properties: {
2774
+ accountId: attachTo.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 30),
2775
+ displayName: `Service Account for ${attachTo}`,
2776
+ description: props.description ?? ""
2777
+ }
2778
+ },
2779
+ ...statements.map((s, i) => ({
2780
+ name: `${construct.id}-binding-${i}`,
2781
+ type: "gcp-types/cloudresourcemanager-v1:virtual.projects.iamMemberBinding",
2782
+ properties: {
2783
+ resource: "PROJECT_ID",
2784
+ role: s.actions[0]?.startsWith("roles/") ? s.actions[0] : `roles/custom.${construct.id.replace(/[^a-zA-Z0-9]/g, "")}`,
2785
+ member: `serviceAccount:${serviceAccount}`
2786
+ }
2787
+ }))
2788
+ ];
2789
+ }
2790
+ // ── Events ────────────────────────────────────────────────────────────
2791
+ case "Events.EventBridge": {
2792
+ const rules = props.rules ?? [];
2793
+ const resources = [];
2794
+ if (props.busName && props.busName !== "default") {
2795
+ resources.push({
2796
+ name: props.busName,
2797
+ type: "pubsub.v1.topic",
2798
+ properties: {
2799
+ messageStoragePolicy: {
2800
+ allowedPersistenceRegions: [region]
2801
+ }
2802
+ }
2803
+ });
2804
+ }
2805
+ for (const r of rules) {
2806
+ const topicName = `${construct.id}-${r.name ?? "rule"}`;
2807
+ resources.push({
2808
+ name: topicName,
2809
+ type: "pubsub.v1.topic",
2810
+ properties: {
2811
+ messageStoragePolicy: {
2812
+ allowedPersistenceRegions: [region]
2813
+ }
2814
+ }
2815
+ });
2816
+ if (r.targetArn) {
2817
+ resources.push({
2818
+ name: `${topicName}-sub`,
2819
+ type: "pubsub.v1.subscription",
2820
+ properties: {
2821
+ topic: topicName,
2822
+ pushConfig: { pushEndpoint: r.targetArn },
2823
+ ackDeadlineSeconds: 30
2824
+ }
2825
+ });
2826
+ }
2827
+ }
2828
+ return resources;
2829
+ }
2830
+ // ── Workflow ──────────────────────────────────────────────────────────
2831
+ case "Workflow.StepFunctions": {
2832
+ const steps = props.steps ?? [];
2833
+ const definition = {
2834
+ main: {
2835
+ steps: steps.map((s, i) => ({
2836
+ [s.name]: {
2837
+ call: "http.post",
2838
+ args: { url: s.resource ?? "" },
2839
+ ...i < steps.length - 1 ? { next: steps[i + 1].name } : {}
2840
+ }
2841
+ }))
2842
+ }
2843
+ };
2844
+ return [{
2845
+ name: construct.id,
2846
+ type: "workflows.v1.workflow",
2847
+ properties: {
2848
+ region,
2849
+ description: props.description ?? "",
2850
+ sourceContents: JSON.stringify(definition, null, 2)
2851
+ }
2852
+ }];
2853
+ }
2854
+ // ── Messaging ─────────────────────────────────────────────────────────
2855
+ case "Messaging.Queue": {
2856
+ const topicName = construct.id;
2857
+ const subName = `${construct.id}-sub`;
2858
+ return [
2859
+ {
2860
+ name: topicName,
2861
+ type: "pubsub.v1.topic",
2862
+ properties: {
2863
+ messageStoragePolicy: { allowedPersistenceRegions: [region] },
2864
+ ...props.encrypted ? { kmsKeyName: `projects/PROJECT_ID/locations/${region}/keyRings/default/cryptoKeys/default` } : {}
2865
+ }
2866
+ },
2867
+ {
2868
+ name: subName,
2869
+ type: "pubsub.v1.subscription",
2870
+ properties: {
2871
+ topic: topicName,
2872
+ ackDeadlineSeconds: props.visibilityTimeoutSeconds ?? 30,
2873
+ messageRetentionDuration: `${props.messageRetentionSeconds ?? 345600}s`,
2874
+ retainAckedMessages: false,
2875
+ ...props.dlqArn ? {
2876
+ deadLetterPolicy: {
2877
+ deadLetterTopic: props.dlqArn,
2878
+ maxDeliveryAttempts: props.maxReceiveCount ?? 5
2879
+ }
2880
+ } : {}
2881
+ }
2882
+ }
2883
+ ];
2884
+ }
2885
+ case "Messaging.Topic": {
2886
+ const subscriptions = props.subscriptions ?? [];
2887
+ const resources = [{
2888
+ name: construct.id,
2889
+ type: "pubsub.v1.topic",
2890
+ properties: {
2891
+ messageStoragePolicy: { allowedPersistenceRegions: [region] },
2892
+ ...props.encrypted ? { kmsKeyName: `projects/PROJECT_ID/locations/${region}/keyRings/default/cryptoKeys/default` } : {}
2893
+ }
2894
+ }];
2895
+ subscriptions.forEach((s, i) => {
2896
+ resources.push({
2897
+ name: `${construct.id}-sub-${i}`,
2898
+ type: "pubsub.v1.subscription",
2899
+ properties: {
2900
+ topic: construct.id,
2901
+ ackDeadlineSeconds: 30,
2902
+ ...s.protocol === "https" || s.protocol === "http" ? { pushConfig: { pushEndpoint: s.endpoint } } : {}
2903
+ }
2904
+ });
2905
+ });
2906
+ return resources;
2907
+ }
2908
+ // ── Secret / Certificate ──────────────────────────────────────────────
2909
+ case "Secret.Vault": {
2910
+ const resources = [{
2911
+ name: construct.id.replace(/[^a-zA-Z0-9_-]/g, "-"),
2912
+ type: "secretmanager.v1.secret",
2913
+ properties: {
2914
+ parent: "projects/PROJECT_ID",
2915
+ secretId: construct.id.replace(/[^a-zA-Z0-9_-]/g, "-"),
2916
+ secret: {
2917
+ replication: {
2918
+ automatic: {}
2919
+ },
2920
+ ...props.kmsKeyId ? { customerManagedEncryption: { kmsKeyName: props.kmsKeyId } } : {},
2921
+ ...props.rotationDays ? {
2922
+ rotation: {
2923
+ nextRotationTime: new Date(Date.now() + props.rotationDays * 864e5).toISOString(),
2924
+ rotationPeriod: `${props.rotationDays * 86400}s`
2925
+ }
2926
+ } : {}
2927
+ }
2928
+ }
2929
+ }];
2930
+ return resources;
2931
+ }
2932
+ case "Certificate.TLS": {
2933
+ const sans = props.subjectAlternativeNames ?? [];
2934
+ return [{
2935
+ name: construct.id.replace(/[^a-zA-Z0-9-]/g, "-"),
2936
+ type: "certificatemanager.v1.certificate",
2937
+ properties: {
2938
+ name: construct.id.replace(/[^a-zA-Z0-9-]/g, "-"),
2939
+ managed: {
2940
+ domains: [props.domainName, ...sans]
2941
+ },
2942
+ scope: props.region ? "REGIONAL" : "DEFAULT"
2943
+ }
2944
+ }];
2945
+ }
2946
+ // ── Monitoring ────────────────────────────────────────────────────────
2947
+ case "Monitoring.Alarm": {
2948
+ const dimensions = props.dimensions;
2949
+ const operatorMap = {
2950
+ GreaterThanThreshold: "COMPARISON_GT",
2951
+ LessThanThreshold: "COMPARISON_LT",
2952
+ GreaterThanOrEqualToThreshold: "COMPARISON_GE",
2953
+ LessThanOrEqualToThreshold: "COMPARISON_LE"
2954
+ };
2955
+ const dimFilter = dimensions ? Object.entries(dimensions).map(([k, v]) => `metric.labels.${k}="${v}"`).join(" AND ") : "";
2956
+ return [{
2957
+ name: construct.id,
2958
+ type: "monitoring.v3.alertPolicy",
2959
+ properties: {
2960
+ displayName: construct.id,
2961
+ conditions: [{
2962
+ displayName: `${props.metricName} condition`,
2963
+ conditionThreshold: {
2964
+ filter: [
2965
+ `metric.type="${props.namespace ?? "cloudfunctions.googleapis.com/function"}/${props.metricName}"`,
2966
+ dimFilter
2967
+ ].filter(Boolean).join(" AND "),
2968
+ comparison: operatorMap[props.comparisonOperator ?? "GreaterThanThreshold"] ?? "COMPARISON_GT",
2969
+ thresholdValue: props.threshold,
2970
+ duration: `${(props.periodSeconds ?? 60) * (props.evaluationPeriods ?? 2)}s`,
2971
+ aggregations: [{
2972
+ alignmentPeriod: `${props.periodSeconds ?? 60}s`,
2973
+ perSeriesAligner: "ALIGN_MEAN"
2974
+ }]
2975
+ }
2976
+ }],
2977
+ alertStrategy: { notificationRateLimit: { period: "300s" } },
2978
+ combiner: "OR",
2979
+ enabled: true,
2980
+ notificationChannels: props.alarmActions ?? []
2981
+ }
2982
+ }];
2983
+ }
2984
+ case "Monitoring.Dashboard": {
2985
+ const widgets = props.widgets ?? [];
2986
+ return [{
2987
+ name: construct.id,
2988
+ type: "monitoring.v1.dashboard",
2989
+ properties: {
2990
+ displayName: construct.id,
2991
+ gridLayout: {
2992
+ columns: 3,
2993
+ widgets: widgets.map((w) => ({
2994
+ title: w.title,
2995
+ ...w.type === "text" ? {
2996
+ text: { content: w.markdown ?? w.title, format: "MARKDOWN" }
2997
+ } : {
2998
+ xyChart: {
2999
+ dataSets: [{
3000
+ timeSeriesQuery: {
3001
+ timeSeriesFilter: {
3002
+ filter: `metric.type="${w.namespace ?? "cloudfunctions.googleapis.com"}/${w.metricName}"`,
3003
+ aggregation: { alignmentPeriod: `${w.period ?? 60}s`, perSeriesAligner: "ALIGN_MEAN" }
3004
+ }
3005
+ }
3006
+ }]
3007
+ }
3008
+ }
3009
+ }))
3010
+ }
3011
+ }
3012
+ }];
3013
+ }
3014
+ case "Logging.Stream": {
3015
+ const filters = props.subscriptionFilters ?? [];
3016
+ const resources = [{
3017
+ name: construct.id.replace(/[^a-zA-Z0-9_-]/g, "-"),
3018
+ type: "logging.v2.logBucket",
3019
+ properties: {
3020
+ parent: `projects/PROJECT_ID/locations/${region}`,
3021
+ bucketId: construct.id.replace(/[^a-zA-Z0-9_-]/g, "-"),
3022
+ retentionDays: props.retentionDays ?? 30
3023
+ }
3024
+ }];
3025
+ for (const f of filters) {
3026
+ resources.push({
3027
+ name: `${construct.id}-sink-${f.name.replace(/[^a-zA-Z0-9-]/g, "-")}`,
3028
+ type: "logging.v2.sink",
3029
+ properties: {
3030
+ parent: `projects/PROJECT_ID`,
3031
+ uniqueWriterIdentity: true,
3032
+ filter: f.filterPattern,
3033
+ destination: f.destinationArn
3034
+ }
3035
+ });
3036
+ }
3037
+ return resources;
3038
+ }
3039
+ case "Custom.Resource": {
3040
+ const dm = props.deploymentManager;
3041
+ if (!dm)
3042
+ return [];
3043
+ return [{ name: props.name ?? construct.id, type: dm.type, properties: dm.properties }];
3044
+ }
3045
+ default:
3046
+ console.warn(`[gcp] Construct type '${construct.type}' nao suportado \u2014 descartado.`);
3047
+ return [];
3048
+ }
3049
+ }
3050
+ function synthesize(stack) {
3051
+ const resources = [];
3052
+ for (const construct of stack.constructs) {
3053
+ const result = synthesizeConstruct(construct);
3054
+ resources.push(...result);
3055
+ }
3056
+ return { resources };
3057
+ }
3058
+ }
3059
+ });
3060
+
3061
+ // ../providers/gcp/dist/provider.js
3062
+ var require_provider3 = __commonJS({
3063
+ "../providers/gcp/dist/provider.js"(exports2) {
3064
+ "use strict";
3065
+ Object.defineProperty(exports2, "__esModule", { value: true });
3066
+ exports2.GCPProvider = void 0;
3067
+ var deployment_manager_1 = require_deployment_manager();
3068
+ var GCPProvider2 = class {
3069
+ name = "gcp";
3070
+ synthesize(stack) {
3071
+ return (0, deployment_manager_1.synthesize)(stack);
3072
+ }
3073
+ };
3074
+ exports2.GCPProvider = GCPProvider2;
3075
+ }
3076
+ });
3077
+
3078
+ // ../providers/gcp/dist/index.js
3079
+ var require_dist3 = __commonJS({
3080
+ "../providers/gcp/dist/index.js"(exports2) {
3081
+ "use strict";
3082
+ Object.defineProperty(exports2, "__esModule", { value: true });
3083
+ exports2.synthesize = exports2.GCPProvider = void 0;
3084
+ var provider_1 = require_provider3();
3085
+ Object.defineProperty(exports2, "GCPProvider", { enumerable: true, get: function() {
3086
+ return provider_1.GCPProvider;
3087
+ } });
3088
+ var deployment_manager_1 = require_deployment_manager();
3089
+ Object.defineProperty(exports2, "synthesize", { enumerable: true, get: function() {
3090
+ return deployment_manager_1.synthesize;
3091
+ } });
3092
+ }
3093
+ });
3094
+
3095
+ // ../providers/terraform/dist/synth/hcl.js
3096
+ var require_hcl = __commonJS({
3097
+ "../providers/terraform/dist/synth/hcl.js"(exports2) {
3098
+ "use strict";
3099
+ Object.defineProperty(exports2, "__esModule", { value: true });
3100
+ exports2.hclString = hclString;
3101
+ exports2.synthesize = synthesize;
3102
+ var INSTANCE_TYPE_MAP = {
3103
+ small: "t3.small",
3104
+ medium: "t3.medium",
3105
+ large: "t3.large"
3106
+ };
3107
+ var AMI_MAP = {
3108
+ "ubuntu": "data.aws_ami.ubuntu.id",
3109
+ "ubuntu-22.04": "data.aws_ami.ubuntu.id",
3110
+ "amazon-linux-2": "data.aws_ami.amazon_linux.id",
3111
+ "amazon-linux-2023": "data.aws_ami.amazon_linux_2023.id",
3112
+ "windows-2022": "data.aws_ami.windows_2022.id",
3113
+ "windows-2019": "data.aws_ami.windows_2019.id",
3114
+ "windows-2016": "data.aws_ami.windows_2016.id"
3115
+ };
3116
+ var CACHE_NODE_TYPE_MAP = {
3117
+ small: "cache.t3.micro",
3118
+ medium: "cache.t3.medium",
3119
+ large: "cache.r6g.large"
3120
+ };
3121
+ var K8S_NODE_TYPE_MAP = {
3122
+ small: "t3.medium",
3123
+ medium: "m5.large",
3124
+ large: "m5.2xlarge"
3125
+ };
3126
+ function indent(text, spaces = 2) {
3127
+ return text.split("\n").map((l) => l.trim() ? " ".repeat(spaces) + l : l).join("\n");
3128
+ }
3129
+ function block(type, labels, body) {
3130
+ const labelStr = labels.map((l) => ` "${l}"`).join("");
3131
+ return `${type}${labelStr} {
3132
+ ${body}
3133
+ }
3134
+ `;
3135
+ }
3136
+ function hclString(value) {
3137
+ return String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$\{/g, "$$$${").replace(/%\{/g, "%%{");
3138
+ }
3139
+ function attr(key, value) {
3140
+ if (typeof value === "string")
3141
+ return `${key} = "${hclString(value)}"`;
3142
+ return `${key} = ${value}`;
3143
+ }
3144
+ function tagsBlock(name) {
3145
+ return indent(`tags = {
3146
+ ${indent(`Name = "${hclString(name)}"`)}
3147
+ }`);
3148
+ }
3149
+ function hclValue(value) {
3150
+ if (typeof value === "string") {
3151
+ if (/^[a-zA-Z_][a-zA-Z0-9_.\[\]]*$/.test(value) && /\./.test(value))
3152
+ return value;
3153
+ return `"${hclString(value)}"`;
3154
+ }
3155
+ if (typeof value === "number" || typeof value === "boolean")
3156
+ return String(value);
3157
+ if (Array.isArray(value))
3158
+ return `[${value.map(hclValue).join(", ")}]`;
3159
+ if (value && typeof value === "object")
3160
+ return hclBody(value);
3161
+ return "null";
3162
+ }
3163
+ function hclBody(obj) {
3164
+ const lines = Object.entries(obj).map(([k, v]) => `${k} = ${hclValue(v)}`);
3165
+ return `{
3166
+ ${indent(lines.join("\n"))}
3167
+ }`;
3168
+ }
3169
+ function synthesizeConstruct(construct) {
3170
+ const props = construct.props;
3171
+ const id = construct.id.replace(/[^a-zA-Z0-9_]/g, "_");
3172
+ switch (construct.type) {
3173
+ // ── Compute ──────────────────────────────────────────────────────────
3174
+ case "Compute.Instance": {
3175
+ const instanceType = INSTANCE_TYPE_MAP[props.instanceType] ?? "t3.small";
3176
+ const ami = AMI_MAP[props.image];
3177
+ const body = indent([
3178
+ `ami = ${ami ? ami : `"${hclString(props.image)}"`}`,
3179
+ attr("instance_type", instanceType),
3180
+ "",
3181
+ tagsBlock(construct.id)
3182
+ ].join("\n"));
3183
+ return block("resource", ["aws_instance", id], body);
3184
+ }
3185
+ case "Compute.AutoScaling": {
3186
+ const instanceType = INSTANCE_TYPE_MAP[props.instanceType] ?? "t3.small";
3187
+ const image = props.image;
3188
+ const lcBody = indent([
3189
+ `image_id = "${hclString(image)}"`,
3190
+ attr("instance_type", instanceType)
3191
+ ].join("\n"));
3192
+ const asgBody = indent([
3193
+ `launch_configuration = aws_launch_configuration.${id}_lc.name`,
3194
+ attr("min_size", props.minCapacity),
3195
+ attr("max_size", props.maxCapacity),
3196
+ attr("desired_capacity", props.desiredCapacity ?? props.minCapacity),
3197
+ ...props.subnetIds ? [`vpc_zone_identifier = ${JSON.stringify(props.subnetIds)}`] : [],
3198
+ "",
3199
+ tagsBlock(construct.id)
3200
+ ].join("\n"));
3201
+ const parts = [
3202
+ block("resource", ["aws_launch_configuration", `${id}_lc`], lcBody),
3203
+ block("resource", ["aws_autoscaling_group", id], asgBody)
3204
+ ];
3205
+ if (props.targetCpuUtilization) {
3206
+ const spBody = indent([
3207
+ `autoscaling_group_name = aws_autoscaling_group.${id}.name`,
3208
+ attr("policy_type", "TargetTrackingScaling"),
3209
+ `target_tracking_configuration {
3210
+ predefined_metric_specification {
3211
+ predefined_metric_type = "ASGAverageCPUUtilization"
3212
+ }
3213
+ target_value = ${props.targetCpuUtilization}
3214
+ }`
3215
+ ].join("\n"));
3216
+ parts.push(block("resource", ["aws_autoscaling_policy", `${id}_sp`], spBody));
3217
+ }
3218
+ return parts.join("\n");
3219
+ }
3220
+ case "Compute.Container": {
3221
+ const environment = props.environment;
3222
+ const containerDef = JSON.stringify([{
3223
+ name: construct.id,
3224
+ image: props.image,
3225
+ cpu: props.cpu ?? 256,
3226
+ memory: props.memory ?? 512,
3227
+ portMappings: props.port ? [{ containerPort: props.port, protocol: "tcp" }] : [],
3228
+ environment: environment ? Object.entries(environment).map(([k, v]) => ({ name: k, value: v })) : []
3229
+ }]);
3230
+ const clusterBody = indent([attr("name", construct.id)].join("\n"));
3231
+ const tdBody = indent([
3232
+ attr("family", construct.id),
3233
+ attr("network_mode", "awsvpc"),
3234
+ `requires_compatibilities = ["FARGATE"]`,
3235
+ attr("cpu", String(props.cpu ?? 256)),
3236
+ attr("memory", String(props.memory ?? 512)),
3237
+ `execution_role_arn = "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole"`,
3238
+ `container_definitions = jsonencode(${containerDef})`
3239
+ ].join("\n"));
3240
+ const svcBody = indent([
3241
+ `cluster = aws_ecs_cluster.${id}.id`,
3242
+ `task_definition = aws_ecs_task_definition.${id}.arn`,
3243
+ attr("desired_count", props.desiredCount ?? 1),
3244
+ attr("launch_type", "FARGATE"),
3245
+ `network_configuration {
3246
+ assign_public_ip = ${props.publicIp ?? false}
3247
+ subnets = []
3248
+ }`
3249
+ ].join("\n"));
3250
+ return [
3251
+ block("resource", ["aws_ecs_cluster", id], clusterBody),
3252
+ block("resource", ["aws_ecs_task_definition", id], tdBody),
3253
+ block("resource", ["aws_ecs_service", id], svcBody)
3254
+ ].join("\n");
3255
+ }
3256
+ case "Compute.Kubernetes": {
3257
+ const nodeType = K8S_NODE_TYPE_MAP[props.nodeInstanceType ?? "medium"] ?? "m5.large";
3258
+ const clusterBody = indent([
3259
+ attr("name", construct.id),
3260
+ attr("version", props.version ?? "1.29"),
3261
+ `role_arn = "arn:aws:iam::ACCOUNT_ID:role/EKSClusterRole"`,
3262
+ `vpc_config {
3263
+ subnet_ids = []
3264
+ endpoint_private_access = ${props.privateCluster ?? false}
3265
+ endpoint_public_access = ${!props.privateCluster}
3266
+ }`,
3267
+ "",
3268
+ tagsBlock(construct.id)
3269
+ ].join("\n"));
3270
+ const ngBody = indent([
3271
+ `cluster_name = aws_eks_cluster.${id}.name`,
3272
+ attr("node_group_name", `${construct.id}-ng`),
3273
+ `node_role_arn = "arn:aws:iam::ACCOUNT_ID:role/EKSNodeRole"`,
3274
+ `subnet_ids = []`,
3275
+ `scaling_config {
3276
+ desired_size = ${props.desiredNodes ?? 2}
3277
+ max_size = ${props.maxNodes ?? 3}
3278
+ min_size = ${props.minNodes ?? 1}
3279
+ }`,
3280
+ `instance_types = ["${nodeType}"]`
3281
+ ].join("\n"));
3282
+ return [
3283
+ block("resource", ["aws_eks_cluster", id], clusterBody),
3284
+ block("resource", ["aws_eks_node_group", `${id}_ng`], ngBody)
3285
+ ].join("\n");
3286
+ }
3287
+ // ── Storage ───────────────────────────────────────────────────────────
3288
+ case "Storage.Bucket": {
3289
+ const versioning = props.versioning ?? false;
3290
+ const blockPublic = !props.publicAccess;
3291
+ const lifecycleRules = props.lifecycleRules ?? [];
3292
+ const bucketBody = indent([
3293
+ attr("bucket", construct.id.toLowerCase()),
3294
+ "",
3295
+ tagsBlock(construct.id)
3296
+ ].join("\n"));
3297
+ const versioningBody = indent([
3298
+ `bucket = aws_s3_bucket.${id}.id`,
3299
+ `versioning_configuration {
3300
+ status = "${versioning ? "Enabled" : "Suspended"}"
3301
+ }`
3302
+ ].join("\n"));
3303
+ const pabBody = indent([
3304
+ `bucket = aws_s3_bucket.${id}.id`,
3305
+ attr("block_public_acls", blockPublic),
3306
+ attr("block_public_policy", blockPublic),
3307
+ attr("ignore_public_acls", blockPublic),
3308
+ attr("restrict_public_buckets", blockPublic)
3309
+ ].join("\n"));
3310
+ const parts = [
3311
+ block("resource", ["aws_s3_bucket", id], bucketBody),
3312
+ block("resource", ["aws_s3_bucket_versioning", `${id}_versioning`], versioningBody),
3313
+ block("resource", ["aws_s3_bucket_public_access_block", `${id}_pab`], pabBody)
3314
+ ];
3315
+ if (lifecycleRules.length > 0) {
3316
+ const lcLines = lifecycleRules.map((r, i) => {
3317
+ return [
3318
+ ` rule {`,
3319
+ ` id = "rule-${i}"`,
3320
+ ` status = "Enabled"`,
3321
+ r.prefix ? ` filter {
3322
+ prefix = "${hclString(r.prefix)}"
3323
+ }` : "",
3324
+ r.expireAfterDays ? ` expiration {
3325
+ days = ${r.expireAfterDays}
3326
+ }` : "",
3327
+ r.transitionToGlacierDays ? ` transition {
3328
+ days = ${r.transitionToGlacierDays}
3329
+ storage_class = "GLACIER"
3330
+ }` : "",
3331
+ ` }`
3332
+ ].filter(Boolean).join("\n");
3333
+ }).join("\n");
3334
+ parts.push(block("resource", ["aws_s3_bucket_lifecycle_configuration", `${id}_lc`], indent(`bucket = aws_s3_bucket.${id}.id
3335
+ ${lcLines}`)));
3336
+ }
3337
+ return parts.join("\n");
3338
+ }
3339
+ case "Storage.FileSystem": {
3340
+ const fsBody = indent([
3341
+ attr("performance_mode", props.performanceMode ?? "generalPurpose"),
3342
+ attr("throughput_mode", props.throughputMode ?? "bursting"),
3343
+ attr("encrypted", props.encrypted ?? true),
3344
+ `lifecycle_policy {
3345
+ transition_to_ia = "AFTER_30_DAYS"
3346
+ }`,
3347
+ "",
3348
+ tagsBlock(construct.id)
3349
+ ].join("\n"));
3350
+ const parts = [block("resource", ["aws_efs_file_system", id], fsBody)];
3351
+ const accessPoints = props.accessPoints ?? [];
3352
+ for (const ap of accessPoints) {
3353
+ const apId = `${id}_ap_${ap.name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
3354
+ const apBody = indent([
3355
+ `file_system_id = aws_efs_file_system.${id}.id`,
3356
+ `root_directory {
3357
+ path = "${hclString(ap.path)}"
3358
+ }`,
3359
+ ap.uid ? `posix_user {
3360
+ uid = ${ap.uid}
3361
+ gid = ${ap.gid ?? ap.uid}
3362
+ }` : ""
3363
+ ].filter(Boolean).join("\n"));
3364
+ parts.push(block("resource", ["aws_efs_access_point", apId], apBody));
3365
+ }
3366
+ return parts.join("\n");
3367
+ }
3368
+ case "Storage.Archive": {
3369
+ const body = indent([
3370
+ attr("bucket", `${construct.id.toLowerCase()}-archive`),
3371
+ `lifecycle_rule {
3372
+ id = "archive-rule"
3373
+ enabled = true
3374
+ transition {
3375
+ days = 0
3376
+ storage_class = "DEEP_ARCHIVE"
3377
+ }
3378
+ ${props.retentionDays ? ` expiration {
3379
+ days = ${props.retentionDays}
3380
+ }
3381
+ ` : ""}}`,
3382
+ `object_lock_enabled = ${props.lockEnabled ?? false}`,
3383
+ "",
3384
+ tagsBlock(construct.id)
3385
+ ].join("\n"));
3386
+ return block("resource", ["aws_s3_bucket", id], body);
3387
+ }
3388
+ // ── Network ───────────────────────────────────────────────────────────
3389
+ case "Network.VPC": {
3390
+ const body = indent([
3391
+ attr("cidr_block", props.cidr ?? "10.0.0.0/16"),
3392
+ attr("enable_dns_hostnames", true),
3393
+ attr("enable_dns_support", true),
3394
+ "",
3395
+ tagsBlock(construct.id)
3396
+ ].join("\n"));
3397
+ return block("resource", ["aws_vpc", id], body);
3398
+ }
3399
+ case "Network.Subnet": {
3400
+ const isPublic = props.public ?? false;
3401
+ const lines = [
3402
+ `vpc_id = "${hclString(props.vpcId)}"`,
3403
+ attr("cidr_block", props.cidr),
3404
+ attr("map_public_ip_on_launch", isPublic),
3405
+ ...props.availabilityZone ? [attr("availability_zone", props.availabilityZone)] : [],
3406
+ "",
3407
+ tagsBlock(construct.id)
3408
+ ];
3409
+ return block("resource", ["aws_subnet", id], indent(lines.join("\n")));
3410
+ }
3411
+ case "Network.SecurityGroup": {
3412
+ const ingress = props.ingressRules ?? [];
3413
+ const egress = props.egressRules ?? [];
3414
+ const ingressBlocks = ingress.map((r, i) => {
3415
+ if (r.cidr === void 0) {
3416
+ console.warn(`[terraform] Security group rule sem CIDR; usando 0.0.0.0/0 \u2014 defina props.cidr explicitamente (${construct.id} ingress[${i}])`);
3417
+ }
3418
+ return indent([
3419
+ "ingress {",
3420
+ indent([
3421
+ attr("protocol", r.protocol),
3422
+ attr("from_port", r.fromPort),
3423
+ attr("to_port", r.toPort),
3424
+ `cidr_blocks = ["${hclString(r.cidr ?? "0.0.0.0/0")}"]`,
3425
+ r.description ? attr("description", r.description) : ""
3426
+ ].filter(Boolean).join("\n")),
3427
+ "}"
3428
+ ].join("\n"));
3429
+ }).join("\n");
3430
+ const egressList = egress.length > 0 ? egress : [{ protocol: "-1", fromPort: 0, toPort: 0, cidr: "0.0.0.0/0" }];
3431
+ const egressBlocks = egressList.map((r) => indent([
3432
+ "egress {",
3433
+ indent([
3434
+ attr("protocol", r.protocol),
3435
+ attr("from_port", r.fromPort),
3436
+ attr("to_port", r.toPort),
3437
+ `cidr_blocks = ["${hclString(r.cidr ?? "0.0.0.0/0")}"]`
3438
+ ].join("\n")),
3439
+ "}"
3440
+ ].join("\n"))).join("\n");
3441
+ const body = indent([
3442
+ `vpc_id = "${hclString(props.vpcId)}"`,
3443
+ attr("description", props.description ?? `Security group ${id}`),
3444
+ "",
3445
+ ingressBlocks,
3446
+ "",
3447
+ egressBlocks,
3448
+ "",
3449
+ tagsBlock(construct.id)
3450
+ ].join("\n"));
3451
+ return block("resource", ["aws_security_group", id], body);
3452
+ }
3453
+ case "Network.WAF": {
3454
+ const rules = props.rules ?? [];
3455
+ const scope = props.scope ?? "REGIONAL";
3456
+ const defaultAction = props.defaultAction ?? "allow";
3457
+ const ruleBlocks = rules.map((r, i) => {
3458
+ const action = r.action ?? "allow";
3459
+ const ruleId = (r.name ?? `rule${i}`).replace(/[^a-zA-Z0-9_]/g, "_");
3460
+ const statement = r.managedGroup ? `statement {
3461
+ managed_rule_group_statement {
3462
+ name = "${hclString(r.managedGroup)}"
3463
+ vendor_name = "AWS"
3464
+ }
3465
+ }` : `statement {
3466
+ byte_match_statement {
3467
+ search_string = "${hclString((r.matchValues ?? ["BadBot"])[0])}"
3468
+ field_to_match { single_header { name = "user-agent" } }
3469
+ text_transformation { priority = 0 type = "NONE" }
3470
+ positional_constraint = "CONTAINS"
3471
+ }
3472
+ }`;
3473
+ return indent([
3474
+ "rule {",
3475
+ indent([
3476
+ attr("name", r.name ?? `rule-${i}`),
3477
+ attr("priority", r.priority ?? i + 1),
3478
+ `action {
3479
+ ${action === "block" ? "block" : action === "count" ? "count" : "allow"} {}
3480
+ }`,
3481
+ statement,
3482
+ `visibility_config {
3483
+ cloudwatch_metrics_enabled = true
3484
+ metric_name = "${ruleId}"
3485
+ sampled_requests_enabled = true
3486
+ }`
3487
+ ].join("\n")),
3488
+ "}"
3489
+ ].join("\n"));
3490
+ }).join("\n");
3491
+ const body = indent([
3492
+ attr("name", id),
3493
+ attr("scope", scope),
3494
+ `default_action {
3495
+ ${defaultAction === "block" ? "block" : "allow"} {}
3496
+ }`,
3497
+ ruleBlocks,
3498
+ `visibility_config {
3499
+ cloudwatch_metrics_enabled = true
3500
+ metric_name = "${id}"
3501
+ sampled_requests_enabled = true
3502
+ }`,
3503
+ "",
3504
+ tagsBlock(construct.id)
3505
+ ].join("\n"));
3506
+ return block("resource", ["aws_wafv2_web_acl", id], body);
3507
+ }
3508
+ case "Network.LoadBalancer": {
3509
+ const lbType = props.type ?? "application";
3510
+ const listeners = props.listeners ?? [];
3511
+ const targetGroups = props.targetGroups ?? [];
3512
+ const lbBody = indent([
3513
+ attr("name", construct.id),
3514
+ attr("load_balancer_type", lbType),
3515
+ attr("internal", props.scheme === "internal"),
3516
+ `subnets = ${JSON.stringify(props.subnetIds ?? [])}`,
3517
+ ...lbType === "application" && props.securityGroupIds ? [`security_groups = ${JSON.stringify(props.securityGroupIds)}`] : [],
3518
+ "",
3519
+ tagsBlock(construct.id)
3520
+ ].join("\n"));
3521
+ const parts = [block("resource", ["aws_lb", id], lbBody)];
3522
+ for (const tg of targetGroups) {
3523
+ const tgId = `${id}_tg_${tg.name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
3524
+ const tgBody = indent([
3525
+ attr("name", tg.name),
3526
+ attr("port", tg.port),
3527
+ attr("protocol", tg.protocol),
3528
+ `vpc_id = ""`,
3529
+ `target_type = "ip"`,
3530
+ `health_check {
3531
+ path = "${hclString(tg.healthCheckPath ?? "/")}"
3532
+ }`
3533
+ ].join("\n"));
3534
+ parts.push(block("resource", ["aws_lb_target_group", tgId], tgBody));
3535
+ }
3536
+ for (let i = 0; i < listeners.length; i++) {
3537
+ const l = listeners[i];
3538
+ const listenerId = `${id}_listener_${i + 1}`;
3539
+ const redirectAction = `redirect {
3540
+ port = "443"
3541
+ protocol = "HTTPS"
3542
+ status_code = "HTTP_301"
3543
+ }`;
3544
+ const fixedAction = `fixed_response {
3545
+ content_type = "text/plain"
3546
+ message_body = "Not found"
3547
+ status_code = "404"
3548
+ }`;
3549
+ const listenerBody = indent([
3550
+ `load_balancer_arn = aws_lb.${id}.arn`,
3551
+ attr("port", l.port),
3552
+ attr("protocol", l.protocol),
3553
+ ...l.certificateArn ? [attr("certificate_arn", l.certificateArn)] : [],
3554
+ `default_action {
3555
+ type = "${l.redirectToHttps ? "redirect" : "fixed-response"}"
3556
+ ${l.redirectToHttps ? redirectAction : fixedAction}
3557
+ }`
3558
+ ].join("\n"));
3559
+ parts.push(block("resource", ["aws_lb_listener", listenerId], listenerBody));
3560
+ }
3561
+ return parts.join("\n");
3562
+ }
3563
+ case "Network.CDN": {
3564
+ const origins = props.origins ?? [];
3565
+ const body = indent([
3566
+ `origin {
3567
+ domain_name = "${hclString(origins[0]?.domainName ?? "")}"
3568
+ origin_id = "${hclString(origins[0]?.id ?? "default")}"
3569
+ }`,
3570
+ `enabled = true`,
3571
+ attr("default_root_object", props.defaultRootObject ?? "index.html"),
3572
+ attr("price_class", props.priceClass ?? "PriceClass_100"),
3573
+ `default_cache_behavior {
3574
+ target_origin_id = "${hclString(origins[0]?.id ?? "default")}"
3575
+ viewer_protocol_policy = "redirect-to-https"
3576
+ allowed_methods = ["GET", "HEAD"]
3577
+ cached_methods = ["GET", "HEAD"]
3578
+ compress = true
3579
+ forwarded_values {
3580
+ query_string = false
3581
+ cookies { forward = "none" }
3582
+ }
3583
+ }`,
3584
+ ...props.certificateArn ? [`viewer_certificate {
3585
+ acm_certificate_arn = "${hclString(props.certificateArn)}"
3586
+ ssl_support_method = "sni-only"
3587
+ }`] : [`viewer_certificate {
3588
+ cloudfront_default_certificate = true
3589
+ }`],
3590
+ `restrictions {
3591
+ geo_restriction {
3592
+ restriction_type = "none"
3593
+ }
3594
+ }`,
3595
+ "",
3596
+ tagsBlock(construct.id)
3597
+ ].join("\n"));
3598
+ return block("resource", ["aws_cloudfront_distribution", id], body);
3599
+ }
3600
+ case "Network.Dns": {
3601
+ const records = props.records ?? [];
3602
+ const zoneBody = indent([
3603
+ attr("name", props.zoneName),
3604
+ tagsBlock(construct.id)
3605
+ ].join("\n"));
3606
+ const parts = [block("resource", ["aws_route53_zone", id], zoneBody)];
3607
+ for (const r of records) {
3608
+ const recId = `${id}_${r.name.replace(/[^a-zA-Z0-9_]/g, "_")}_${r.type}`;
3609
+ const recBody = indent([
3610
+ `zone_id = aws_route53_zone.${id}.zone_id`,
3611
+ attr("name", r.name),
3612
+ attr("type", r.type),
3613
+ attr("ttl", r.ttl ?? 300),
3614
+ `records = ${JSON.stringify(r.values)}`
3615
+ ].join("\n"));
3616
+ parts.push(block("resource", ["aws_route53_record", recId], recBody));
3617
+ }
3618
+ return parts.join("\n");
3619
+ }
3620
+ // ── Database ──────────────────────────────────────────────────────────
3621
+ case "Database.SQL": {
3622
+ const engine = props.engine ?? "mysql";
3623
+ const edition = props.edition ?? "";
3624
+ const licenseModel = props.licenseModel ?? "";
3625
+ const engineVersionMap = {
3626
+ mysql: "8.0.36",
3627
+ postgres: "15.4",
3628
+ mariadb: "10.11.6",
3629
+ oracle: "19.0.0.0.ru-2024-01.rur-2024-01.r1",
3630
+ sqlserver: "15.00.4365.2.v1"
3631
+ };
3632
+ const engineIdentMap = {
3633
+ mysql: "mysql",
3634
+ postgres: "postgres",
3635
+ mariadb: "mariadb",
3636
+ oracle: `oracle-${edition || "se2"}`,
3637
+ sqlserver: `sqlserver-${edition || "ex"}`
3638
+ };
3639
+ const defaultInstanceMap = {
3640
+ mysql: "db.t3.micro",
3641
+ postgres: "db.t3.micro",
3642
+ mariadb: "db.t3.micro",
3643
+ oracle: "db.t3.small",
3644
+ sqlserver: "db.t3.small"
3645
+ };
3646
+ const usernameMap = {
3647
+ mysql: "dbadmin",
3648
+ postgres: "dbadmin",
3649
+ mariadb: "dbadmin",
3650
+ oracle: "dbadmin",
3651
+ sqlserver: "sqladmin"
3652
+ };
3653
+ const attrs = [
3654
+ attr("identifier", construct.id.toLowerCase()),
3655
+ attr("engine", engineIdentMap[engine] ?? engine),
3656
+ attr("engine_version", engineVersionMap[engine] ?? "8.0.36"),
3657
+ attr("instance_class", props.instanceType ?? defaultInstanceMap[engine] ?? "db.t3.micro"),
3658
+ attr("allocated_storage", props.storageGb ?? 20),
3659
+ attr("username", usernameMap[engine] ?? "dbadmin"),
3660
+ `password = var.db_password`,
3661
+ attr("multi_az", props.multiAz ?? false),
3662
+ attr("skip_final_snapshot", false),
3663
+ attr("storage_encrypted", true),
3664
+ attr("backup_retention_period", props.backupRetentionDays ?? 7),
3665
+ attr("deletion_protection", props.deletionProtection ?? false)
3666
+ ];
3667
+ if (licenseModel)
3668
+ attrs.push(attr("license_model", licenseModel));
3669
+ attrs.push("", tagsBlock(construct.id));
3670
+ return block("resource", ["aws_db_instance", id], indent(attrs.join("\n")));
3671
+ }
3672
+ case "Database.DocumentDB": {
3673
+ const instances = props.instances ?? 1;
3674
+ const clusterBody = indent([
3675
+ attr("cluster_identifier", construct.id.toLowerCase()),
3676
+ attr("engine", "docdb"),
3677
+ attr("master_username", "docdbadmin"),
3678
+ `master_password = var.db_password`,
3679
+ attr("backup_retention_period", 7),
3680
+ attr("skip_final_snapshot", false),
3681
+ attr("storage_encrypted", true),
3682
+ attr("deletion_protection", props.deletionProtection ?? false),
3683
+ "",
3684
+ tagsBlock(construct.id)
3685
+ ].join("\n"));
3686
+ const instanceBlocks = Array.from({ length: instances }, (_, i) => block("resource", ["aws_docdb_cluster_instance", `${id}_instance_${i + 1}`], indent([
3687
+ attr("identifier", `${construct.id.toLowerCase()}-${i + 1}`),
3688
+ `cluster_id = aws_docdb_cluster.${id}.id`,
3689
+ attr("instance_class", props.instanceType ?? "db.t3.medium")
3690
+ ].join("\n")))).join("\n");
3691
+ return [block("resource", ["aws_docdb_cluster", id], clusterBody), instanceBlocks].join("\n");
3692
+ }
3693
+ case "Database.DynamoDB": {
3694
+ const billingMode = props.billingMode ?? "PAY_PER_REQUEST";
3695
+ const gsis = props.globalSecondaryIndexes ?? [];
3696
+ const gsiBlocks = gsis.map((g) => indent([
3697
+ "global_secondary_index {",
3698
+ indent([
3699
+ attr("name", g.name),
3700
+ attr("hash_key", g.partitionKey),
3701
+ ...g.sortKey ? [attr("range_key", g.sortKey)] : [],
3702
+ attr("projection_type", "ALL")
3703
+ ].join("\n")),
3704
+ "}"
3705
+ ].join("\n"))).join("\n");
3706
+ const body = indent([
3707
+ attr("name", construct.id),
3708
+ attr("billing_mode", billingMode),
3709
+ ...billingMode === "PROVISIONED" ? [
3710
+ attr("read_capacity", props.readCapacity ?? 5),
3711
+ attr("write_capacity", props.writeCapacity ?? 5)
3712
+ ] : [],
3713
+ attr("hash_key", props.partitionKey),
3714
+ ...props.sortKey ? [attr("range_key", props.sortKey)] : [],
3715
+ `attribute {
3716
+ name = "${hclString(props.partitionKey)}"
3717
+ type = "S"
3718
+ }`,
3719
+ ...props.sortKey ? [`attribute {
3720
+ name = "${hclString(props.sortKey)}"
3721
+ type = "S"
3722
+ }`] : [],
3723
+ ...gsis.map((g) => `attribute {
3724
+ name = "${hclString(g.partitionKey)}"
3725
+ type = "S"
3726
+ }`),
3727
+ gsiBlocks,
3728
+ ...props.ttlAttribute ? [`ttl {
3729
+ attribute_name = "${hclString(props.ttlAttribute)}"
3730
+ enabled = true
3731
+ }`] : [],
3732
+ `point_in_time_recovery {
3733
+ enabled = ${props.pointInTimeRecovery ?? true}
3734
+ }`,
3735
+ ...props.streamEnabled ? [`stream_enabled = true`, `stream_view_type = "NEW_AND_OLD_IMAGES"`] : [],
3736
+ "",
3737
+ tagsBlock(construct.id)
3738
+ ].join("\n"));
3739
+ return block("resource", ["aws_dynamodb_table", id], body);
3740
+ }
3741
+ // ── Cache ─────────────────────────────────────────────────────────────
3742
+ case "Cache.Redis": {
3743
+ const numNodes = props.numCacheNodes ?? 1;
3744
+ const autoFailover = props.automaticFailoverEnabled ?? false;
3745
+ const body = indent([
3746
+ attr("replication_group_id", construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 40)),
3747
+ attr("description", `Redis ${construct.id}`),
3748
+ attr("node_type", CACHE_NODE_TYPE_MAP[props.nodeType ?? "small"] ?? "cache.t3.micro"),
3749
+ attr("num_cache_clusters", numNodes),
3750
+ attr("automatic_failover_enabled", autoFailover && numNodes > 1),
3751
+ attr("at_rest_encryption_enabled", props.atRestEncryptionEnabled ?? true),
3752
+ attr("transit_encryption_enabled", props.transitEncryptionEnabled ?? true),
3753
+ attr("engine_version", props.version ?? "7.0"),
3754
+ ...props.subnetGroupName ? [attr("subnet_group_name", props.subnetGroupName)] : [],
3755
+ ...props.securityGroupIds ? [`security_group_ids = ${JSON.stringify(props.securityGroupIds)}`] : [],
3756
+ "",
3757
+ tagsBlock(construct.id)
3758
+ ].join("\n"));
3759
+ return block("resource", ["aws_elasticache_replication_group", id], body);
3760
+ }
3761
+ case "Cache.Memcached": {
3762
+ const body = indent([
3763
+ attr("cluster_id", construct.id.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 40)),
3764
+ attr("engine", "memcached"),
3765
+ attr("node_type", CACHE_NODE_TYPE_MAP[props.nodeType ?? "small"] ?? "cache.t3.micro"),
3766
+ attr("num_cache_nodes", props.numCacheNodes ?? 2),
3767
+ ...props.subnetGroupName ? [attr("subnet_group_name", props.subnetGroupName)] : [],
3768
+ "",
3769
+ tagsBlock(construct.id)
3770
+ ].join("\n"));
3771
+ return block("resource", ["aws_elasticache_cluster", id], body);
3772
+ }
3773
+ // ── Function ──────────────────────────────────────────────────────────
3774
+ case "Function.Lambda": {
3775
+ const environment = props.environment;
3776
+ const runtimeMap = {
3777
+ "nodejs20": "nodejs20.x",
3778
+ "nodejs18": "nodejs18.x",
3779
+ "python3.12": "python3.12",
3780
+ "python3.11": "python3.11",
3781
+ "java21": "java21",
3782
+ "go1.x": "go1.x",
3783
+ "dotnet8": "dotnet8"
3784
+ };
3785
+ const envBlock = environment && Object.keys(environment).length > 0 ? "\n" + indent([
3786
+ "environment {",
3787
+ indent(`variables = {
3788
+ ${Object.entries(environment).map(([k, v]) => ` ${k} = "${hclString(v)}"`).join("\n")}
3789
+ }`),
3790
+ "}"
3791
+ ].join("\n")) : "";
3792
+ const body = indent([
3793
+ attr("function_name", construct.id),
3794
+ attr("runtime", runtimeMap[props.runtime ?? "nodejs20"] ?? "nodejs20.x"),
3795
+ attr("handler", props.handler ?? "index.handler"),
3796
+ attr("role", "arn:aws:iam::ACCOUNT_ID:role/lambda-role"),
3797
+ `filename = "function.zip"`,
3798
+ `source_code_hash = filebase64sha256("function.zip")`,
3799
+ ...props.memory ? [attr("memory_size", props.memory)] : [],
3800
+ ...props.timeout ? [attr("timeout", props.timeout)] : [],
3801
+ ...props.reservedConcurrency !== void 0 ? [attr("reserved_concurrent_executions", props.reservedConcurrency)] : [],
3802
+ envBlock,
3803
+ ...props.vpcId ? [`vpc_config {
3804
+ subnet_ids = ${JSON.stringify(props.subnetIds ?? [])}
3805
+ security_group_ids = ${JSON.stringify(props.securityGroupIds ?? [])}
3806
+ }`] : [],
3807
+ "",
3808
+ tagsBlock(construct.id)
3809
+ ].filter((l) => l !== "").join("\n"));
3810
+ return block("resource", ["aws_lambda_function", id], body);
3811
+ }
3812
+ case "Function.ApiGateway": {
3813
+ const apigwType = props.type ?? "HTTP";
3814
+ const routes = props.routes ?? [];
3815
+ const stageName = props.stageName ?? "$default";
3816
+ const apiBody = indent([
3817
+ attr("name", props.name),
3818
+ attr("protocol_type", apigwType),
3819
+ ...props.cors ? [`cors_configuration {
3820
+ allow_origins = ["*"]
3821
+ allow_methods = ["*"]
3822
+ allow_headers = ["*"]
3823
+ }`] : [],
3824
+ "",
3825
+ tagsBlock(construct.id)
3826
+ ].join("\n"));
3827
+ const stageBody = indent([
3828
+ `api_id = aws_apigatewayv2_api.${id}.id`,
3829
+ attr("name", stageName),
3830
+ attr("auto_deploy", true)
3831
+ ].join("\n"));
3832
+ const parts = [
3833
+ block("resource", ["aws_apigatewayv2_api", id], apiBody),
3834
+ block("resource", ["aws_apigatewayv2_stage", `${id}_stage`], stageBody)
3835
+ ];
3836
+ const authorizerLambdaId = props.authorizerLambdaId;
3837
+ if (authorizerLambdaId) {
3838
+ const authorizerBody = indent([
3839
+ `api_id = aws_apigatewayv2_api.${id}.id`,
3840
+ attr("authorizer_type", "REQUEST"),
3841
+ attr("name", `${props.name}-authorizer`),
3842
+ `authorizer_uri = aws_lambda_function.${authorizerLambdaId}.invoke_arn`,
3843
+ attr("authorizer_payload_format_version", "2.0"),
3844
+ `identity_sources = ["$request.header.Authorization"]`
3845
+ ].join("\n"));
3846
+ parts.push(block("resource", ["aws_apigatewayv2_authorizer", `${id}_authorizer`], authorizerBody));
3847
+ }
3848
+ for (const r of routes) {
3849
+ const routeId = `${id}_${r.method.toLowerCase()}_${r.path.replace(/[^a-zA-Z0-9]/g, "_")}`;
3850
+ const routeBody = indent([
3851
+ `api_id = aws_apigatewayv2_api.${id}.id`,
3852
+ attr("route_key", `${r.method} ${r.path}`),
3853
+ ...r.lambdaId ? [`target = "integrations/\${aws_apigatewayv2_integration.${routeId}_integ.id}"`] : [],
3854
+ ...authorizerLambdaId ? [
3855
+ attr("authorization_type", "CUSTOM"),
3856
+ `authorizer_id = aws_apigatewayv2_authorizer.${id}_authorizer.id`
3857
+ ] : []
3858
+ ].join("\n"));
3859
+ parts.push(block("resource", ["aws_apigatewayv2_route", routeId], routeBody));
3860
+ if (r.lambdaId) {
3861
+ const integBody = indent([
3862
+ `api_id = aws_apigatewayv2_api.${id}.id`,
3863
+ attr("integration_type", "AWS_PROXY"),
3864
+ `integration_uri = aws_lambda_function.${r.lambdaId}.invoke_arn`,
3865
+ attr("payload_format_version", "2.0")
3866
+ ].join("\n"));
3867
+ parts.push(block("resource", ["aws_apigatewayv2_integration", `${routeId}_integ`], integBody));
3868
+ }
3869
+ }
3870
+ return parts.join("\n");
3871
+ }
3872
+ // ── Policy ────────────────────────────────────────────────────────────
3873
+ case "Policy.IAM": {
3874
+ const statements = props.statements ?? [];
3875
+ const attachType = props.attachType;
3876
+ const attachTo = props.attachTo.replace(/[^a-zA-Z0-9_]/g, "_");
3877
+ const principalService = attachType === "lambda" ? "lambda.amazonaws.com" : "ec2.amazonaws.com";
3878
+ const policyStmts = statements.map((s) => {
3879
+ const actions = s.actions.map((a) => `"${hclString(a)}"`).join(", ");
3880
+ const resources = (s.resources ?? ["*"]).map((r) => `"${hclString(r)}"`).join(", ");
3881
+ return ` {
3882
+ "Effect": "${hclString(s.effect)}",
3883
+ "Action": [${actions}],
3884
+ "Resource": [${resources}]
3885
+ }`;
3886
+ }).join(",\n");
3887
+ const roleBody = indent([
3888
+ attr("name", `${attachTo}-role`),
3889
+ `assume_role_policy = jsonencode({
3890
+ Version = "2012-10-17"
3891
+ Statement = [{
3892
+ Effect = "Allow"
3893
+ Principal = { Service = "${principalService}" }
3894
+ Action = "sts:AssumeRole"
3895
+ }]
3896
+ })`,
3897
+ "",
3898
+ tagsBlock(`${attachTo}-role`)
3899
+ ].join("\n"));
3900
+ const policyBody = indent([
3901
+ attr("name", `${id}-policy`),
3902
+ `policy = jsonencode({
3903
+ Version = "2012-10-17"
3904
+ Statement = [
3905
+ ${policyStmts}
3906
+ ]
3907
+ })`
3908
+ ].join("\n"));
3909
+ const attachBody = indent([
3910
+ `role = aws_iam_role.${id}_role.name`,
3911
+ `policy_arn = aws_iam_policy.${id}_policy.arn`
3912
+ ].join("\n"));
3913
+ const parts = [
3914
+ block("resource", ["aws_iam_role", `${id}_role`], roleBody),
3915
+ block("resource", ["aws_iam_policy", `${id}_policy`], policyBody),
3916
+ block("resource", ["aws_iam_role_policy_attachment", `${id}_attach`], attachBody)
3917
+ ];
3918
+ if (attachType === "compute") {
3919
+ parts.push(block("resource", ["aws_iam_instance_profile", `${id}_profile`], indent([
3920
+ attr("name", `${attachTo}-profile`),
3921
+ `role = aws_iam_role.${id}_role.name`
3922
+ ].join("\n"))));
3923
+ }
3924
+ return parts.join("\n");
3925
+ }
3926
+ // ── Events ────────────────────────────────────────────────────────────
3927
+ case "Events.EventBridge": {
3928
+ const rules = props.rules ?? [];
3929
+ const busName = props.busName ?? "default";
3930
+ const parts = [];
3931
+ if (busName !== "default") {
3932
+ parts.push(block("resource", ["aws_cloudwatch_event_bus", id], indent(attr("name", busName))));
3933
+ }
3934
+ for (const r of rules) {
3935
+ const ruleId = (r.name ?? "rule").replace(/[^a-zA-Z0-9_]/g, "_");
3936
+ const pattern = {};
3937
+ if (r.source)
3938
+ pattern["source"] = r.source;
3939
+ if (r.detailTypes)
3940
+ pattern["detail-type"] = r.detailTypes;
3941
+ const ruleBody = indent([
3942
+ attr("name", r.name),
3943
+ `event_bus_name = "${hclString(busName)}"`,
3944
+ `event_pattern = jsonencode(${JSON.stringify(pattern)})`,
3945
+ attr("state", "ENABLED")
3946
+ ].join("\n"));
3947
+ parts.push(block("resource", ["aws_cloudwatch_event_rule", `${id}_${ruleId}`], ruleBody));
3948
+ if (r.targetArn) {
3949
+ const targetBody = indent([
3950
+ `rule = aws_cloudwatch_event_rule.${id}_${ruleId}.name`,
3951
+ attr("target_id", `${ruleId}Target`),
3952
+ attr("arn", r.targetArn)
3953
+ ].join("\n"));
3954
+ parts.push(block("resource", ["aws_cloudwatch_event_target", `${id}_${ruleId}_target`], targetBody));
3955
+ }
3956
+ }
3957
+ return parts.join("\n");
3958
+ }
3959
+ // ── Workflow ──────────────────────────────────────────────────────────
3960
+ case "Workflow.StepFunctions": {
3961
+ const steps = props.steps ?? [];
3962
+ const definition = {
3963
+ Comment: props.description ?? `Workflow ${construct.id}`,
3964
+ StartAt: steps[0]?.name ?? "Start",
3965
+ States: Object.fromEntries(steps.map((s, i) => [s.name, {
3966
+ Type: s.type ?? "Task",
3967
+ Resource: s.resource ?? "",
3968
+ ...i < steps.length - 1 ? { Next: steps[i + 1].name } : { End: true }
3969
+ }]))
3970
+ };
3971
+ const body = indent([
3972
+ attr("name", construct.id),
3973
+ attr("type", props.type ?? "STANDARD"),
3974
+ `definition = jsonencode(${JSON.stringify(definition)})`,
3975
+ attr("role_arn", "arn:aws:iam::ACCOUNT_ID:role/StepFunctionsExecutionRole"),
3976
+ "",
3977
+ tagsBlock(construct.id)
3978
+ ].join("\n"));
3979
+ return block("resource", ["aws_sfn_state_machine", id], body);
3980
+ }
3981
+ // ── Messaging ─────────────────────────────────────────────────────────
3982
+ case "Messaging.Queue": {
3983
+ const fifo = props.fifo ?? false;
3984
+ const body = indent([
3985
+ attr("name", fifo ? `${construct.id}.fifo` : construct.id),
3986
+ attr("visibility_timeout_seconds", props.visibilityTimeoutSeconds ?? 30),
3987
+ attr("message_retention_seconds", props.messageRetentionSeconds ?? 345600),
3988
+ attr("delay_seconds", props.delaySeconds ?? 0),
3989
+ attr("fifo_queue", fifo),
3990
+ attr("sqs_managed_sse_enabled", props.encrypted ?? true),
3991
+ ...props.dlqArn ? [`redrive_policy = jsonencode({
3992
+ deadLetterTargetArn = "${hclString(props.dlqArn)}"
3993
+ maxReceiveCount = ${props.maxReceiveCount ?? 3}
3994
+ })`] : [],
3995
+ "",
3996
+ tagsBlock(construct.id)
3997
+ ].join("\n"));
3998
+ return block("resource", ["aws_sqs_queue", id], body);
3999
+ }
4000
+ case "Messaging.Topic": {
4001
+ const fifo = props.fifo ?? false;
4002
+ const subscriptions = props.subscriptions ?? [];
4003
+ const topicBody = indent([
4004
+ attr("name", fifo ? `${construct.id}.fifo` : construct.id),
4005
+ attr("display_name", props.displayName ?? construct.id),
4006
+ attr("fifo_topic", fifo),
4007
+ ...props.encrypted ? [attr("kms_master_key_id", "alias/aws/sns")] : [],
4008
+ "",
4009
+ tagsBlock(construct.id)
4010
+ ].join("\n"));
4011
+ const subBlocks = subscriptions.map((s, i) => block("resource", ["aws_sns_topic_subscription", `${id}_sub_${i}`], indent([
4012
+ `topic_arn = aws_sns_topic.${id}.arn`,
4013
+ attr("protocol", s.protocol),
4014
+ attr("endpoint", s.endpoint)
4015
+ ].join("\n")))).join("\n");
4016
+ return [block("resource", ["aws_sns_topic", id], topicBody), subBlocks].filter(Boolean).join("\n");
4017
+ }
4018
+ // ── Secret / Certificate ──────────────────────────────────────────────
4019
+ case "Secret.Vault": {
4020
+ const body = indent([
4021
+ attr("name", construct.id),
4022
+ attr("description", props.description ?? `Secret ${construct.id}`),
4023
+ ...props.kmsKeyId ? [attr("kms_key_id", props.kmsKeyId)] : [],
4024
+ ...props.rotationDays ? [`rotation_rules {
4025
+ automatically_after_days = ${props.rotationDays}
4026
+ }`] : [],
4027
+ "",
4028
+ tagsBlock(construct.id)
4029
+ ].join("\n"));
4030
+ return block("resource", ["aws_secretsmanager_secret", id], body);
4031
+ }
4032
+ case "Certificate.TLS": {
4033
+ const sans = props.subjectAlternativeNames ?? [];
4034
+ const body = indent([
4035
+ attr("domain_name", props.domainName),
4036
+ attr("validation_method", props.validationMethod ?? "DNS"),
4037
+ ...sans.length > 0 ? [`subject_alternative_names = ${JSON.stringify(sans)}`] : [],
4038
+ `lifecycle {
4039
+ create_before_destroy = true
4040
+ }`,
4041
+ "",
4042
+ tagsBlock(construct.id)
4043
+ ].join("\n"));
4044
+ return block("resource", ["aws_acm_certificate", id], body);
4045
+ }
4046
+ // ── Monitoring ────────────────────────────────────────────────────────
4047
+ case "Monitoring.Alarm": {
4048
+ const dimensions = props.dimensions;
4049
+ const dimBlock = dimensions ? `dimensions = {
4050
+ ${Object.entries(dimensions).map(([k, v]) => ` ${k} = "${hclString(v)}"`).join("\n")}
4051
+ }` : "";
4052
+ const body = indent([
4053
+ attr("alarm_name", construct.id),
4054
+ attr("metric_name", props.metricName),
4055
+ attr("namespace", props.namespace ?? "AWS/Lambda"),
4056
+ attr("threshold", props.threshold),
4057
+ attr("evaluation_periods", props.evaluationPeriods ?? 2),
4058
+ attr("period", props.periodSeconds ?? 60),
4059
+ attr("comparison_operator", props.comparisonOperator ?? "GreaterThanThreshold"),
4060
+ attr("statistic", props.statistic ?? "Average"),
4061
+ attr("treat_missing_data", props.treatMissingData ?? "notBreaching"),
4062
+ ...props.alarmActions ? [`alarm_actions = ${JSON.stringify(props.alarmActions)}`] : [],
4063
+ ...props.okActions ? [`ok_actions = ${JSON.stringify(props.okActions)}`] : [],
4064
+ dimBlock
4065
+ ].filter(Boolean).join("\n"));
4066
+ return block("resource", ["aws_cloudwatch_metric_alarm", id], body);
4067
+ }
4068
+ case "Monitoring.Dashboard": {
4069
+ const widgets = props.widgets ?? [];
4070
+ const dashBody = {
4071
+ widgets: widgets.map((w, i) => ({
4072
+ type: w.type === "text" ? "text" : "metric",
4073
+ x: i % 3 * 8,
4074
+ y: Math.floor(i / 3) * 6,
4075
+ width: 8,
4076
+ height: 6,
4077
+ properties: w.type === "text" ? { markdown: w.markdown ?? w.title } : { title: w.title, metrics: [[w.namespace ?? "AWS/Lambda", w.metricName]], period: w.period ?? 60, stat: w.stat ?? "Average", view: "timeSeries" }
4078
+ }))
4079
+ };
4080
+ const body = indent([
4081
+ attr("dashboard_name", construct.id),
4082
+ `dashboard_body = jsonencode(${JSON.stringify(dashBody)})`
4083
+ ].join("\n"));
4084
+ return block("resource", ["aws_cloudwatch_dashboard", id], body);
4085
+ }
4086
+ case "Logging.Stream": {
4087
+ const filters = props.subscriptionFilters ?? [];
4088
+ const lgBody = indent([
4089
+ attr("name", `/iacmp/${construct.id}`),
4090
+ attr("retention_in_days", props.retentionDays ?? 30),
4091
+ ...props.kmsKeyId ? [attr("kms_key_id", props.kmsKeyId)] : [],
4092
+ "",
4093
+ tagsBlock(construct.id)
4094
+ ].join("\n"));
4095
+ const parts = [block("resource", ["aws_cloudwatch_log_group", id], lgBody)];
4096
+ for (const f of filters) {
4097
+ const filterId = `${id}_${f.name.replace(/[^a-zA-Z0-9_]/g, "_")}_filter`;
4098
+ const filterBody = indent([
4099
+ `log_group_name = aws_cloudwatch_log_group.${id}.name`,
4100
+ attr("name", f.name),
4101
+ attr("filter_pattern", f.filterPattern),
4102
+ attr("destination_arn", f.destinationArn)
4103
+ ].join("\n"));
4104
+ parts.push(block("resource", ["aws_cloudwatch_log_subscription_filter", filterId], filterBody));
4105
+ }
4106
+ return parts.join("\n");
4107
+ }
4108
+ case "Custom.Resource": {
4109
+ const tf = props.terraform;
4110
+ if (!tf)
4111
+ return "";
4112
+ const body = indent(Object.entries(tf.body).map(([k, v]) => `${k} = ${hclValue(v)}`).join("\n"));
4113
+ return block("resource", [tf.type, id], body);
4114
+ }
4115
+ default:
4116
+ console.warn(`[terraform] Construct type '${construct.type}' nao suportado \u2014 descartado.`);
4117
+ return "";
4118
+ }
4119
+ }
4120
+ function synthesize(stack) {
4121
+ const awsBlock = block("aws", [], indent(`source = "hashicorp/aws"
4122
+ version = "~> 5.0"`));
4123
+ const requiredProvidersBlock = block("required_providers", [], indent(awsBlock));
4124
+ const terraformBlock = block("terraform", [], indent(requiredProvidersBlock));
4125
+ const providerBlock = block("provider", ["aws"], indent(attr("region", "us-east-1")));
4126
+ const needsDbPassword = stack.constructs.some((c) => c.type === "Database.SQL" || c.type === "Database.DocumentDB");
4127
+ const dbPasswordVar = needsDbPassword ? block("variable", ["db_password"], indent([
4128
+ "type = string",
4129
+ "sensitive = true",
4130
+ attr("description", "Senha do administrador do banco de dados (Database.SQL/DocumentDB). Forneca via TF_VAR_db_password ou tfvars.")
4131
+ ].join("\n"))) : "";
4132
+ const header = [terraformBlock, providerBlock, dbPasswordVar].filter(Boolean).join("\n");
4133
+ const resources = stack.constructs.map((c) => synthesizeConstruct(c)).filter(Boolean).join("\n");
4134
+ return [header, resources].filter(Boolean).join("\n");
4135
+ }
4136
+ }
4137
+ });
4138
+
4139
+ // ../providers/terraform/dist/provider.js
4140
+ var require_provider4 = __commonJS({
4141
+ "../providers/terraform/dist/provider.js"(exports2) {
4142
+ "use strict";
4143
+ Object.defineProperty(exports2, "__esModule", { value: true });
4144
+ exports2.TerraformProvider = void 0;
4145
+ var hcl_1 = require_hcl();
4146
+ var TerraformProvider2 = class {
4147
+ name = "terraform";
4148
+ synthesize(stack) {
4149
+ return (0, hcl_1.synthesize)(stack);
4150
+ }
4151
+ };
4152
+ exports2.TerraformProvider = TerraformProvider2;
4153
+ }
4154
+ });
4155
+
4156
+ // ../providers/terraform/dist/index.js
4157
+ var require_dist4 = __commonJS({
4158
+ "../providers/terraform/dist/index.js"(exports2) {
4159
+ "use strict";
4160
+ Object.defineProperty(exports2, "__esModule", { value: true });
4161
+ exports2.synthesize = exports2.TerraformProvider = void 0;
4162
+ var provider_1 = require_provider4();
4163
+ Object.defineProperty(exports2, "TerraformProvider", { enumerable: true, get: function() {
4164
+ return provider_1.TerraformProvider;
4165
+ } });
4166
+ var hcl_1 = require_hcl();
4167
+ Object.defineProperty(exports2, "synthesize", { enumerable: true, get: function() {
4168
+ return hcl_1.synthesize;
4169
+ } });
4170
+ }
4171
+ });
4172
+
4173
+ // ../plugin-sdk/dist/plugin.js
4174
+ var require_plugin = __commonJS({
4175
+ "../plugin-sdk/dist/plugin.js"(exports2) {
4176
+ "use strict";
4177
+ Object.defineProperty(exports2, "__esModule", { value: true });
4178
+ exports2.definePlugin = definePlugin;
4179
+ function definePlugin(plugin) {
4180
+ return plugin;
4181
+ }
4182
+ }
4183
+ });
4184
+
4185
+ // ../plugin-sdk/dist/loader.js
4186
+ var require_loader = __commonJS({
4187
+ "../plugin-sdk/dist/loader.js"(exports2) {
4188
+ "use strict";
4189
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
4190
+ if (k2 === void 0) k2 = k;
4191
+ var desc = Object.getOwnPropertyDescriptor(m, k);
4192
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
4193
+ desc = { enumerable: true, get: function() {
4194
+ return m[k];
4195
+ } };
4196
+ }
4197
+ Object.defineProperty(o, k2, desc);
4198
+ }) : (function(o, m, k, k2) {
4199
+ if (k2 === void 0) k2 = k;
4200
+ o[k2] = m[k];
4201
+ }));
4202
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
4203
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
4204
+ }) : function(o, v) {
4205
+ o["default"] = v;
4206
+ });
4207
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
4208
+ var ownKeys = function(o) {
4209
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
4210
+ var ar = [];
4211
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
4212
+ return ar;
4213
+ };
4214
+ return ownKeys(o);
4215
+ };
4216
+ return function(mod) {
4217
+ if (mod && mod.__esModule) return mod;
4218
+ var result = {};
4219
+ if (mod != null) {
4220
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
4221
+ }
4222
+ __setModuleDefault(result, mod);
4223
+ return result;
4224
+ };
4225
+ })();
4226
+ Object.defineProperty(exports2, "__esModule", { value: true });
4227
+ exports2.loadPlugins = loadPlugins2;
4228
+ var fs2 = __importStar(require("fs"));
4229
+ var path3 = __importStar(require("path"));
4230
+ function loadPlugins2(projectDir) {
4231
+ const configPath = path3.join(projectDir, "iacmp.json");
4232
+ if (!fs2.existsSync(configPath)) {
4233
+ return [];
4234
+ }
4235
+ let config;
4236
+ try {
4237
+ config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
4238
+ } catch (err) {
4239
+ throw new Error(`[iacmp] Falha ao ler ${configPath}: ${err.message}`);
4240
+ }
4241
+ if (!config.plugins || config.plugins.length === 0) {
4242
+ return [];
4243
+ }
4244
+ const providers = [];
4245
+ for (const pluginName of config.plugins) {
4246
+ try {
4247
+ const pluginPath = require.resolve(pluginName, { paths: [projectDir] });
4248
+ const raw = require(pluginPath);
4249
+ const mod = raw.default ?? raw;
4250
+ if (!mod || !Array.isArray(mod.providers)) {
4251
+ console.warn(`[iacmp] Plugin '${pluginName}' nao exporta um IacmpPlugin valido (esperado { providers: IacmpProvider[] }).`);
4252
+ continue;
4253
+ }
4254
+ providers.push(...mod.providers);
4255
+ } catch (err) {
4256
+ console.warn(`[iacmp] Plugin '${pluginName}' nao pode ser carregado: ${err.message}`);
4257
+ }
4258
+ }
4259
+ return providers;
4260
+ }
4261
+ }
4262
+ });
4263
+
4264
+ // ../plugin-sdk/dist/index.js
4265
+ var require_dist5 = __commonJS({
4266
+ "../plugin-sdk/dist/index.js"(exports2) {
4267
+ "use strict";
4268
+ Object.defineProperty(exports2, "__esModule", { value: true });
4269
+ exports2.loadPlugins = exports2.definePlugin = void 0;
4270
+ var plugin_1 = require_plugin();
4271
+ Object.defineProperty(exports2, "definePlugin", { enumerable: true, get: function() {
4272
+ return plugin_1.definePlugin;
4273
+ } });
4274
+ var loader_1 = require_loader();
4275
+ Object.defineProperty(exports2, "loadPlugins", { enumerable: true, get: function() {
4276
+ return loader_1.loadPlugins;
4277
+ } });
4278
+ }
4279
+ });
4280
+
4281
+ // src/commands/synth.ts
4282
+ var synth_exports = {};
4283
+ __export(synth_exports, {
4284
+ default: () => Synth
4285
+ });
4286
+ module.exports = __toCommonJS(synth_exports);
4287
+ var import_core = require("@oclif/core");
4288
+ var fs = __toESM(require("fs"));
4289
+ var path2 = __toESM(require("path"));
4290
+ var import_provider_aws = __toESM(require_dist());
4291
+ var import_provider_azure = __toESM(require_dist2());
4292
+ var import_provider_gcp = __toESM(require_dist3());
4293
+ var import_provider_terraform = __toESM(require_dist4());
4294
+ var import_plugin_sdk = __toESM(require_dist5());
4295
+
4296
+ // src/synth-out.ts
4297
+ var path = __toESM(require("path"));
4298
+ function synthRoot(cwd) {
4299
+ return path.join(cwd, "synth-out");
4300
+ }
4301
+ function providerOutDir(cwd, provider) {
4302
+ return path.join(synthRoot(cwd), provider);
4303
+ }
4304
+
4305
+ // src/commands/synth.ts
4306
+ var Synth = class _Synth extends import_core.Command {
4307
+ static description = "Sintetiza as stacks para o formato nativo do provider";
4308
+ static flags = {
4309
+ provider: import_core.Flags.string({ char: "p", description: "Provider alvo (aws, azure, gcp, terraform)", default: "aws" }),
4310
+ stack: import_core.Flags.string({ char: "s", description: "Nome da stack espec\xEDfica" })
4311
+ };
4312
+ static examples = [
4313
+ "$ iacmp synth",
4314
+ "$ iacmp synth --provider aws",
4315
+ "$ iacmp synth --provider azure",
4316
+ "$ iacmp synth --provider gcp",
4317
+ "$ iacmp synth --provider terraform",
4318
+ "$ iacmp synth --stack minha-stack"
4319
+ ];
4320
+ async run() {
4321
+ const { flags } = await this.parse(_Synth);
4322
+ const cwd = process.cwd();
4323
+ const configPath = path2.join(cwd, "iacmp.json");
4324
+ if (!fs.existsSync(configPath)) {
4325
+ this.error("Projeto n\xE3o inicializado. Rode: iacmp init");
4326
+ }
4327
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
4328
+ const provider = flags.provider ?? config.provider ?? "aws";
4329
+ const stacksDir = path2.join(cwd, "stacks");
4330
+ if (!fs.existsSync(stacksDir)) {
4331
+ this.error("Diret\xF3rio stacks/ n\xE3o encontrado.");
4332
+ }
4333
+ const findStackFiles = (dir) => {
4334
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
4335
+ const files = [];
4336
+ for (const entry of entries) {
4337
+ const full = path2.join(dir, entry.name);
4338
+ if (entry.isDirectory()) {
4339
+ files.push(...findStackFiles(full));
4340
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) {
4341
+ files.push(full);
4342
+ }
4343
+ }
4344
+ return files;
4345
+ };
4346
+ const stackFiles = findStackFiles(stacksDir).filter((f) => !flags.stack || path2.basename(f).replace(/\.(ts|js)$/, "") === flags.stack);
4347
+ if (stackFiles.length === 0) {
4348
+ this.error("Nenhuma stack encontrada em stacks/");
4349
+ }
4350
+ const outDir = synthRoot(cwd);
4351
+ if (!fs.existsSync(outDir)) {
4352
+ fs.mkdirSync(outDir, { recursive: true });
4353
+ }
4354
+ const nativeProviders = ["aws", "azure", "gcp", "terraform"];
4355
+ const pluginProviders = (0, import_plugin_sdk.loadPlugins)(cwd);
4356
+ const pluginProvider = pluginProviders.find((p) => p.name === provider);
4357
+ if (!nativeProviders.includes(provider) && !pluginProvider) {
4358
+ this.error(`Provider '${provider}' n\xE3o encontrado. Providers dispon\xEDveis: ${nativeProviders.join(", ")}`);
4359
+ }
4360
+ for (const stackPath of stackFiles) {
4361
+ const file = path2.basename(stackPath);
4362
+ const stackName = file.replace(/\.(ts|js)$/, "");
4363
+ let stackModule;
4364
+ try {
4365
+ if (file.endsWith(".ts")) {
4366
+ const tsNodePath = this.resolveTsNode(cwd);
4367
+ if (tsNodePath) {
4368
+ require(tsNodePath).register({
4369
+ transpileOnly: true,
4370
+ skipProject: true,
4371
+ compilerOptions: {
4372
+ target: "ES2022",
4373
+ module: "commonjs",
4374
+ moduleResolution: "node",
4375
+ esModuleInterop: true,
4376
+ strict: false,
4377
+ skipLibCheck: true
4378
+ }
4379
+ });
4380
+ } else {
4381
+ this.warn(`ts-node n\xE3o encontrado em ${cwd}/node_modules. Rode: npm install`);
4382
+ continue;
4383
+ }
4384
+ }
4385
+ stackModule = require(stackPath);
4386
+ } catch (err) {
4387
+ this.warn(`N\xE3o foi poss\xEDvel carregar ${file}: ${err.message}`);
4388
+ continue;
4389
+ }
4390
+ const stack = stackModule.default ?? stackModule.stack ?? stackModule;
4391
+ if (!stack || typeof stack !== "object" || !("constructs" in stack)) {
4392
+ this.warn(`${file} n\xE3o exporta uma Stack v\xE1lida. Exporte a stack como default.`);
4393
+ continue;
4394
+ }
4395
+ const typedStack = stack;
4396
+ const provOutDir = providerOutDir(cwd, provider);
4397
+ fs.mkdirSync(provOutDir, { recursive: true });
4398
+ switch (provider) {
4399
+ case "aws": {
4400
+ const p = new import_provider_aws.AWSProvider();
4401
+ const template = p.synthesize(typedStack);
4402
+ const outPath = path2.join(provOutDir, `${stackName}.json`);
4403
+ fs.writeFileSync(outPath, JSON.stringify(template, null, 2) + "\n");
4404
+ this.log(`Sintetizado: ${outPath}`);
4405
+ break;
4406
+ }
4407
+ case "azure": {
4408
+ const p = new import_provider_azure.AzureProvider();
4409
+ const template = p.synthesize(typedStack);
4410
+ const outPath = path2.join(provOutDir, `${stackName}.json`);
4411
+ fs.writeFileSync(outPath, JSON.stringify(template, null, 2) + "\n");
4412
+ this.log(`Sintetizado: ${outPath}`);
4413
+ break;
4414
+ }
4415
+ case "gcp": {
4416
+ const p = new import_provider_gcp.GCPProvider();
4417
+ const deployment = p.synthesize(typedStack);
4418
+ const outPath = path2.join(provOutDir, `${stackName}.json`);
4419
+ fs.writeFileSync(outPath, JSON.stringify(deployment, null, 2) + "\n");
4420
+ this.log(`Sintetizado: ${outPath}`);
4421
+ break;
4422
+ }
4423
+ case "terraform": {
4424
+ const p = new import_provider_terraform.TerraformProvider();
4425
+ const hcl = p.synthesize(typedStack);
4426
+ const outPath = path2.join(provOutDir, `${stackName}.tf`);
4427
+ fs.writeFileSync(outPath, hcl);
4428
+ this.log(`Sintetizado: ${outPath}`);
4429
+ break;
4430
+ }
4431
+ default: {
4432
+ if (pluginProvider) {
4433
+ const output = pluginProvider.synthesize(typedStack);
4434
+ const outPath = path2.join(provOutDir, `${stackName}.json`);
4435
+ fs.writeFileSync(outPath, JSON.stringify(output, null, 2) + "\n");
4436
+ this.log(`Sintetizado via plugin '${provider}': ${outPath}`);
4437
+ }
4438
+ break;
4439
+ }
4440
+ }
4441
+ }
4442
+ }
4443
+ resolveTsNode(projectDir) {
4444
+ const dirs = [];
4445
+ let dir = projectDir;
4446
+ for (let i = 0; i < 5; i++) {
4447
+ dirs.push(dir);
4448
+ const parent = path2.dirname(dir);
4449
+ if (parent === dir) break;
4450
+ dir = parent;
4451
+ }
4452
+ for (const d of dirs) {
4453
+ const tsNodePath = path2.join(d, "node_modules", "ts-node");
4454
+ if (fs.existsSync(tsNodePath)) return tsNodePath;
4455
+ }
4456
+ return null;
4457
+ }
4458
+ };