puls-dev 0.1.9 → 0.2.1

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 (69) hide show
  1. package/README.md +8 -8
  2. package/dist/index.d.ts +0 -7
  3. package/dist/index.js +0 -7
  4. package/dist/providers/aws/api.d.ts +4 -0
  5. package/dist/providers/aws/api.js +4 -0
  6. package/dist/providers/aws/cloudwatch.d.ts +44 -0
  7. package/dist/providers/aws/cloudwatch.js +205 -0
  8. package/dist/providers/aws/cloudwatch.test.d.ts +1 -0
  9. package/dist/providers/aws/cloudwatch.test.js +224 -0
  10. package/dist/providers/aws/fargate.d.ts +2 -0
  11. package/dist/providers/aws/fargate.js +6 -0
  12. package/dist/providers/aws/iam.d.ts +52 -0
  13. package/dist/providers/aws/iam.js +307 -0
  14. package/dist/providers/aws/iam.test.d.ts +1 -0
  15. package/dist/providers/aws/iam.test.js +367 -0
  16. package/dist/providers/aws/index.d.ts +8 -0
  17. package/dist/providers/aws/index.js +8 -0
  18. package/dist/providers/aws/lambda.d.ts +3 -1
  19. package/dist/providers/aws/lambda.js +17 -8
  20. package/dist/providers/aws/lambda.test.d.ts +1 -0
  21. package/dist/providers/aws/lambda.test.js +189 -0
  22. package/dist/providers/aws/rds.d.ts +1 -0
  23. package/dist/providers/aws/rds.js +4 -1
  24. package/dist/providers/aws/route53.d.ts +1 -1
  25. package/dist/providers/aws/route53.js +20 -12
  26. package/dist/providers/aws/route53.test.d.ts +1 -0
  27. package/dist/providers/aws/route53.test.js +229 -0
  28. package/dist/providers/aws/s3.d.ts +3 -0
  29. package/dist/providers/aws/s3.js +65 -3
  30. package/dist/providers/aws/s3.test.d.ts +1 -0
  31. package/dist/providers/aws/s3.test.js +172 -0
  32. package/dist/providers/aws/sns.d.ts +22 -0
  33. package/dist/providers/aws/sns.js +146 -0
  34. package/dist/providers/aws/sns.test.d.ts +1 -0
  35. package/dist/providers/aws/sns.test.js +162 -0
  36. package/dist/providers/do/api.js +5 -1
  37. package/dist/providers/do/certificate.test.d.ts +1 -0
  38. package/dist/providers/do/certificate.test.js +133 -0
  39. package/dist/providers/do/domain.d.ts +12 -1
  40. package/dist/providers/do/domain.js +129 -13
  41. package/dist/providers/do/domain.test.d.ts +1 -0
  42. package/dist/providers/do/domain.test.js +200 -0
  43. package/dist/providers/do/droplet.js +2 -2
  44. package/dist/providers/do/droplet.test.d.ts +1 -0
  45. package/dist/providers/do/droplet.test.js +265 -0
  46. package/dist/providers/do/firewall.test.d.ts +1 -0
  47. package/dist/providers/do/firewall.test.js +176 -0
  48. package/dist/providers/do/index.d.ts +1 -0
  49. package/dist/providers/do/index.js +1 -0
  50. package/dist/providers/do/load_balancer.d.ts +39 -5
  51. package/dist/providers/do/load_balancer.js +272 -30
  52. package/dist/providers/do/load_balancer.test.d.ts +1 -0
  53. package/dist/providers/do/load_balancer.test.js +269 -0
  54. package/dist/providers/firebase/api.js +2 -2
  55. package/dist/providers/firebase/functions.d.ts +1 -0
  56. package/dist/providers/firebase/functions.js +24 -10
  57. package/dist/providers/firebase/functions.test.d.ts +1 -0
  58. package/dist/providers/firebase/functions.test.js +297 -0
  59. package/dist/providers/firebase/hosting.js +5 -5
  60. package/dist/providers/firebase/hosting.test.d.ts +1 -0
  61. package/dist/providers/firebase/hosting.test.js +181 -0
  62. package/dist/providers/proxmox/index.d.ts +1 -0
  63. package/dist/providers/proxmox/index.js +1 -0
  64. package/dist/providers/proxmox/vm.d.ts +2 -1
  65. package/dist/providers/proxmox/vm.js +39 -53
  66. package/dist/providers/proxmox/vm.test.d.ts +1 -0
  67. package/dist/providers/proxmox/vm.test.js +155 -0
  68. package/dist/types/aws.d.ts +11 -0
  69. package/package.json +105 -6
@@ -1,7 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { spawn } from "node:child_process";
4
- import { dirname } from "node:path";
5
4
  import { createConnection } from "node:net";
6
5
  import { BaseBuilder } from "../../core/resource.js";
7
6
  import { Config } from "../../core/config.js";
@@ -25,6 +24,7 @@ export class VMBuilder extends BaseBuilder {
25
24
  _vlan;
26
25
  _ip;
27
26
  _sshKeys;
27
+ _machine = "q35";
28
28
  constructor(name) {
29
29
  super(name);
30
30
  this.discoveryPromise = this.discoverVm(name);
@@ -81,6 +81,10 @@ export class VMBuilder extends BaseBuilder {
81
81
  this._sshKeys = Array.isArray(keys) ? [...keys] : keys;
82
82
  return this;
83
83
  }
84
+ machine(type) {
85
+ this._machine = type;
86
+ return this;
87
+ }
84
88
  async deploy() {
85
89
  const dryRun = this.isDryRunActive();
86
90
  const existing = await this.discoveryPromise;
@@ -103,7 +107,7 @@ export class VMBuilder extends BaseBuilder {
103
107
  console.log(` 📝 [PLAN] Create VM "${this.name}"`);
104
108
  if (this._image)
105
109
  console.log(` └─ Image: ${this._image}`);
106
- console.log(` └─ Cores: ${this._cores} Memory: ${this._memory} MB`);
110
+ console.log(` └─ Cores: ${this._cores} Memory: ${this._memory} MB Machine: ${this._machine}`);
107
111
  if (this._vlan)
108
112
  console.log(` └─ VLAN: ${this._vlan}`);
109
113
  if (this._provision) {
@@ -133,8 +137,35 @@ export class VMBuilder extends BaseBuilder {
133
137
  ? `Check that VMID ${this._image} exists and is marked as a template.`
134
138
  : `Create a template whose name contains "${this._image}".`));
135
139
  }
136
- // Resolve target node: explicit → configured nodes list → template's node → API discovery
140
+ // Resolve target node: explicit → cluster-aware (online & max free RAM) → configured nodes list → template's node → API discovery
137
141
  let node = this._node;
142
+ if (!node) {
143
+ try {
144
+ const nodesList = await pm.get("/nodes");
145
+ const configuredNodes = Config.get().providers.proxmox?.nodes;
146
+ const onlineNodes = (nodesList ?? []).filter((n) => {
147
+ if (n.status !== "online")
148
+ return false;
149
+ if (configuredNodes && configuredNodes.length > 0) {
150
+ return configuredNodes.includes(n.node);
151
+ }
152
+ return true;
153
+ });
154
+ if (onlineNodes.length > 0) {
155
+ // Sort descending by free memory (maxmem - mem)
156
+ onlineNodes.sort((a, b) => {
157
+ const freeA = (a.maxmem ?? 0) - (a.mem ?? 0);
158
+ const freeB = (b.maxmem ?? 0) - (b.mem ?? 0);
159
+ return freeB - freeA;
160
+ });
161
+ node = onlineNodes[0].node;
162
+ console.log(` 🧠 Cluster-aware node selection: picked "${node}" with the most free RAM (${Math.round((((onlineNodes[0].maxmem ?? 0) - (onlineNodes[0].mem ?? 0)) / 1024 / 1024 / 1024) * 10) / 10} GB free)`);
163
+ }
164
+ }
165
+ catch (err) {
166
+ // Fallback silently to configured nodes list or discovery
167
+ }
168
+ }
138
169
  if (!node) {
139
170
  const configuredNodes = Config.get().providers.proxmox?.nodes;
140
171
  node = configuredNodes?.[0] ?? template?.node;
@@ -192,7 +223,7 @@ export class VMBuilder extends BaseBuilder {
192
223
  const net0 = `virtio,bridge=vmbr1${this._vlan ? `,tag=${this._vlan}` : ""}`;
193
224
  const configPatch = {
194
225
  onboot: 1,
195
- machine: "q35",
226
+ machine: this._machine,
196
227
  cores: this._cores,
197
228
  memory: this._memory,
198
229
  net0,
@@ -380,59 +411,14 @@ export class VMBuilder extends BaseBuilder {
380
411
  }
381
412
  runProvisioner(ip, script) {
382
413
  const ext = script.split(".").pop()?.toLowerCase();
383
- if (ext === "sh")
384
- return this.runShellScript(ip, script);
414
+ if (ext === "sh") {
415
+ throw new Error(`Shell script provisioning (.sh) is no longer supported. ` +
416
+ `Please migrate "${script}" to an Ansible playbook (.yaml/.yml).`);
417
+ }
385
418
  if (ext === "pp")
386
419
  return this.runPuppet(ip, script);
387
420
  return this.runAnsible(ip, script); // .yml / .yaml
388
421
  }
389
- runShellScript(ip, script) {
390
- console.log(` 🔧 Running shell script: ${script} → ${ip}`);
391
- const keyPath = this.sshKeyPath();
392
- const scriptDir = dirname(script); // e.g. 'config'
393
- const sshArgs = [
394
- "-i",
395
- keyPath,
396
- "-o",
397
- "StrictHostKeyChecking=no",
398
- "-o",
399
- "ConnectTimeout=30",
400
- ];
401
- return new Promise((resolve, reject) => {
402
- // Copy the script's directory to the same path on the remote (e.g. config/ → /config/)
403
- console.log(` 📂 Copying ${scriptDir}/ → ${ip}:/${scriptDir}/`);
404
- const scp = spawn("scp", [
405
- "-i",
406
- keyPath,
407
- "-o",
408
- "StrictHostKeyChecking=no",
409
- "-r",
410
- scriptDir,
411
- `root@${ip}:/`,
412
- ], { stdio: "inherit" });
413
- scp.on("error", (err) => reject(new Error(`scp failed: ${err.message}`)));
414
- scp.on("close", (scpCode) => {
415
- if (scpCode !== 0) {
416
- reject(new Error(`scp exited with code ${scpCode}`));
417
- return;
418
- }
419
- const proc = spawn("ssh", [...sshArgs, `root@${ip}`, "bash -s"], {
420
- stdio: ["pipe", "inherit", "inherit"],
421
- });
422
- proc.stdin.write(readFileSync(script));
423
- proc.stdin.end();
424
- proc.on("close", (code) => {
425
- if (code === 0) {
426
- console.log(` ✅ Provisioning complete`);
427
- resolve();
428
- }
429
- else
430
- reject(new Error(`Shell script exited with code ${code}`));
431
- });
432
- proc.on("error", (err) => reject(new Error(`ssh failed: ${err.message}`)));
433
- });
434
- });
435
- }
436
422
  runPuppet(ip, manifest) {
437
423
  console.log(` 🔧 Applying Puppet manifest: ${manifest} → ${ip}`);
438
424
  const keyPath = this.sshKeyPath();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,155 @@
1
+ import { test, describe, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert";
3
+ import { ProxmoxApiClient } from "./api.js";
4
+ import { VMBuilder } from "./vm.js";
5
+ import { Config } from "../../core/config.js";
6
+ describe("Proxmox VMBuilder Unit Tests", () => {
7
+ let originalGet;
8
+ let originalPost;
9
+ let originalDelete;
10
+ let clientCalls = [];
11
+ let mockGetResponses = {};
12
+ let mockPostResponses = {};
13
+ beforeEach(() => {
14
+ Config.set({
15
+ dryRun: false,
16
+ providers: {
17
+ proxmox: {
18
+ url: "https://pve.example.com:8006",
19
+ user: "root@pam",
20
+ tokenName: "puls",
21
+ tokenSecret: "secret-key",
22
+ verifySsl: false,
23
+ dnsDomain: "nolimit.int",
24
+ },
25
+ },
26
+ });
27
+ clientCalls = [];
28
+ mockGetResponses = {};
29
+ mockPostResponses = {};
30
+ originalGet = ProxmoxApiClient.prototype.get;
31
+ originalPost = ProxmoxApiClient.prototype.post;
32
+ originalDelete = ProxmoxApiClient.prototype.delete;
33
+ ProxmoxApiClient.prototype.get = async function (path) {
34
+ clientCalls.push({ method: "GET", path });
35
+ if (mockGetResponses[path] !== undefined) {
36
+ const handler = mockGetResponses[path];
37
+ if (typeof handler === "function")
38
+ return handler();
39
+ return handler;
40
+ }
41
+ return [];
42
+ };
43
+ ProxmoxApiClient.prototype.post = async function (path, body) {
44
+ clientCalls.push({ method: "POST", path, body });
45
+ if (mockPostResponses[path] !== undefined) {
46
+ const handler = mockPostResponses[path];
47
+ if (typeof handler === "function")
48
+ return handler(body);
49
+ return handler;
50
+ }
51
+ if (path.includes("/clone")) {
52
+ return "UPID:pve1:00000000:00000000:00000000:qemuclone:101:root@pam:";
53
+ }
54
+ return {};
55
+ };
56
+ ProxmoxApiClient.prototype.delete = async function (path) {
57
+ clientCalls.push({ method: "DELETE", path });
58
+ };
59
+ });
60
+ afterEach(() => {
61
+ ProxmoxApiClient.prototype.get = originalGet;
62
+ ProxmoxApiClient.prototype.post = originalPost;
63
+ ProxmoxApiClient.prototype.delete = originalDelete;
64
+ });
65
+ test("gracefully handles discovery when VM does not exist", async () => {
66
+ mockGetResponses["/cluster/resources?type=vm"] = [];
67
+ const builder = new VMBuilder("my-vm");
68
+ const discoveryResult = await builder.discoveryPromise;
69
+ assert.strictEqual(discoveryResult, null);
70
+ assert.ok(clientCalls.some((c) => c.path === "/cluster/resources?type=vm"));
71
+ });
72
+ test("discovers existing VM successfully", async () => {
73
+ mockGetResponses["/cluster/resources?type=vm"] = [
74
+ { name: "my-vm", vmid: 200, node: "pve2", template: 0, status: "running" },
75
+ ];
76
+ const builder = new VMBuilder("my-vm");
77
+ const discoveryResult = await builder.discoveryPromise;
78
+ assert.ok(discoveryResult);
79
+ assert.strictEqual(discoveryResult.vmid, 200);
80
+ assert.strictEqual(discoveryResult.node, "pve2");
81
+ const deployResult = await builder.deploy();
82
+ assert.strictEqual(deployResult.vmid, 200);
83
+ assert.strictEqual(builder.resolvedNode, "pve2");
84
+ });
85
+ test("performs clean dry-run planning without making API writes", async () => {
86
+ Config.set({
87
+ dryRun: true,
88
+ providers: {
89
+ proxmox: {
90
+ url: "https://pve.example.com:8006",
91
+ user: "root@pam",
92
+ tokenName: "puls",
93
+ tokenSecret: "secret-key",
94
+ },
95
+ },
96
+ });
97
+ const builder = new VMBuilder("dryrun-vm")
98
+ .cores(4)
99
+ .memory(4096)
100
+ .machine("i440fx");
101
+ const deployResult = await builder.deploy();
102
+ assert.strictEqual(deployResult.vmid, "PENDING");
103
+ assert.ok(!clientCalls.some((c) => c.method === "POST"));
104
+ });
105
+ test("deploys new VM and performs cluster-aware node selection based on free RAM", async () => {
106
+ mockGetResponses["/cluster/resources?type=vm"] = [];
107
+ mockGetResponses["/cluster/nextid"] = 105;
108
+ // Simulate three nodes in the cluster with different RAM allocations and statuses
109
+ mockGetResponses["/nodes"] = [
110
+ { node: "pve-offline", status: "offline", maxmem: 64 * 1024 * 1024 * 1024, mem: 4 * 1024 * 1024 * 1024 }, // offline
111
+ { node: "pve-ram-low", status: "online", maxmem: 16 * 1024 * 1024 * 1024, mem: 14 * 1024 * 1024 * 1024 }, // 2GB free
112
+ { node: "pve-ram-high", status: "online", maxmem: 32 * 1024 * 1024 * 1024, mem: 12 * 1024 * 1024 * 1024 }, // 20GB free
113
+ ];
114
+ // Mock wait for task (normally waitForTask would poll the UPID, but in tests it mock-completes or we bypass it)
115
+ // In our test, since we don't have a template image configured, it creates a blank VM by POSTing to /nodes/{node}/qemu
116
+ const builder = new VMBuilder("my-new-vm")
117
+ .cores(2)
118
+ .memory(2048)
119
+ .ip("10.8.10.85")
120
+ .machine("i440fx");
121
+ const deployResult = await builder.deploy();
122
+ // Verify it resolved to the VMID and the most free RAM node ("pve-ram-high")
123
+ assert.strictEqual(deployResult.vmid, 105);
124
+ assert.strictEqual(builder.resolvedNode, "pve-ram-high");
125
+ // Verify the blank VM POST went to the correct node
126
+ const createCall = clientCalls.find((c) => c.method === "POST" && c.path.startsWith("/nodes/pve-ram-high/qemu"));
127
+ assert.ok(createCall);
128
+ assert.deepStrictEqual(createCall.body, {
129
+ vmid: 105,
130
+ name: "my-new-vm",
131
+ cores: 2,
132
+ memory: 2048,
133
+ net0: "virtio,bridge=vmbr1",
134
+ ostype: "l26",
135
+ });
136
+ // Verify config patch incorporates the custom machine override "i440fx"
137
+ const configCall = clientCalls.find((c) => c.method === "POST" && c.path === "/nodes/pve-ram-high/qemu/105/config");
138
+ assert.ok(configCall);
139
+ assert.strictEqual(configCall.body.machine, "i440fx");
140
+ assert.strictEqual(configCall.body.cores, 2);
141
+ assert.strictEqual(configCall.body.memory, 2048);
142
+ });
143
+ test("destroys an existing VM successfully", async () => {
144
+ mockGetResponses["/cluster/resources?type=vm"] = [
145
+ { name: "my-vm", vmid: 200, node: "pve1", template: 0 },
146
+ ];
147
+ const builder = new VMBuilder("my-vm");
148
+ await builder.discoveryPromise;
149
+ const destroyResult = await builder.destroy();
150
+ assert.deepStrictEqual(destroyResult, { destroyed: "my-vm" });
151
+ // In Proxmox, VM deletion is handled via BaseBuilder default or custom VMBuilder destroy.
152
+ // Let's verify we logged or called the delete path or returned safely.
153
+ assert.ok(destroyResult.destroyed);
154
+ });
155
+ });
@@ -53,3 +53,14 @@ export interface RegistrantContact {
53
53
  COUNTRY: string;
54
54
  }
55
55
  export declare const DOMAIN_REGISTER: RegistrantContact;
56
+ export interface IAMPolicyStatement {
57
+ Effect: "Allow" | "Deny";
58
+ Action: string | string[];
59
+ Resource?: string | string[];
60
+ Principal?: Record<string, string | string[]>;
61
+ Condition?: Record<string, any>;
62
+ }
63
+ export interface IAMPolicyDocument {
64
+ Version?: string;
65
+ Statement: IAMPolicyStatement[];
66
+ }
package/package.json CHANGED
@@ -1,11 +1,33 @@
1
1
  {
2
2
  "name": "puls-dev",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "Intent-driven infrastructure-as-code with eager discovery and no state files.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./aws": {
15
+ "types": "./dist/providers/aws/index.d.ts",
16
+ "default": "./dist/providers/aws/index.js"
17
+ },
18
+ "./do": {
19
+ "types": "./dist/providers/do/index.d.ts",
20
+ "default": "./dist/providers/do/index.js"
21
+ },
22
+ "./proxmox": {
23
+ "types": "./dist/providers/proxmox/index.d.ts",
24
+ "default": "./dist/providers/proxmox/index.js"
25
+ },
26
+ "./firebase": {
27
+ "types": "./dist/providers/firebase/index.d.ts",
28
+ "default": "./dist/providers/firebase/index.js"
29
+ }
30
+ },
9
31
  "files": [
10
32
  "dist",
11
33
  "README.md",
@@ -14,7 +36,7 @@
14
36
  "scripts": {
15
37
  "build": "tsc",
16
38
  "prepublishOnly": "npm run build",
17
- "test": "tsx --test src/**/*.test.ts"
39
+ "test": "tsx --test \"src/**/*.test.ts\""
18
40
  },
19
41
  "keywords": [
20
42
  "iac",
@@ -28,15 +50,38 @@
28
50
  "author": "Bia",
29
51
  "license": "ISC",
30
52
  "devDependencies": {
53
+ "@aws-sdk/client-acm": "^3.1053.0",
54
+ "@aws-sdk/client-apigatewayv2": "^3.1053.0",
55
+ "@aws-sdk/client-cloudfront": "^3.1053.0",
56
+ "@aws-sdk/client-cloudwatch": "^3.1053.0",
57
+ "@aws-sdk/client-cloudwatch-logs": "^3.1053.0",
58
+ "@aws-sdk/client-ec2": "^3.1053.0",
59
+ "@aws-sdk/client-ecs": "^3.1053.0",
60
+ "@aws-sdk/client-iam": "^3.1053.0",
61
+ "@aws-sdk/client-lambda": "^3.1053.0",
62
+ "@aws-sdk/client-rds": "^3.1053.0",
63
+ "@aws-sdk/client-route-53": "^3.1053.0",
64
+ "@aws-sdk/client-route-53-domains": "^3.1053.0",
65
+ "@aws-sdk/client-s3": "^3.1053.0",
66
+ "@aws-sdk/client-secrets-manager": "^3.1053.0",
67
+ "@aws-sdk/client-sns": "^3.1053.0",
68
+ "@aws-sdk/client-sqs": "^3.1053.0",
31
69
  "@types/node": "^25.6.2",
70
+ "google-auth-library": "^10.6.2",
32
71
  "ts-node": "^10.9.2",
33
72
  "tsx": "^4.21.0",
34
- "typescript": "^6.0.3"
73
+ "typescript": "^6.0.3",
74
+ "undici": "^8.3.0"
35
75
  },
36
76
  "dependencies": {
77
+ "dotenv": "^17.4.2",
78
+ "reflect-metadata": "^0.2.2"
79
+ },
80
+ "peerDependencies": {
37
81
  "@aws-sdk/client-acm": "^3.1040.0",
38
82
  "@aws-sdk/client-apigatewayv2": "^3.1044.0",
39
83
  "@aws-sdk/client-cloudfront": "^3.1040.0",
84
+ "@aws-sdk/client-cloudwatch": "^3.1045.0",
40
85
  "@aws-sdk/client-cloudwatch-logs": "^3.1045.0",
41
86
  "@aws-sdk/client-ec2": "^3.1045.0",
42
87
  "@aws-sdk/client-ecs": "^3.1045.0",
@@ -47,11 +92,65 @@
47
92
  "@aws-sdk/client-route-53-domains": "^3.1041.0",
48
93
  "@aws-sdk/client-s3": "^3.1040.0",
49
94
  "@aws-sdk/client-secrets-manager": "^3.1045.0",
95
+ "@aws-sdk/client-sns": "^3.1045.0",
50
96
  "@aws-sdk/client-sqs": "^3.1045.0",
51
- "dotenv": "^17.4.2",
52
97
  "google-auth-library": "^10.6.2",
53
- "mkdocs": "^0.0.1",
54
- "reflect-metadata": "^0.2.2",
55
98
  "undici": "^8.2.0"
99
+ },
100
+ "peerDependenciesMeta": {
101
+ "@aws-sdk/client-acm": {
102
+ "optional": true
103
+ },
104
+ "@aws-sdk/client-apigatewayv2": {
105
+ "optional": true
106
+ },
107
+ "@aws-sdk/client-cloudfront": {
108
+ "optional": true
109
+ },
110
+ "@aws-sdk/client-cloudwatch": {
111
+ "optional": true
112
+ },
113
+ "@aws-sdk/client-cloudwatch-logs": {
114
+ "optional": true
115
+ },
116
+ "@aws-sdk/client-ec2": {
117
+ "optional": true
118
+ },
119
+ "@aws-sdk/client-ecs": {
120
+ "optional": true
121
+ },
122
+ "@aws-sdk/client-iam": {
123
+ "optional": true
124
+ },
125
+ "@aws-sdk/client-lambda": {
126
+ "optional": true
127
+ },
128
+ "@aws-sdk/client-rds": {
129
+ "optional": true
130
+ },
131
+ "@aws-sdk/client-route-53": {
132
+ "optional": true
133
+ },
134
+ "@aws-sdk/client-route-53-domains": {
135
+ "optional": true
136
+ },
137
+ "@aws-sdk/client-s3": {
138
+ "optional": true
139
+ },
140
+ "@aws-sdk/client-secrets-manager": {
141
+ "optional": true
142
+ },
143
+ "@aws-sdk/client-sns": {
144
+ "optional": true
145
+ },
146
+ "@aws-sdk/client-sqs": {
147
+ "optional": true
148
+ },
149
+ "google-auth-library": {
150
+ "optional": true
151
+ },
152
+ "undici": {
153
+ "optional": true
154
+ }
56
155
  }
57
156
  }