puls-dev 0.1.7 → 0.1.9

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.
Files changed (45) hide show
  1. package/README.md +10 -8
  2. package/dist/core/checker.d.ts +1 -1
  3. package/dist/core/checker.js +88 -56
  4. package/dist/core/decorators.js +8 -2
  5. package/dist/core/resource.js +2 -2
  6. package/dist/core/stack.d.ts +1 -1
  7. package/dist/core/stack.js +2 -2
  8. package/dist/providers/aws/acm.d.ts +1 -1
  9. package/dist/providers/aws/acm.js +27 -23
  10. package/dist/providers/aws/api.d.ts +14 -14
  11. package/dist/providers/aws/api.js +21 -21
  12. package/dist/providers/aws/apigateway.d.ts +2 -2
  13. package/dist/providers/aws/apigateway.js +33 -29
  14. package/dist/providers/aws/cloudfront.d.ts +3 -3
  15. package/dist/providers/aws/cloudfront.js +49 -34
  16. package/dist/providers/aws/fargate.d.ts +2 -2
  17. package/dist/providers/aws/fargate.js +99 -52
  18. package/dist/providers/aws/lambda.d.ts +2 -2
  19. package/dist/providers/aws/lambda.js +63 -32
  20. package/dist/providers/aws/rds.d.ts +1 -1
  21. package/dist/providers/aws/rds.js +77 -39
  22. package/dist/providers/aws/route53.d.ts +5 -5
  23. package/dist/providers/aws/route53.js +42 -35
  24. package/dist/providers/aws/s3.d.ts +2 -2
  25. package/dist/providers/aws/s3.js +40 -33
  26. package/dist/providers/aws/secrets.js +15 -7
  27. package/dist/providers/aws/sqs.d.ts +1 -1
  28. package/dist/providers/aws/sqs.js +47 -23
  29. package/dist/providers/do/domain.d.ts +4 -4
  30. package/dist/providers/do/domain.js +15 -11
  31. package/dist/providers/firebase/auth.d.ts +1 -1
  32. package/dist/providers/firebase/auth.js +65 -33
  33. package/dist/providers/firebase/firestore.d.ts +2 -2
  34. package/dist/providers/firebase/firestore.js +45 -28
  35. package/dist/providers/firebase/functions.d.ts +1 -1
  36. package/dist/providers/firebase/functions.js +75 -42
  37. package/dist/providers/firebase/hosting.d.ts +3 -1
  38. package/dist/providers/firebase/hosting.js +102 -56
  39. package/dist/providers/firebase/remoteconfig.d.ts +1 -1
  40. package/dist/providers/firebase/remoteconfig.js +42 -33
  41. package/dist/providers/firebase/storage.d.ts +1 -1
  42. package/dist/providers/firebase/storage.js +38 -24
  43. package/dist/providers/proxmox/vm.js +34 -22
  44. package/dist/types/aws.js +1 -1
  45. package/package.json +2 -1
@@ -1,17 +1,17 @@
1
- import { DescribeDBInstancesCommand, CreateDBInstanceCommand, ModifyDBInstanceCommand, DeleteDBInstanceCommand, DescribeDBSubnetGroupsCommand, CreateDBSubnetGroupCommand, } from '@aws-sdk/client-rds';
2
- import { DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, CreateSecurityGroupCommand, AuthorizeSecurityGroupIngressCommand, } from '@aws-sdk/client-ec2';
3
- import { BaseBuilder } from '../../core/resource.js';
4
- import { getRDSClient, getEC2Client } from './api.js';
5
- import { Config } from '../../core/config.js';
1
+ import { DescribeDBInstancesCommand, CreateDBInstanceCommand, ModifyDBInstanceCommand, DeleteDBInstanceCommand, DescribeDBSubnetGroupsCommand, CreateDBSubnetGroupCommand, } from "@aws-sdk/client-rds";
2
+ import { DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, CreateSecurityGroupCommand, AuthorizeSecurityGroupIngressCommand, } from "@aws-sdk/client-ec2";
3
+ import { BaseBuilder } from "../../core/resource.js";
4
+ import { getRDSClient, getEC2Client } from "./api.js";
5
+ import { Config } from "../../core/config.js";
6
6
  const DB_PORT = {
7
7
  postgres: 5432,
8
8
  mysql: 3306,
9
9
  mariadb: 3306,
10
10
  };
11
11
  export class RDSBuilder extends BaseBuilder {
12
- _engine = 'postgres';
13
- _engineVersion = '16';
14
- _instanceClass = 'db.t3.micro';
12
+ _engine = "postgres";
13
+ _engineVersion = "16";
14
+ _instanceClass = "db.t3.micro";
15
15
  _storage = 20;
16
16
  _username;
17
17
  _password;
@@ -31,12 +31,30 @@ export class RDSBuilder extends BaseBuilder {
31
31
  this._engineVersion = e.version;
32
32
  return this;
33
33
  }
34
- size(instanceClass) { this._instanceClass = instanceClass; return this; }
35
- storage(gb) { this._storage = gb; return this; }
36
- subnets(ids) { this._subnetIds = ids; return this; }
37
- securityGroups(ids) { this._securityGroupIds = ids; return this; }
38
- publicAccess(enabled = true) { this._publicAccess = enabled; return this; }
39
- database(name) { this._dbName = name; return this; }
34
+ size(instanceClass) {
35
+ this._instanceClass = instanceClass;
36
+ return this;
37
+ }
38
+ storage(gb) {
39
+ this._storage = gb;
40
+ return this;
41
+ }
42
+ subnets(ids) {
43
+ this._subnetIds = ids;
44
+ return this;
45
+ }
46
+ securityGroups(ids) {
47
+ this._securityGroupIds = ids;
48
+ return this;
49
+ }
50
+ publicAccess(enabled = true) {
51
+ this._publicAccess = enabled;
52
+ return this;
53
+ }
54
+ database(name) {
55
+ this._dbName = name;
56
+ return this;
57
+ }
40
58
  credentials(username, password) {
41
59
  this._username = username;
42
60
  this._password = password;
@@ -48,7 +66,7 @@ export class RDSBuilder extends BaseBuilder {
48
66
  DBInstanceIdentifier: identifier,
49
67
  }));
50
68
  const instance = result.DBInstances?.[0];
51
- if (!instance || instance.DBInstanceStatus === 'deleting')
69
+ if (!instance || instance.DBInstanceStatus === "deleting")
52
70
  return null;
53
71
  this.resolvedArn = instance.DBInstanceArn ?? null;
54
72
  this.resolvedEndpoint = instance.Endpoint?.Address ?? null;
@@ -56,9 +74,9 @@ export class RDSBuilder extends BaseBuilder {
56
74
  return instance;
57
75
  }
58
76
  catch (e) {
59
- if (e.name === 'DBInstanceNotFound')
77
+ if (e.name === "DBInstanceNotFound")
60
78
  return null;
61
- if (e.name === 'CredentialsProviderError')
79
+ if (e.name === "CredentialsProviderError")
62
80
  return null;
63
81
  throw e;
64
82
  }
@@ -66,15 +84,17 @@ export class RDSBuilder extends BaseBuilder {
66
84
  async discoverDefaultVpc() {
67
85
  const ec2 = getEC2Client();
68
86
  const vpcs = await ec2.send(new DescribeVpcsCommand({
69
- Filters: [{ Name: 'isDefault', Values: ['true'] }],
87
+ Filters: [{ Name: "isDefault", Values: ["true"] }],
70
88
  }));
71
89
  const vpc = vpcs.Vpcs?.[0];
72
90
  if (!vpc?.VpcId)
73
91
  throw new Error(`[RDS:${this.name}] No default VPC found. Use .subnets(ids[]) to specify subnets.`);
74
92
  const subnets = await ec2.send(new DescribeSubnetsCommand({
75
- Filters: [{ Name: 'vpc-id', Values: [vpc.VpcId] }],
93
+ Filters: [{ Name: "vpc-id", Values: [vpc.VpcId] }],
76
94
  }));
77
- const subnetIds = (subnets.Subnets ?? []).map(s => s.SubnetId).filter(Boolean);
95
+ const subnetIds = (subnets.Subnets ?? [])
96
+ .map((s) => s.SubnetId)
97
+ .filter(Boolean);
78
98
  return { vpcId: vpc.VpcId, vpcCidr: vpc.CidrBlock, subnetIds };
79
99
  }
80
100
  async ensureSubnetGroup(subnetIds) {
@@ -85,7 +105,7 @@ export class RDSBuilder extends BaseBuilder {
85
105
  return groupName;
86
106
  }
87
107
  catch (e) {
88
- if (e.name !== 'DBSubnetGroupNotFoundFault')
108
+ if (e.name !== "DBSubnetGroupNotFoundFault")
89
109
  throw e;
90
110
  }
91
111
  await rds.send(new CreateDBSubnetGroupCommand({
@@ -102,8 +122,8 @@ export class RDSBuilder extends BaseBuilder {
102
122
  const port = DB_PORT[this._engine] ?? 5432;
103
123
  const existing = await ec2.send(new DescribeSecurityGroupsCommand({
104
124
  Filters: [
105
- { Name: 'group-name', Values: [sgName] },
106
- { Name: 'vpc-id', Values: [vpcId] },
125
+ { Name: "group-name", Values: [sgName] },
126
+ { Name: "vpc-id", Values: [vpcId] },
107
127
  ],
108
128
  }));
109
129
  if (existing.SecurityGroups?.length)
@@ -116,38 +136,49 @@ export class RDSBuilder extends BaseBuilder {
116
136
  const sgId = created.GroupId;
117
137
  // Allow inbound on DB port from VPC CIDR only (secure default)
118
138
  // Use .publicAccess() to open to the internet
119
- const cidr = this._publicAccess ? '0.0.0.0/0' : vpcCidr;
139
+ const cidr = this._publicAccess ? "0.0.0.0/0" : vpcCidr;
120
140
  await ec2.send(new AuthorizeSecurityGroupIngressCommand({
121
141
  GroupId: sgId,
122
- IpPermissions: [{
123
- IpProtocol: 'tcp',
142
+ IpPermissions: [
143
+ {
144
+ IpProtocol: "tcp",
124
145
  FromPort: port,
125
146
  ToPort: port,
126
- IpRanges: [{ CidrIp: cidr, Description: this._publicAccess ? 'Public access' : 'VPC only' }],
127
- }],
147
+ IpRanges: [
148
+ {
149
+ CidrIp: cidr,
150
+ Description: this._publicAccess ? "Public access" : "VPC only",
151
+ },
152
+ ],
153
+ },
154
+ ],
128
155
  }));
129
- console.log(` āœ… Created security group: ${sgName} (${sgId}) — port ${port} open to ${cidr}`);
156
+ console.log(` āœ… Created security group: ${sgName} (${sgId}) - port ${port} open to ${cidr}`);
130
157
  return sgId;
131
158
  }
132
159
  async deploy() {
133
160
  const dryRun = this.isDryRunActive();
134
161
  const existing = await this.discoveryPromise;
135
- const region = Config.get().providers.aws?.region ?? 'us-east-1';
162
+ const region = Config.get().providers.aws?.region ?? "us-east-1";
136
163
  const port = DB_PORT[this._engine] ?? 5432;
137
164
  console.log(`\n⚔ Finalizing RDS Instance "${this.name}"...`);
138
165
  if (dryRun) {
139
- console.log(` šŸ“ [PLAN] ${existing ? 'Update' : 'Create'} RDS instance "${this.name}"`);
166
+ console.log(` šŸ“ [PLAN] ${existing ? "Update" : "Create"} RDS instance "${this.name}"`);
140
167
  console.log(` └─ Engine: ${this._engine} ${this._engineVersion}`);
141
168
  console.log(` └─ Class: ${this._instanceClass} | Storage: ${this._storage}GB`);
142
169
  console.log(` └─ Port: ${port} | Public: ${this._publicAccess}`);
143
170
  this.resolvedEndpoint = `${this.name}.DRYRUN.${region}.rds.amazonaws.com`;
144
171
  this.resolvedPort = port;
145
- return { name: this.name, endpoint: this.resolvedEndpoint, port: this.resolvedPort };
172
+ return {
173
+ name: this.name,
174
+ endpoint: this.resolvedEndpoint,
175
+ port: this.resolvedPort,
176
+ };
146
177
  }
147
178
  if (!this._username || !this._password) {
148
179
  throw new Error(`[RDS:${this.name}] .credentials(username, password) is required`);
149
180
  }
150
- // Resolve networking — only hit EC2 if needed
181
+ // Resolve networking - only hit EC2 if needed
151
182
  let subnetGroupName;
152
183
  let securityGroupIds;
153
184
  if (this._subnetIds && this._securityGroupIds) {
@@ -157,7 +188,9 @@ export class RDSBuilder extends BaseBuilder {
157
188
  else {
158
189
  const { vpcId, vpcCidr, subnetIds } = await this.discoverDefaultVpc();
159
190
  subnetGroupName = await this.ensureSubnetGroup(this._subnetIds ?? subnetIds);
160
- securityGroupIds = this._securityGroupIds ?? [await this.ensureSecurityGroup(vpcId, vpcCidr)];
191
+ securityGroupIds = this._securityGroupIds ?? [
192
+ await this.ensureSecurityGroup(vpcId, vpcCidr),
193
+ ];
161
194
  }
162
195
  const rds = getRDSClient();
163
196
  if (existing) {
@@ -183,15 +216,15 @@ export class RDSBuilder extends BaseBuilder {
183
216
  DBSubnetGroupName: subnetGroupName,
184
217
  VpcSecurityGroupIds: securityGroupIds,
185
218
  PubliclyAccessible: this._publicAccess,
186
- StorageType: 'gp3',
219
+ StorageType: "gp3",
187
220
  BackupRetentionPeriod: 7,
188
221
  DeletionProtection: false,
189
222
  }));
190
- console.log(`šŸš€ Creating RDS instance "${this.name}" — waiting for available...`);
223
+ console.log(`šŸš€ Creating RDS instance "${this.name}" - waiting for available...`);
191
224
  await this.waitFor(`"${this.name}" to become available`, async () => {
192
225
  const r = await getRDSClient().send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: this.name }));
193
226
  const inst = r.DBInstances?.[0];
194
- if (inst?.DBInstanceStatus === 'available') {
227
+ if (inst?.DBInstanceStatus === "available") {
195
228
  this.resolvedEndpoint = inst.Endpoint?.Address ?? null;
196
229
  this.resolvedPort = inst.Endpoint?.Port ?? null;
197
230
  this.resolvedArn = inst.DBInstanceArn ?? null;
@@ -202,14 +235,19 @@ export class RDSBuilder extends BaseBuilder {
202
235
  console.log(` 🌐 Endpoint: ${this.resolvedEndpoint}:${this.resolvedPort}`);
203
236
  }
204
237
  await this.deploySidecars();
205
- return { name: this.name, endpoint: this.resolvedEndpoint, port: this.resolvedPort, arn: this.resolvedArn };
238
+ return {
239
+ name: this.name,
240
+ endpoint: this.resolvedEndpoint,
241
+ port: this.resolvedPort,
242
+ arn: this.resolvedArn,
243
+ };
206
244
  }
207
245
  async destroy() {
208
246
  const dryRun = this.isDryRunActive();
209
247
  const existing = await this.discoveryPromise;
210
248
  console.log(`\nšŸ—‘ļø Destroying RDS Instance "${this.name}"...`);
211
249
  if (!existing) {
212
- console.log(` āœ… Instance "${this.name}" does not exist — nothing to do`);
250
+ console.log(` āœ… Instance "${this.name}" does not exist - nothing to do`);
213
251
  return { destroyed: this.name };
214
252
  }
215
253
  if (dryRun) {
@@ -1,7 +1,7 @@
1
- import { BaseBuilder } from '../../core/resource.js';
2
- import { Output } from '../../core/output.js';
3
- import { ACMCertificateBuilder } from './acm.js';
4
- import type { RegistrantContact } from '../../types/aws.js';
1
+ import { BaseBuilder } from "../../core/resource.js";
2
+ import { Output } from "../../core/output.js";
3
+ import { ACMCertificateBuilder } from "./acm.js";
4
+ import type { RegistrantContact } from "../../types/aws.js";
5
5
  export declare class Route53Builder extends BaseBuilder {
6
6
  readonly out: {
7
7
  zone: Output<{
@@ -21,7 +21,7 @@ export declare class Route53Builder extends BaseBuilder {
21
21
  cert(): ACMCertificateBuilder | undefined;
22
22
  withWildcardSSL(): this;
23
23
  register(contact?: RegistrantContact): this;
24
- record(name: string, type: 'A' | 'CNAME' | 'AAAA', value: string): this;
24
+ record(name: string, type: "A" | "CNAME" | "AAAA", value: string): this;
25
25
  pointer(name: string, target: BaseBuilder): this;
26
26
  deploy(): Promise<{
27
27
  zone: string;
@@ -1,9 +1,9 @@
1
- import { ListHostedZonesByNameCommand, CreateHostedZoneCommand, ChangeResourceRecordSetsCommand, } from '@aws-sdk/client-route-53';
2
- import { RegisterDomainCommand, GetOperationDetailCommand, CheckDomainAvailabilityCommand, } from '@aws-sdk/client-route-53-domains';
3
- import { BaseBuilder } from '../../core/resource.js';
4
- import { Output } from '../../core/output.js';
5
- import { ACMCertificateBuilder } from './acm.js';
6
- import { getR53Client, getR53DomainsClient } from './api.js';
1
+ import { ListHostedZonesByNameCommand, CreateHostedZoneCommand, ChangeResourceRecordSetsCommand, } from "@aws-sdk/client-route-53";
2
+ import { RegisterDomainCommand, GetOperationDetailCommand, CheckDomainAvailabilityCommand, } from "@aws-sdk/client-route-53-domains";
3
+ import { BaseBuilder } from "../../core/resource.js";
4
+ import { Output } from "../../core/output.js";
5
+ import { ACMCertificateBuilder } from "./acm.js";
6
+ import { getR53Client, getR53DomainsClient } from "./api.js";
7
7
  export class Route53Builder extends BaseBuilder {
8
8
  out = {
9
9
  zone: new Output(),
@@ -14,8 +14,8 @@ export class Route53Builder extends BaseBuilder {
14
14
  _isRegistering = false;
15
15
  _registrantContact;
16
16
  _wantsWildcardSSL = false;
17
- constructor(zoneName = '') {
18
- super(zoneName || 'route53-pending');
17
+ constructor(zoneName = "") {
18
+ super(zoneName || "route53-pending");
19
19
  this.zoneName = zoneName;
20
20
  this.discoveryPromise = this.discoverZone(zoneName);
21
21
  }
@@ -25,29 +25,29 @@ export class Route53Builder extends BaseBuilder {
25
25
  try {
26
26
  const r53 = getR53Client();
27
27
  const result = await r53.send(new ListHostedZonesByNameCommand({ DNSName: name, MaxItems: 5 }));
28
- const match = (result.HostedZones ?? []).find(z => z.Name === `${name}.`);
28
+ const match = (result.HostedZones ?? []).find((z) => z.Name === `${name}.`);
29
29
  if (match) {
30
- this.zoneId = match.Id.replace('/hostedzone/', '');
30
+ this.zoneId = match.Id.replace("/hostedzone/", "");
31
31
  return match;
32
32
  }
33
33
  return null;
34
34
  }
35
35
  catch (e) {
36
- if (e.name === 'CredentialsProviderError')
36
+ if (e.name === "CredentialsProviderError")
37
37
  return null;
38
38
  throw e;
39
39
  }
40
40
  }
41
41
  randomDomain() {
42
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
43
- const id = Array.from({ length: 12 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
42
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
43
+ const id = Array.from({ length: 12 }, () => chars[Math.floor(Math.random() * chars.length)]).join("");
44
44
  this.zoneName = `${id}.com`;
45
45
  this.name = this.zoneName;
46
46
  this.discoveryPromise = this.discoverZone(this.zoneName);
47
47
  return this;
48
48
  }
49
49
  cert() {
50
- return this.sidecars.find(s => s instanceof ACMCertificateBuilder);
50
+ return this.sidecars.find((s) => s instanceof ACMCertificateBuilder);
51
51
  }
52
52
  withWildcardSSL() {
53
53
  this._wantsWildcardSSL = true;
@@ -63,7 +63,7 @@ export class Route53Builder extends BaseBuilder {
63
63
  return this;
64
64
  }
65
65
  pointer(name, target) {
66
- this.records.push({ name, type: 'A', value: target, isAlias: true });
66
+ this.records.push({ name, type: "A", value: target, isAlias: true });
67
67
  return this;
68
68
  }
69
69
  async deploy() {
@@ -78,17 +78,17 @@ export class Route53Builder extends BaseBuilder {
78
78
  if (!existing) {
79
79
  if (dryRun) {
80
80
  console.log(` šŸ“ [PLAN] Create hosted zone ${this.zoneName}`);
81
- this.out.zone.resolve({ name: this.zoneName, id: 'PENDING' });
81
+ this.out.zone.resolve({ name: this.zoneName, id: "PENDING" });
82
82
  }
83
83
  else {
84
- // Route53 Domains auto-creates the hosted zone on registration — check again before creating
84
+ // Route53 Domains auto-creates the hosted zone on registration - check again before creating
85
85
  const recheck = await this.discoverZone(this.zoneName);
86
86
  if (!recheck) {
87
87
  const result = await r53.send(new CreateHostedZoneCommand({
88
88
  Name: this.zoneName,
89
89
  CallerReference: `puls-${Date.now()}`,
90
90
  }));
91
- this.zoneId = result.HostedZone.Id.replace('/hostedzone/', '');
91
+ this.zoneId = result.HostedZone.Id.replace("/hostedzone/", "");
92
92
  console.log(`šŸš€ Created hosted zone ${this.zoneName} (id=${this.zoneId})`);
93
93
  }
94
94
  else {
@@ -106,21 +106,23 @@ export class Route53Builder extends BaseBuilder {
106
106
  }
107
107
  const cert = this.cert();
108
108
  if (cert) {
109
- cert.forZone(this); // zoneId is set by now — cert writes its own validation CNAMEs
109
+ cert.forZone(this); // zoneId is set by now - cert writes its own validation CNAMEs
110
110
  await cert.deploy();
111
111
  }
112
112
  // Regular records
113
113
  if (this.records.length > 0 && !dryRun && this.zoneId) {
114
- const resolved = this.records.map(r => ({
114
+ const resolved = this.records.map((r) => ({
115
115
  type: r.type,
116
116
  name: r.name,
117
117
  value: r.value instanceof BaseBuilder ? `[alias: ${r.value.name}]` : r.value,
118
118
  }));
119
- await this.upsertRecords(r53, resolved.map(r => ({ ...r, ttl: 300 })));
119
+ await this.upsertRecords(r53, resolved.map((r) => ({ ...r, ttl: 300 })));
120
120
  }
121
121
  for (const rec of this.records) {
122
- const val = rec.value instanceof BaseBuilder ? `[Alias to ${rec.value.name}]` : rec.value;
123
- console.log(` āœ… [${dryRun ? 'PLAN' : 'OK'}] ${rec.type}: ${rec.name}.${this.zoneName} → ${val}`);
122
+ const val = rec.value instanceof BaseBuilder
123
+ ? `[Alias to ${rec.value.name}]`
124
+ : rec.value;
125
+ console.log(` āœ… [${dryRun ? "PLAN" : "OK"}] ${rec.type}: ${rec.name}.${this.zoneName} → ${val}`);
124
126
  }
125
127
  return { zone: this.zoneName, id: this.zoneId };
126
128
  }
@@ -135,13 +137,13 @@ export class Route53Builder extends BaseBuilder {
135
137
  }
136
138
  // Check availability before attempting registration
137
139
  const avail = await domains.send(new CheckDomainAvailabilityCommand({ DomainName: this.zoneName }));
138
- if (avail.Availability !== 'AVAILABLE') {
139
- console.log(` ā„¹ļø Domain ${this.zoneName} is not available for registration (${avail.Availability}) — skipping`);
140
+ if (avail.Availability !== "AVAILABLE") {
141
+ console.log(` ā„¹ļø Domain ${this.zoneName} is not available for registration (${avail.Availability}) - skipping`);
140
142
  return;
141
143
  }
142
144
  const contact = c ? this.mapContact(c) : undefined;
143
145
  if (!contact)
144
- throw new Error(`register() called without contact details — provide a RegistrantContact`);
146
+ throw new Error(`register() called without contact details - provide a RegistrantContact`);
145
147
  console.log(` šŸ“‹ Registering domain ${this.zoneName}... (est. ~5 min)`);
146
148
  const result = await domains.send(new RegisterDomainCommand({
147
149
  DomainName: this.zoneName,
@@ -157,10 +159,10 @@ export class Route53Builder extends BaseBuilder {
157
159
  console.log(`šŸš€ Domain registration submitted (operationId=${result.OperationId})`);
158
160
  await this.waitFor(`domain "${this.zoneName}" to become active`, async () => {
159
161
  const op = await domains.send(new GetOperationDetailCommand({ OperationId: result.OperationId }));
160
- if (op.Status === 'ERROR' || op.Status === 'FAILED') {
162
+ if (op.Status === "ERROR" || op.Status === "FAILED") {
161
163
  throw new Error(`Domain registration failed (${op.Status}): ${op.Message}`);
162
164
  }
163
- return op.Status === 'SUCCESSFUL';
165
+ return op.Status === "SUCCESSFUL";
164
166
  }, { intervalMs: 15_000, timeoutMs: 900_000 });
165
167
  console.log(` āœ… Domain ${this.zoneName} registered`);
166
168
  }
@@ -180,11 +182,11 @@ export class Route53Builder extends BaseBuilder {
180
182
  }
181
183
  // Route53 Domains requires +CC.subscriber format (e.g. +46.708339809)
182
184
  normalizePhone(phone) {
183
- if (phone.includes('.'))
185
+ if (phone.includes("."))
184
186
  return phone;
185
- const digits = phone.replace(/^\+/, '');
187
+ const digits = phone.replace(/^\+/, "");
186
188
  // +1 (US/CA) and +7 (RU/KZ) are single-digit country codes
187
- if (digits.startsWith('1') || digits.startsWith('7')) {
189
+ if (digits.startsWith("1") || digits.startsWith("7")) {
188
190
  return `+${digits[0]}.${digits.slice(1)}`;
189
191
  }
190
192
  // Default: treat first 2 digits as country code
@@ -192,9 +194,14 @@ export class Route53Builder extends BaseBuilder {
192
194
  }
193
195
  async upsertCnames(records) {
194
196
  if (!this.zoneId)
195
- throw new Error(`Zone ${this.zoneName} has no ID — was it deployed?`);
197
+ throw new Error(`Zone ${this.zoneName} has no ID - was it deployed?`);
196
198
  const r53 = getR53Client();
197
- await this.upsertRecords(r53, records.map(r => ({ type: 'CNAME', name: `${r.name}.${this.zoneName}`, value: r.value, ttl: 300 })));
199
+ await this.upsertRecords(r53, records.map((r) => ({
200
+ type: "CNAME",
201
+ name: `${r.name}.${this.zoneName}`,
202
+ value: r.value,
203
+ ttl: 300,
204
+ })));
198
205
  for (const r of records) {
199
206
  console.log(` āœ… CNAME ${r.name}.${this.zoneName} → ${r.value}`);
200
207
  }
@@ -203,8 +210,8 @@ export class Route53Builder extends BaseBuilder {
203
210
  await r53.send(new ChangeResourceRecordSetsCommand({
204
211
  HostedZoneId: this.zoneId,
205
212
  ChangeBatch: {
206
- Changes: records.map(r => ({
207
- Action: 'UPSERT',
213
+ Changes: records.map((r) => ({
214
+ Action: "UPSERT",
208
215
  ResourceRecordSet: {
209
216
  Name: r.name,
210
217
  Type: r.type,
@@ -1,5 +1,5 @@
1
- import { BaseBuilder } from '../../core/resource.js';
2
- import { CloudFrontBuilder } from './cloudfront.js';
1
+ import { BaseBuilder } from "../../core/resource.js";
2
+ import { CloudFrontBuilder } from "./cloudfront.js";
3
3
  export declare class S3BucketBuilder extends BaseBuilder {
4
4
  bucketName: string;
5
5
  private _versioning;
@@ -1,9 +1,9 @@
1
- import { readFileSync } from 'node:fs';
2
- import { basename, extname } from 'node:path';
3
- import { HeadBucketCommand, CreateBucketCommand, GetBucketPolicyCommand, PutBucketPolicyCommand, PutObjectCommand, } from '@aws-sdk/client-s3';
4
- import { BaseBuilder } from '../../core/resource.js';
5
- import { getS3Client } from './api.js';
6
- import { Config } from '../../core/config.js';
1
+ import { readFileSync } from "node:fs";
2
+ import { basename, extname } from "node:path";
3
+ import { HeadBucketCommand, CreateBucketCommand, GetBucketPolicyCommand, PutBucketPolicyCommand, PutObjectCommand, } from "@aws-sdk/client-s3";
4
+ import { BaseBuilder } from "../../core/resource.js";
5
+ import { getS3Client } from "./api.js";
6
+ import { Config } from "../../core/config.js";
7
7
  export class S3BucketBuilder extends BaseBuilder {
8
8
  bucketName;
9
9
  _versioning = false;
@@ -27,11 +27,11 @@ export class S3BucketBuilder extends BaseBuilder {
27
27
  }
28
28
  catch (e) {
29
29
  const status = e.$metadata?.httpStatusCode;
30
- if (status === 404 || e.name === 'NotFound')
30
+ if (status === 404 || e.name === "NotFound")
31
31
  return false;
32
32
  if (status === 301 || status === 403)
33
33
  return true; // exists in different region or access denied
34
- if (e.name === 'CredentialsProviderError')
34
+ if (e.name === "CredentialsProviderError")
35
35
  return false;
36
36
  throw e;
37
37
  }
@@ -51,7 +51,7 @@ export class S3BucketBuilder extends BaseBuilder {
51
51
  async deploy() {
52
52
  const dryRun = this.isDryRunActive();
53
53
  const exists = await this.discoveryPromise;
54
- const region = this._region ?? Config.get().providers.aws?.region ?? 'us-east-1';
54
+ const region = this._region ?? Config.get().providers.aws?.region ?? "us-east-1";
55
55
  const s3 = getS3Client(region);
56
56
  console.log(`\n🪣 Finalizing S3 Bucket "${this.bucketName}"...`);
57
57
  if (!exists) {
@@ -60,7 +60,7 @@ export class S3BucketBuilder extends BaseBuilder {
60
60
  }
61
61
  else {
62
62
  const createCmd = { Bucket: this.bucketName };
63
- if (region !== 'us-east-1') {
63
+ if (region !== "us-east-1") {
64
64
  createCmd.CreateBucketConfiguration = { LocationConstraint: region };
65
65
  }
66
66
  await s3.send(new CreateBucketCommand(createCmd));
@@ -71,13 +71,13 @@ export class S3BucketBuilder extends BaseBuilder {
71
71
  console.log(` āœ… Bucket ${this.bucketName} already exists.`);
72
72
  }
73
73
  if (this._allowedDistributions.length > 0) {
74
- const unresolved = this._allowedDistributions.filter(d => !d.resolvedArn);
74
+ const unresolved = this._allowedDistributions.filter((d) => !d.resolvedArn);
75
75
  if (unresolved.length > 0) {
76
76
  throw new Error(`[S3:${this.bucketName}] allowFrom() has unresolved distributions: ` +
77
- unresolved.map(d => `"${d.name}"`).join(', ') +
78
- '. Declare the bucket after all CloudFront distributions in your Stack.');
77
+ unresolved.map((d) => `"${d.name}"`).join(", ") +
78
+ ". Declare the bucket after all CloudFront distributions in your Stack.");
79
79
  }
80
- const newArns = this._allowedDistributions.map(d => d.resolvedArn);
80
+ const newArns = this._allowedDistributions.map((d) => d.resolvedArn);
81
81
  if (dryRun) {
82
82
  console.log(` šŸ“ [PLAN] Append ${newArns.length} CloudFront OAC ARN(s) to bucket policy`);
83
83
  for (const arn of newArns)
@@ -102,15 +102,16 @@ export class S3BucketBuilder extends BaseBuilder {
102
102
  const key = basename(filePath);
103
103
  const body = readFileSync(filePath);
104
104
  const contentTypeMap = {
105
- '.json': 'application/json',
106
- '.js': 'application/javascript',
107
- '.html': 'text/html',
108
- '.css': 'text/css',
109
- '.png': 'image/png',
110
- '.jpg': 'image/jpeg',
111
- '.svg': 'image/svg+xml',
105
+ ".json": "application/json",
106
+ ".js": "application/javascript",
107
+ ".html": "text/html",
108
+ ".css": "text/css",
109
+ ".png": "image/png",
110
+ ".jpg": "image/jpeg",
111
+ ".svg": "image/svg+xml",
112
112
  };
113
- const contentType = contentTypeMap[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
113
+ const contentType = contentTypeMap[extname(filePath).toLowerCase()] ??
114
+ "application/octet-stream";
114
115
  await s3.send(new PutObjectCommand({
115
116
  Bucket: this.bucketName,
116
117
  Key: key,
@@ -120,45 +121,51 @@ export class S3BucketBuilder extends BaseBuilder {
120
121
  console.log(` āœ… Uploaded ${key} → s3://${this.bucketName}/${key}`);
121
122
  }
122
123
  async updateBucketPolicy(s3, newArns) {
123
- let policy = { Version: '2012-10-17', Statement: [] };
124
+ let policy = { Version: "2012-10-17", Statement: [] };
124
125
  try {
125
126
  const existing = await s3.send(new GetBucketPolicyCommand({ Bucket: this.bucketName }));
126
127
  if (existing.Policy)
127
128
  policy = JSON.parse(existing.Policy);
128
129
  }
129
130
  catch (e) {
130
- if (e.name !== 'NoSuchBucketPolicy')
131
+ if (e.name !== "NoSuchBucketPolicy")
131
132
  throw e;
132
133
  }
133
134
  // Find any existing CloudFront-principal statement regardless of Sid
134
- let stmt = policy.Statement.find((s) => s.Principal?.Service === 'cloudfront.amazonaws.com' && s.Effect === 'Allow');
135
+ let stmt = policy.Statement.find((s) => s.Principal?.Service === "cloudfront.amazonaws.com" &&
136
+ s.Effect === "Allow");
135
137
  if (!stmt) {
136
138
  stmt = {
137
- Sid: 'AllowCloudFrontServicePrincipal',
138
- Effect: 'Allow',
139
- Principal: { Service: 'cloudfront.amazonaws.com' },
140
- Action: 's3:GetObject',
139
+ Sid: "AllowCloudFrontServicePrincipal",
140
+ Effect: "Allow",
141
+ Principal: { Service: "cloudfront.amazonaws.com" },
142
+ Action: "s3:GetObject",
141
143
  Resource: `arn:aws:s3:::${this.bucketName}/*`,
142
- Condition: { StringEquals: { 'AWS:SourceArn': [] } },
144
+ Condition: { StringEquals: { "AWS:SourceArn": [] } },
143
145
  };
144
146
  policy.Statement.push(stmt);
145
147
  }
146
148
  // Condition key may be 'aws:SourceArn' or 'AWS:SourceArn' depending on how it was created
147
149
  const cond = stmt.Condition?.StringEquals ?? {};
148
- const sourceArnKey = Object.keys(cond).find(k => k.toLowerCase() === 'aws:sourcearn') ?? 'AWS:SourceArn';
150
+ const sourceArnKey = Object.keys(cond).find((k) => k.toLowerCase() === "aws:sourcearn") ??
151
+ "AWS:SourceArn";
149
152
  if (!stmt.Condition)
150
153
  stmt.Condition = { StringEquals: {} };
151
154
  if (!stmt.Condition.StringEquals)
152
155
  stmt.Condition.StringEquals = {};
153
156
  const existing = stmt.Condition.StringEquals[sourceArnKey];
154
- const existingArns = Array.isArray(existing) ? existing : existing ? [existing] : [];
157
+ const existingArns = Array.isArray(existing)
158
+ ? existing
159
+ : existing
160
+ ? [existing]
161
+ : [];
155
162
  const merged = [...new Set([...existingArns, ...newArns])];
156
163
  stmt.Condition.StringEquals[sourceArnKey] = merged;
157
164
  await s3.send(new PutBucketPolicyCommand({
158
165
  Bucket: this.bucketName,
159
166
  Policy: JSON.stringify(policy),
160
167
  }));
161
- console.log(` āœ… Updated bucket policy — ${merged.length} distribution ARN(s) allowed`);
168
+ console.log(` āœ… Updated bucket policy - ${merged.length} distribution ARN(s) allowed`);
162
169
  for (const arn of newArns)
163
170
  console.log(` └─ ${arn}`);
164
171
  }