puls-dev 0.1.7 → 0.1.8

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 +1 -1
  38. package/dist/providers/firebase/hosting.js +92 -52
  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,8 +1,8 @@
1
- import { GetApisCommand, CreateApiCommand, GetIntegrationsCommand, CreateIntegrationCommand, GetRoutesCommand, CreateRouteCommand, DeleteApiCommand, } from '@aws-sdk/client-apigatewayv2';
2
- import { AddPermissionCommand } from '@aws-sdk/client-lambda';
3
- import { BaseBuilder } from '../../core/resource.js';
4
- import { getAPIGWClient, getLambdaClient } from './api.js';
5
- import { Config } from '../../core/config.js';
1
+ import { GetApisCommand, CreateApiCommand, GetIntegrationsCommand, CreateIntegrationCommand, GetRoutesCommand, CreateRouteCommand, DeleteApiCommand, } from "@aws-sdk/client-apigatewayv2";
2
+ import { AddPermissionCommand } from "@aws-sdk/client-lambda";
3
+ import { BaseBuilder } from "../../core/resource.js";
4
+ import { getAPIGWClient, getLambdaClient } from "./api.js";
5
+ import { Config } from "../../core/config.js";
6
6
  export class APIGatewayBuilder extends BaseBuilder {
7
7
  _routes = [];
8
8
  resolvedId = null;
@@ -15,7 +15,7 @@ export class APIGatewayBuilder extends BaseBuilder {
15
15
  try {
16
16
  const gw = getAPIGWClient();
17
17
  const list = await gw.send(new GetApisCommand({}));
18
- const match = list.Items?.find(a => a.Name === name);
18
+ const match = list.Items?.find((a) => a.Name === name);
19
19
  if (match) {
20
20
  this.resolvedId = match.ApiId;
21
21
  this.resolvedEndpoint = match.ApiEndpoint ?? null;
@@ -23,30 +23,30 @@ export class APIGatewayBuilder extends BaseBuilder {
23
23
  return match ?? null;
24
24
  }
25
25
  catch (e) {
26
- if (e.name === 'CredentialsProviderError')
26
+ if (e.name === "CredentialsProviderError")
27
27
  return null;
28
28
  throw e;
29
29
  }
30
30
  }
31
31
  route(methodPath, fn) {
32
- const spaceIdx = methodPath.indexOf(' ');
32
+ const spaceIdx = methodPath.indexOf(" ");
33
33
  const method = methodPath.slice(0, spaceIdx).toUpperCase();
34
34
  const path = methodPath.slice(spaceIdx + 1);
35
35
  this._routes.push({ method, path, fn });
36
36
  return this;
37
37
  }
38
- // Single Lambda proxy forwards all traffic to one function
38
+ // Single Lambda proxy - forwards all traffic to one function
39
39
  proxy(fn) {
40
- return this.route('ANY /{proxy+}', fn);
40
+ return this.route("ANY /{proxy+}", fn);
41
41
  }
42
42
  async deploy() {
43
43
  const dryRun = this.isDryRunActive();
44
44
  const existing = await this.discoveryPromise;
45
- const region = Config.get().providers.aws?.region ?? 'us-east-1';
45
+ const region = Config.get().providers.aws?.region ?? "us-east-1";
46
46
  const gw = getAPIGWClient();
47
47
  console.log(`\n⚡ Finalizing API Gateway "${this.name}"...`);
48
48
  if (dryRun) {
49
- console.log(` 📝 [PLAN] ${existing ? 'Update' : 'Create'} HTTP API "${this.name}"`);
49
+ console.log(` 📝 [PLAN] ${existing ? "Update" : "Create"} HTTP API "${this.name}"`);
50
50
  for (const r of this._routes) {
51
51
  console.log(` └─ ${r.method} ${r.path} → ${r.fn.name}`);
52
52
  }
@@ -57,24 +57,24 @@ export class APIGatewayBuilder extends BaseBuilder {
57
57
  if (!existing) {
58
58
  const created = await gw.send(new CreateApiCommand({
59
59
  Name: this.name,
60
- ProtocolType: 'HTTP',
61
- // Auto-deploy on $default stage no manual deployment step needed
62
- RouteSelectionExpression: '$request.method $request.path',
60
+ ProtocolType: "HTTP",
61
+ // Auto-deploy on $default stage - no manual deployment step needed
62
+ RouteSelectionExpression: "$request.method $request.path",
63
63
  }));
64
64
  this.resolvedId = created.ApiId;
65
65
  this.resolvedEndpoint = created.ApiEndpoint;
66
66
  console.log(`🚀 Created HTTP API "${this.name}" (id=${this.resolvedId})`);
67
67
  // $default stage with auto-deploy
68
- await gw.send(new (await import('@aws-sdk/client-apigatewayv2')).CreateStageCommand({
68
+ await gw.send(new (await import("@aws-sdk/client-apigatewayv2")).CreateStageCommand({
69
69
  ApiId: this.resolvedId,
70
- StageName: '$default',
70
+ StageName: "$default",
71
71
  AutoDeploy: true,
72
72
  }));
73
73
  }
74
74
  else {
75
75
  console.log(` ✅ HTTP API "${this.name}" exists (id=${this.resolvedId})`);
76
76
  }
77
- // Reconcile routes only create what's missing
77
+ // Reconcile routes - only create what's missing
78
78
  const existingIntegrations = await gw.send(new GetIntegrationsCommand({ ApiId: this.resolvedId }));
79
79
  const existingRoutes = await gw.send(new GetRoutesCommand({ ApiId: this.resolvedId }));
80
80
  const integrationByFnArn = new Map();
@@ -82,20 +82,20 @@ export class APIGatewayBuilder extends BaseBuilder {
82
82
  if (i.IntegrationUri)
83
83
  integrationByFnArn.set(i.IntegrationUri, i.IntegrationId);
84
84
  }
85
- const existingRouteKeys = new Set((existingRoutes.Items ?? []).map(r => r.RouteKey));
85
+ const existingRouteKeys = new Set((existingRoutes.Items ?? []).map((r) => r.RouteKey));
86
86
  for (const r of this._routes) {
87
87
  const fnArn = r.fn.resolvedArn;
88
88
  if (!fnArn)
89
- throw new Error(`[APIGateway:${this.name}] Lambda "${r.fn.name}" has no resolvedArn deploy it before the API.`);
89
+ throw new Error(`[APIGateway:${this.name}] Lambda "${r.fn.name}" has no resolvedArn - deploy it before the API.`);
90
90
  const integrationUri = `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${fnArn}/invocations`;
91
91
  // Reuse existing integration for this Lambda or create a new one
92
92
  let integrationId = integrationByFnArn.get(integrationUri);
93
93
  if (!integrationId) {
94
94
  const integ = await gw.send(new CreateIntegrationCommand({
95
95
  ApiId: this.resolvedId,
96
- IntegrationType: 'AWS_PROXY',
96
+ IntegrationType: "AWS_PROXY",
97
97
  IntegrationUri: integrationUri,
98
- PayloadFormatVersion: '2.0',
98
+ PayloadFormatVersion: "2.0",
99
99
  }));
100
100
  integrationId = integ.IntegrationId;
101
101
  integrationByFnArn.set(integrationUri, integrationId);
@@ -118,23 +118,27 @@ export class APIGatewayBuilder extends BaseBuilder {
118
118
  }
119
119
  console.log(` 🌐 Endpoint: ${this.resolvedEndpoint}`);
120
120
  await this.deploySidecars();
121
- return { name: this.name, endpoint: this.resolvedEndpoint, id: this.resolvedId };
121
+ return {
122
+ name: this.name,
123
+ endpoint: this.resolvedEndpoint,
124
+ id: this.resolvedId,
125
+ };
122
126
  }
123
127
  async grantInvokePermission(fnArn, region) {
124
- const accountId = fnArn.split(':')[4];
128
+ const accountId = fnArn.split(":")[4];
125
129
  const statementId = `puls-apigw-${this.resolvedId}`;
126
130
  try {
127
131
  await getLambdaClient().send(new AddPermissionCommand({
128
132
  FunctionName: fnArn,
129
133
  StatementId: statementId,
130
- Action: 'lambda:InvokeFunction',
131
- Principal: 'apigateway.amazonaws.com',
134
+ Action: "lambda:InvokeFunction",
135
+ Principal: "apigateway.amazonaws.com",
132
136
  SourceArn: `arn:aws:execute-api:${region}:${accountId}:${this.resolvedId}/*/*`,
133
137
  }));
134
138
  }
135
139
  catch (e) {
136
- // Permission already exists idempotent
137
- if (e.name !== 'ResourceConflictException')
140
+ // Permission already exists - idempotent
141
+ if (e.name !== "ResourceConflictException")
138
142
  throw e;
139
143
  }
140
144
  }
@@ -143,7 +147,7 @@ export class APIGatewayBuilder extends BaseBuilder {
143
147
  const existing = await this.discoveryPromise;
144
148
  console.log(`\n🗑️ Destroying API Gateway "${this.name}"...`);
145
149
  if (!existing) {
146
- console.log(` ✅ API "${this.name}" does not exist nothing to do`);
150
+ console.log(` ✅ API "${this.name}" does not exist - nothing to do`);
147
151
  return { destroyed: this.name };
148
152
  }
149
153
  if (dryRun) {
@@ -1,6 +1,6 @@
1
- import { BaseBuilder } from '../../core/resource.js';
2
- import { S3BucketBuilder } from './s3.js';
3
- import { Route53Builder } from './route53.js';
1
+ import { BaseBuilder } from "../../core/resource.js";
2
+ import { S3BucketBuilder } from "./s3.js";
3
+ import { Route53Builder } from "./route53.js";
4
4
  export declare class CloudFrontBuilder extends BaseBuilder {
5
5
  resolvedArn: string | null;
6
6
  resolvedId: string | null;
@@ -1,7 +1,7 @@
1
- import { ListDistributionsCommand, GetDistributionCommand, CreateDistributionCommand, GetDistributionConfigCommand, CreateInvalidationCommand, } from '@aws-sdk/client-cloudfront';
2
- import { BaseBuilder } from '../../core/resource.js';
3
- import { S3BucketBuilder } from './s3.js';
4
- import { getCFClient } from './api.js';
1
+ import { ListDistributionsCommand, GetDistributionCommand, CreateDistributionCommand, GetDistributionConfigCommand, CreateInvalidationCommand, } from "@aws-sdk/client-cloudfront";
2
+ import { BaseBuilder } from "../../core/resource.js";
3
+ import { S3BucketBuilder } from "./s3.js";
4
+ import { getCFClient } from "./api.js";
5
5
  export class CloudFrontBuilder extends BaseBuilder {
6
6
  resolvedArn = null;
7
7
  resolvedId = null;
@@ -22,7 +22,7 @@ export class CloudFrontBuilder extends BaseBuilder {
22
22
  const cf = getCFClient();
23
23
  const list = await cf.send(new ListDistributionsCommand({}));
24
24
  const items = list.DistributionList?.Items ?? [];
25
- const match = items.find(d => d.Comment === name || d.Id === name);
25
+ const match = items.find((d) => d.Comment === name || d.Id === name);
26
26
  if (match) {
27
27
  this.resolvedId = match.Id;
28
28
  this.resolvedArn = match.ARN;
@@ -30,7 +30,7 @@ export class CloudFrontBuilder extends BaseBuilder {
30
30
  return match ?? null;
31
31
  }
32
32
  catch (e) {
33
- if (e.name === 'CredentialsProviderError')
33
+ if (e.name === "CredentialsProviderError")
34
34
  return null;
35
35
  throw e;
36
36
  }
@@ -50,7 +50,7 @@ export class CloudFrontBuilder extends BaseBuilder {
50
50
  forDomain(zone, prefixes) {
51
51
  this._zone = zone;
52
52
  this._prefixes = prefixes;
53
- this._aliases = prefixes.map(p => `${p}.${zone.zoneName}`);
53
+ this._aliases = prefixes.map((p) => `${p}.${zone.zoneName}`);
54
54
  const cert = zone.cert();
55
55
  if (cert)
56
56
  this._certRef = cert;
@@ -74,20 +74,23 @@ export class CloudFrontBuilder extends BaseBuilder {
74
74
  this.resolvedArn = existing.ARN;
75
75
  console.log(` ✅ Distribution "${this.name}" exists (id=${this.resolvedId})`);
76
76
  if (this._aliases.length)
77
- console.log(` ✅ Aliases: [${this._aliases.join(', ')}]`);
77
+ console.log(` ✅ Aliases: [${this._aliases.join(", ")}]`);
78
78
  if (this._invalidatePaths?.length) {
79
79
  if (dryRun) {
80
- console.log(` 📝 [PLAN] Invalidate: ${this._invalidatePaths.join(', ')}`);
80
+ console.log(` 📝 [PLAN] Invalidate: ${this._invalidatePaths.join(", ")}`);
81
81
  }
82
82
  else {
83
83
  await cf.send(new CreateInvalidationCommand({
84
84
  DistributionId: this.resolvedId,
85
85
  InvalidationBatch: {
86
- Paths: { Quantity: this._invalidatePaths.length, Items: this._invalidatePaths },
86
+ Paths: {
87
+ Quantity: this._invalidatePaths.length,
88
+ Items: this._invalidatePaths,
89
+ },
87
90
  CallerReference: `puls-${Date.now()}`,
88
91
  },
89
92
  }));
90
- console.log(` ✅ Invalidated: ${this._invalidatePaths.join(', ')}`);
93
+ console.log(` ✅ Invalidated: ${this._invalidatePaths.join(", ")}`);
91
94
  }
92
95
  }
93
96
  return { id: this.resolvedId, arn: this.resolvedArn, name: this.name };
@@ -97,7 +100,7 @@ export class CloudFrontBuilder extends BaseBuilder {
97
100
  if (this._referenceId)
98
101
  console.log(` └─ Clone config from: ${this._referenceId}`);
99
102
  if (this._aliases.length)
100
- console.log(` └─ Aliases: [${this._aliases.join(', ')}]`);
103
+ console.log(` └─ Aliases: [${this._aliases.join(", ")}]`);
101
104
  const dryRunCert = this._certRef ?? this._zone?.cert();
102
105
  if (dryRunCert?.resolvedArn)
103
106
  console.log(` └─ Certificate: ${dryRunCert.resolvedArn}`);
@@ -110,7 +113,7 @@ export class CloudFrontBuilder extends BaseBuilder {
110
113
  }
111
114
  }
112
115
  this.resolvedArn = `arn:aws:cloudfront::DRYRUN:distribution/PENDING`;
113
- this.resolvedId = 'PENDING';
116
+ this.resolvedId = "PENDING";
114
117
  return { id: this.resolvedId, arn: this.resolvedArn, name: this.name };
115
118
  }
116
119
  let distroConfig;
@@ -127,21 +130,27 @@ export class CloudFrontBuilder extends BaseBuilder {
127
130
  distroConfig.Comment = this.name;
128
131
  // Override aliases + cert if provided
129
132
  if (this._aliases.length) {
130
- distroConfig.Aliases = { Quantity: this._aliases.length, Items: this._aliases };
133
+ distroConfig.Aliases = {
134
+ Quantity: this._aliases.length,
135
+ Items: this._aliases,
136
+ };
131
137
  }
132
- // _certRef is set at construction time, but cert sidecar is added lazily in zone.deploy()
138
+ // _certRef is set at construction time, but cert sidecar is added lazily in zone.deploy() -
133
139
  // fall back to zone.cert() which is populated by the time CloudFront.deploy() runs
134
140
  const certRef = this._certRef ?? this._zone?.cert();
135
141
  if (certRef?.resolvedArn) {
136
142
  distroConfig.ViewerCertificate = {
137
143
  ACMCertificateArn: certRef.resolvedArn,
138
- SSLSupportMethod: 'sni-only',
139
- MinimumProtocolVersion: 'TLSv1.2_2021',
144
+ SSLSupportMethod: "sni-only",
145
+ MinimumProtocolVersion: "TLSv1.2_2021",
140
146
  };
141
147
  }
142
- // Remove CallerReference from cloned config AWS will reject it
148
+ // Remove CallerReference from cloned config - AWS will reject it
143
149
  delete distroConfig.CallerReference;
144
- const distroInput = { ...distroConfig, CallerReference: `puls-${this.name}-${Date.now()}` };
150
+ const distroInput = {
151
+ ...distroConfig,
152
+ CallerReference: `puls-${this.name}-${Date.now()}`,
153
+ };
145
154
  // ACM ISSUED → CloudFront cert index replication can take up to ~5 min
146
155
  const MAX_ATTEMPTS = 10;
147
156
  let result;
@@ -151,9 +160,9 @@ export class CloudFrontBuilder extends BaseBuilder {
151
160
  break;
152
161
  }
153
162
  catch (e) {
154
- if (e.name === 'InvalidViewerCertificate' && attempt < MAX_ATTEMPTS) {
155
- console.log(` ⏳ Cert not yet visible to CloudFront retrying in 30s (${attempt}/${MAX_ATTEMPTS - 1})...`);
156
- await new Promise(r => setTimeout(r, 30_000));
163
+ if (e.name === "InvalidViewerCertificate" && attempt < MAX_ATTEMPTS) {
164
+ console.log(` ⏳ Cert not yet visible to CloudFront - retrying in 30s (${attempt}/${MAX_ATTEMPTS - 1})...`);
165
+ await new Promise((r) => setTimeout(r, 30_000));
157
166
  }
158
167
  else
159
168
  throw e;
@@ -164,12 +173,12 @@ export class CloudFrontBuilder extends BaseBuilder {
164
173
  console.log(`🚀 Created distribution "${this.name}" (id=${this.resolvedId})`);
165
174
  await this.waitFor(`distribution "${this.name}" to finish deploying`, async () => {
166
175
  const d = await cf.send(new GetDistributionCommand({ Id: this.resolvedId }));
167
- return d.Distribution?.Status === 'Deployed';
176
+ return d.Distribution?.Status === "Deployed";
168
177
  }, { intervalMs: 30_000, timeoutMs: 1_200_000 });
169
178
  const cfDomain = result.Distribution.DomainName;
170
179
  console.log(` 🌐 Domain: ${cfDomain}`);
171
180
  if (this._zone && this._prefixes.length) {
172
- await this._zone.upsertCnames(this._prefixes.map(p => ({ name: p, value: cfDomain })));
181
+ await this._zone.upsertCnames(this._prefixes.map((p) => ({ name: p, value: cfDomain })));
173
182
  }
174
183
  return { id: this.resolvedId, arn: this.resolvedArn, name: this.name };
175
184
  }
@@ -177,29 +186,35 @@ export class CloudFrontBuilder extends BaseBuilder {
177
186
  const originId = `puls-origin-${this.name}`;
178
187
  const originDomain = this._origin instanceof S3BucketBuilder
179
188
  ? `${this._origin.bucketName}.s3.amazonaws.com`
180
- : this._origin ?? 'example.com';
189
+ : (this._origin ?? "example.com");
181
190
  return {
182
191
  Comment: this.name,
183
192
  Enabled: true,
184
- HttpVersion: 'http2',
193
+ HttpVersion: "http2",
185
194
  Origins: {
186
195
  Quantity: 1,
187
- Items: [{
196
+ Items: [
197
+ {
188
198
  Id: originId,
189
199
  DomainName: originDomain,
190
- S3OriginConfig: { OriginAccessIdentity: '' },
191
- }],
200
+ S3OriginConfig: { OriginAccessIdentity: "" },
201
+ },
202
+ ],
192
203
  },
193
204
  DefaultCacheBehavior: {
194
205
  TargetOriginId: originId,
195
- ViewerProtocolPolicy: 'redirect-to-https',
196
- CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', // Managed-CachingOptimized
197
- AllowedMethods: { Quantity: 2, Items: ['GET', 'HEAD'], CachedMethods: { Quantity: 2, Items: ['GET', 'HEAD'] } },
206
+ ViewerProtocolPolicy: "redirect-to-https",
207
+ CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6", // Managed-CachingOptimized
208
+ AllowedMethods: {
209
+ Quantity: 2,
210
+ Items: ["GET", "HEAD"],
211
+ CachedMethods: { Quantity: 2, Items: ["GET", "HEAD"] },
212
+ },
198
213
  Compress: true,
199
- ForwardedValues: { QueryString: false, Cookies: { Forward: 'none' } },
214
+ ForwardedValues: { QueryString: false, Cookies: { Forward: "none" } },
200
215
  MinTTL: 0,
201
216
  },
202
- PriceClass: 'PriceClass_All',
217
+ PriceClass: "PriceClass_All",
203
218
  };
204
219
  }
205
220
  }
@@ -1,5 +1,5 @@
1
- import { BaseBuilder } from '../../core/resource.js';
2
- import { SecretsBuilder } from './secrets.js';
1
+ import { BaseBuilder } from "../../core/resource.js";
2
+ import { SecretsBuilder } from "./secrets.js";
3
3
  export declare class FargateBuilder extends BaseBuilder {
4
4
  private _image?;
5
5
  private _cpu;