puls-dev 0.2.8 → 0.3.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.
Files changed (66) hide show
  1. package/dist/core/checker.js +71 -0
  2. package/dist/core/config.d.ts +5 -0
  3. package/dist/core/config.js +12 -1
  4. package/dist/core/context.d.ts +14 -0
  5. package/dist/core/context.js +2 -0
  6. package/dist/core/decorators.d.ts +2 -0
  7. package/dist/core/decorators.js +8 -14
  8. package/dist/core/group.test.d.ts +1 -0
  9. package/dist/core/group.test.js +94 -0
  10. package/dist/core/parallel.test.d.ts +1 -0
  11. package/dist/core/parallel.test.js +215 -0
  12. package/dist/core/production.test.d.ts +1 -0
  13. package/dist/core/production.test.js +189 -0
  14. package/dist/core/provisioner.js +29 -11
  15. package/dist/core/resource.d.ts +8 -0
  16. package/dist/core/resource.js +45 -0
  17. package/dist/core/retry.d.ts +9 -0
  18. package/dist/core/retry.js +28 -0
  19. package/dist/core/retry.test.d.ts +1 -0
  20. package/dist/core/retry.test.js +66 -0
  21. package/dist/core/secret.d.ts +2 -1
  22. package/dist/core/secret.js +12 -2
  23. package/dist/core/stack.js +381 -75
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/providers/aws/api.js +97 -17
  27. package/dist/providers/aws/ec2.d.ts +3 -0
  28. package/dist/providers/aws/ec2.js +37 -3
  29. package/dist/providers/aws/ec2.test.js +5 -3
  30. package/dist/providers/aws/index.d.ts +2 -0
  31. package/dist/providers/aws/index.js +2 -0
  32. package/dist/providers/aws/secrets.js +20 -3
  33. package/dist/providers/aws/template.d.ts +34 -0
  34. package/dist/providers/aws/template.js +252 -0
  35. package/dist/providers/aws/template.test.d.ts +1 -0
  36. package/dist/providers/aws/template.test.js +208 -0
  37. package/dist/providers/do/api.d.ts +2 -0
  38. package/dist/providers/do/api.js +124 -26
  39. package/dist/providers/do/droplet.js +14 -0
  40. package/dist/providers/firebase/api.js +92 -29
  41. package/dist/providers/firebase/list.d.ts +2 -0
  42. package/dist/providers/firebase/list.js +25 -0
  43. package/dist/providers/gcp/api.js +88 -14
  44. package/dist/providers/gcp/index.d.ts +3 -1
  45. package/dist/providers/gcp/index.js +3 -1
  46. package/dist/providers/gcp/list.d.ts +2 -0
  47. package/dist/providers/gcp/list.js +55 -0
  48. package/dist/providers/gcp/secrets.js +21 -4
  49. package/dist/providers/gcp/template.d.ts +32 -0
  50. package/dist/providers/gcp/template.js +252 -0
  51. package/dist/providers/gcp/template.test.d.ts +1 -0
  52. package/dist/providers/gcp/template.test.js +227 -0
  53. package/dist/providers/gcp/vm.d.ts +3 -0
  54. package/dist/providers/gcp/vm.js +46 -3
  55. package/dist/providers/proxmox/api.d.ts +1 -0
  56. package/dist/providers/proxmox/api.js +72 -16
  57. package/dist/providers/proxmox/index.d.ts +3 -1
  58. package/dist/providers/proxmox/index.js +14 -1
  59. package/dist/providers/proxmox/template.d.ts +44 -0
  60. package/dist/providers/proxmox/template.js +350 -0
  61. package/dist/providers/proxmox/template.test.d.ts +1 -0
  62. package/dist/providers/proxmox/template.test.js +215 -0
  63. package/dist/providers/proxmox/vm.d.ts +3 -0
  64. package/dist/providers/proxmox/vm.js +43 -11
  65. package/dist/types/inventory.d.ts +44 -1
  66. package/package.json +2 -2
@@ -1,24 +1,37 @@
1
1
  import "reflect-metadata";
2
2
  import { BaseBuilder } from "./resource.js";
3
+ import { Config } from "./config.js";
4
+ import { resourceContextStorage } from "./context.js";
5
+ import { resolvedSecrets } from "./secret.js";
3
6
  const _registry = new Map();
4
- function formatEntry(val) {
7
+ function formatEntry(val, parentKey) {
8
+ const isSensitiveKey = (k) => /password|secret|token|key/i.test(k);
9
+ if (parentKey && isSensitiveKey(parentKey)) {
10
+ return { primary: "********" };
11
+ }
5
12
  if (!val || typeof val !== "object")
6
13
  return { primary: String(val) };
14
+ const redactedVal = { ...val };
15
+ for (const k of Object.keys(redactedVal)) {
16
+ if (isSensitiveKey(k)) {
17
+ redactedVal[k] = "********";
18
+ }
19
+ }
7
20
  // Known shapes
8
- if ("destroyed" in val)
9
- return { primary: val.destroyed ? "🗑️ destroyed" : "─ not found" };
10
- if (val.zone)
11
- return { primary: val.zone };
12
- if (val.name && val.id)
13
- return { primary: `${val.name} [${val.id}]` };
14
- if (val.name)
15
- return { primary: val.name };
16
- if (val.arn)
17
- return { primary: val.arn };
21
+ if ("destroyed" in redactedVal)
22
+ return { primary: redactedVal.destroyed ? "🗑️ destroyed" : "─ not found" };
23
+ if (redactedVal.zone)
24
+ return { primary: redactedVal.zone };
25
+ if (redactedVal.name && redactedVal.id)
26
+ return { primary: `${redactedVal.name} [${redactedVal.id}]` };
27
+ if (redactedVal.name)
28
+ return { primary: redactedVal.name };
29
+ if (redactedVal.arn)
30
+ return { primary: redactedVal.arn };
18
31
  // Generic: pull all scalar values
19
- const pairs = Object.entries(val).filter(([, v]) => typeof v === "string" || typeof v === "number");
32
+ const pairs = Object.entries(redactedVal).filter(([, v]) => typeof v === "string" || typeof v === "number");
20
33
  if (pairs.length === 0)
21
- return { primary: JSON.stringify(val) };
34
+ return { primary: JSON.stringify(redactedVal) };
22
35
  // Try compact inline (values only, dot-separated)
23
36
  const inline = pairs.map(([, v]) => v).join(" · ");
24
37
  if (inline.length <= 52)
@@ -35,7 +48,7 @@ function printOutputs(stackName, outputs) {
35
48
  const keyWidth = Math.max(...Object.keys(outputs).map((k) => k.length));
36
49
  const rows = Object.entries(outputs).map(([key, val]) => ({
37
50
  key,
38
- ...formatEntry(val),
51
+ ...formatEntry(val, key),
39
52
  }));
40
53
  // textWidth = width of row text content (without the 2-space padding on each side)
41
54
  const textWidth = Math.max(...rows.flatMap(({ key, primary, sub }) => [
@@ -87,75 +100,368 @@ export class Stack {
87
100
  return instance;
88
101
  }
89
102
  async deploy() {
90
- console.log(`\n🏗️ Deploying Stack: ${this.constructor.name}`);
91
- // Stack-level beforeDeploy hook
92
- if (typeof this.beforeDeploy === "function") {
93
- console.log(` ⚡ Running Stack-level beforeDeploy hook...`);
94
- await this.beforeDeploy();
95
- }
96
- const props = Object.getOwnPropertyNames(this);
97
- const outputs = {};
98
- for (const prop of props) {
99
- const resource = this[prop];
100
- if (resource instanceof BaseBuilder) {
101
- const isProtected = Reflect.getMetadata("protected", this, prop);
102
- const isDestroyed = Reflect.getMetadata("destroy", this, prop);
103
- if (isProtected)
104
- resource.protect();
105
- const forceConfigCheck = Reflect.getMetadata("forceConfigCheck", this, prop);
106
- if (forceConfigCheck && typeof resource.forceConfigCheck === "function") {
107
- resource.forceConfigCheck();
103
+ const controller = new AbortController();
104
+ const hosts = [];
105
+ const context = {
106
+ abortSignal: controller.signal,
107
+ hosts,
108
+ stackName: this.constructor.name
109
+ };
110
+ return resourceContextStorage.run(context, async () => {
111
+ const originalLog = console.log;
112
+ console.log = (...args) => {
113
+ const redact = (message) => {
114
+ if (typeof message !== "string")
115
+ return message;
116
+ let result = message;
117
+ for (const secret of resolvedSecrets) {
118
+ if (secret && secret.length >= 3) {
119
+ const escaped = secret.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
120
+ const regex = new RegExp(escaped, 'g');
121
+ result = result.replace(regex, '********');
122
+ }
123
+ }
124
+ return result;
125
+ };
126
+ const redactedArgs = args.map(arg => {
127
+ if (typeof arg === "string") {
128
+ return redact(arg);
129
+ }
130
+ try {
131
+ const str = String(arg);
132
+ let hasSecret = false;
133
+ for (const secret of resolvedSecrets) {
134
+ if (secret && secret.length >= 3 && str.includes(secret)) {
135
+ hasSecret = true;
136
+ break;
137
+ }
138
+ }
139
+ if (hasSecret) {
140
+ return redact(str);
141
+ }
142
+ }
143
+ catch { }
144
+ return arg;
145
+ });
146
+ originalLog(...redactedArgs);
147
+ };
148
+ try {
149
+ console.log(`\n🏗️ Deploying Stack: ${this.constructor.name}`);
150
+ // Stack-level beforeDeploy hook
151
+ if (typeof this.beforeDeploy === "function") {
152
+ console.log(` ⚡ Running Stack-level beforeDeploy hook...`);
153
+ await this.beforeDeploy();
154
+ }
155
+ const props = Object.getOwnPropertyNames(this);
156
+ const outputs = {};
157
+ const isParallel = Config.isParallelActive();
158
+ // 1. Gather all resources
159
+ const resources = [];
160
+ for (const prop of props) {
161
+ const val = this[prop];
162
+ if (val instanceof BaseBuilder) {
163
+ resources.push({ prop, resource: val });
164
+ // Apply metadata properties eagerly
165
+ const isProtected = Reflect.getMetadata("protected", this, prop);
166
+ if (isProtected)
167
+ val.protect();
168
+ const forceConfigCheck = Reflect.getMetadata("forceConfigCheck", this, prop);
169
+ if (forceConfigCheck && typeof val.forceConfigCheck === "function") {
170
+ val.forceConfigCheck();
171
+ }
172
+ }
173
+ else if (Array.isArray(val)) {
174
+ const isProtected = Reflect.getMetadata("protected", this, prop);
175
+ const forceConfigCheck = Reflect.getMetadata("forceConfigCheck", this, prop);
176
+ for (const item of val) {
177
+ if (item instanceof BaseBuilder) {
178
+ resources.push({ prop, resource: item });
179
+ if (isProtected)
180
+ item.protect();
181
+ if (forceConfigCheck && typeof item.forceConfigCheck === "function") {
182
+ item.forceConfigCheck();
183
+ }
184
+ }
185
+ }
186
+ }
108
187
  }
109
- let res;
110
- if (isDestroyed) {
111
- await resource._runBeforeDestroy();
112
- res = await resource.destroy();
113
- await resource._runAfterDestroy(res);
188
+ // 2. Schedule execution
189
+ if (isParallel) {
190
+ const startPromise = Promise.resolve();
191
+ const promises = resources.map(({ prop, resource }) => {
192
+ resource._deployPromise = (async () => {
193
+ try {
194
+ await startPromise;
195
+ if (controller.signal.aborted) {
196
+ throw new Error("Deployment aborted due to previous failure");
197
+ }
198
+ // Wait for explicit dependencies
199
+ for (const dep of resource._dependencies) {
200
+ if (dep._deployPromise) {
201
+ await dep._deployPromise;
202
+ }
203
+ }
204
+ if (controller.signal.aborted) {
205
+ throw new Error("Deployment aborted due to previous failure");
206
+ }
207
+ // Execute hooks and deploy
208
+ const isDestroyed = Reflect.getMetadata("destroy", this, prop);
209
+ let res;
210
+ if (isDestroyed) {
211
+ await resource._runBeforeDestroy();
212
+ res = await resource.destroy();
213
+ await resource._runAfterDestroy(res);
214
+ }
215
+ else {
216
+ await resource._runBeforeDeploy();
217
+ res = await resource.deploy();
218
+ await resource._runAfterDeploy(res);
219
+ }
220
+ const propVal = this[prop];
221
+ if (Array.isArray(propVal)) {
222
+ const idx = propVal.indexOf(resource);
223
+ if (idx !== -1) {
224
+ if (!outputs[prop]) {
225
+ outputs[prop] = [];
226
+ }
227
+ outputs[prop][idx] = res;
228
+ }
229
+ }
230
+ else {
231
+ outputs[prop] = res;
232
+ }
233
+ return res;
234
+ }
235
+ catch (err) {
236
+ controller.abort();
237
+ throw err;
238
+ }
239
+ })();
240
+ return resource._deployPromise;
241
+ });
242
+ await Promise.all(promises);
114
243
  }
115
244
  else {
116
- await resource._runBeforeDeploy();
117
- res = await resource.deploy();
118
- await resource._runAfterDeploy(res);
245
+ // Sequential mode
246
+ for (const { prop, resource } of resources) {
247
+ if (controller.signal.aborted) {
248
+ throw new Error("Deployment aborted due to previous failure");
249
+ }
250
+ try {
251
+ const isDestroyed = Reflect.getMetadata("destroy", this, prop);
252
+ let res;
253
+ if (isDestroyed) {
254
+ await resource._runBeforeDestroy();
255
+ res = await resource.destroy();
256
+ await resource._runAfterDestroy(res);
257
+ }
258
+ else {
259
+ await resource._runBeforeDeploy();
260
+ res = await resource.deploy();
261
+ await resource._runAfterDeploy(res);
262
+ }
263
+ const propVal = this[prop];
264
+ if (Array.isArray(propVal)) {
265
+ const idx = propVal.indexOf(resource);
266
+ if (idx !== -1) {
267
+ if (!outputs[prop]) {
268
+ outputs[prop] = [];
269
+ }
270
+ outputs[prop][idx] = res;
271
+ }
272
+ }
273
+ else {
274
+ outputs[prop] = res;
275
+ }
276
+ }
277
+ catch (err) {
278
+ controller.abort();
279
+ throw err;
280
+ }
281
+ }
119
282
  }
120
- outputs[prop] = res;
283
+ printOutputs(this.constructor.name, outputs);
284
+ // Stack-level afterDeploy hook
285
+ if (typeof this.afterDeploy === "function") {
286
+ console.log(` ⚡ Running Stack-level afterDeploy hook...`);
287
+ await this.afterDeploy(outputs);
288
+ }
289
+ return outputs;
121
290
  }
122
- }
123
- printOutputs(this.constructor.name, outputs);
124
- // Stack-level afterDeploy hook
125
- if (typeof this.afterDeploy === "function") {
126
- console.log(` ⚡ Running Stack-level afterDeploy hook...`);
127
- await this.afterDeploy(outputs);
128
- }
129
- return outputs;
291
+ finally {
292
+ console.log = originalLog;
293
+ }
294
+ });
130
295
  }
131
296
  async destroy() {
132
- console.log(`\n💥 Tearing down Stack: ${this.constructor.name}`);
133
- // Stack-level beforeDestroy hook
134
- if (typeof this.beforeDestroy === "function") {
135
- console.log(` ⚡ Running Stack-level beforeDestroy hook...`);
136
- await this.beforeDestroy();
137
- }
138
- const props = Object.getOwnPropertyNames(this).reverse();
139
- const outputs = {};
140
- for (const prop of props) {
141
- const resource = this[prop];
142
- if (resource instanceof BaseBuilder) {
143
- if (Reflect.getMetadata("protected", this, prop)) {
144
- console.log(` 🔒 Skipping protected resource "${prop}"`);
145
- continue;
297
+ const controller = new AbortController();
298
+ const hosts = [];
299
+ const context = {
300
+ abortSignal: controller.signal,
301
+ hosts,
302
+ stackName: this.constructor.name
303
+ };
304
+ return resourceContextStorage.run(context, async () => {
305
+ const originalLog = console.log;
306
+ console.log = (...args) => {
307
+ const redact = (message) => {
308
+ if (typeof message !== "string")
309
+ return message;
310
+ let result = message;
311
+ for (const secret of resolvedSecrets) {
312
+ if (secret && secret.length >= 3) {
313
+ const escaped = secret.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
314
+ const regex = new RegExp(escaped, 'g');
315
+ result = result.replace(regex, '********');
316
+ }
317
+ }
318
+ return result;
319
+ };
320
+ const redactedArgs = args.map(arg => {
321
+ if (typeof arg === "string") {
322
+ return redact(arg);
323
+ }
324
+ try {
325
+ const str = String(arg);
326
+ let hasSecret = false;
327
+ for (const secret of resolvedSecrets) {
328
+ if (secret && secret.length >= 3 && str.includes(secret)) {
329
+ hasSecret = true;
330
+ break;
331
+ }
332
+ }
333
+ if (hasSecret) {
334
+ return redact(str);
335
+ }
336
+ }
337
+ catch { }
338
+ return arg;
339
+ });
340
+ originalLog(...redactedArgs);
341
+ };
342
+ try {
343
+ console.log(`\n💥 Tearing down Stack: ${this.constructor.name}`);
344
+ // Stack-level beforeDestroy hook
345
+ if (typeof this.beforeDestroy === "function") {
346
+ console.log(` ⚡ Running Stack-level beforeDestroy hook...`);
347
+ await this.beforeDestroy();
348
+ }
349
+ const props = Object.getOwnPropertyNames(this).reverse();
350
+ const outputs = {};
351
+ const isParallel = Config.isParallelActive();
352
+ // 1. Gather all resources
353
+ const resources = [];
354
+ for (const prop of props) {
355
+ const val = this[prop];
356
+ if (val instanceof BaseBuilder) {
357
+ if (Reflect.getMetadata("protected", this, prop)) {
358
+ console.log(` 🔒 Skipping protected resource "${prop}"`);
359
+ continue;
360
+ }
361
+ resources.push({ prop, resource: val });
362
+ }
363
+ else if (Array.isArray(val)) {
364
+ if (Reflect.getMetadata("protected", this, prop)) {
365
+ console.log(` 🔒 Skipping protected resource "${prop}"`);
366
+ continue;
367
+ }
368
+ for (const item of val) {
369
+ if (item instanceof BaseBuilder) {
370
+ resources.push({ prop, resource: item });
371
+ }
372
+ }
373
+ }
374
+ }
375
+ // 2. Schedule execution
376
+ if (isParallel) {
377
+ const startPromise = Promise.resolve();
378
+ // In parallel destroy, await all dependents (reverse dependencies) first
379
+ const promises = resources.map(({ prop, resource }) => {
380
+ resource._destroyPromise = (async () => {
381
+ try {
382
+ await startPromise;
383
+ if (controller.signal.aborted) {
384
+ throw new Error("Teardown aborted due to previous failure");
385
+ }
386
+ // Wait for all resources that explicitly declare this one as a dependency
387
+ const dependents = resources.filter(r => r.resource._dependencies.includes(resource));
388
+ for (const dep of dependents) {
389
+ if (dep.resource._destroyPromise) {
390
+ await dep.resource._destroyPromise;
391
+ }
392
+ }
393
+ if (controller.signal.aborted) {
394
+ throw new Error("Teardown aborted due to previous failure");
395
+ }
396
+ // Execute teardown
397
+ await resource._runBeforeDestroy();
398
+ const res = await resource.destroy();
399
+ await resource._runAfterDestroy(res);
400
+ const propVal = this[prop];
401
+ if (Array.isArray(propVal)) {
402
+ const idx = propVal.indexOf(resource);
403
+ if (idx !== -1) {
404
+ if (!outputs[prop]) {
405
+ outputs[prop] = [];
406
+ }
407
+ outputs[prop][idx] = res;
408
+ }
409
+ }
410
+ else {
411
+ outputs[prop] = res;
412
+ }
413
+ return res;
414
+ }
415
+ catch (err) {
416
+ controller.abort();
417
+ throw err;
418
+ }
419
+ })();
420
+ return resource._destroyPromise;
421
+ });
422
+ await Promise.all(promises);
423
+ }
424
+ else {
425
+ // Sequential mode
426
+ for (const { prop, resource } of resources) {
427
+ if (controller.signal.aborted) {
428
+ throw new Error("Teardown aborted due to previous failure");
429
+ }
430
+ try {
431
+ await resource._runBeforeDestroy();
432
+ const res = await resource.destroy();
433
+ await resource._runAfterDestroy(res);
434
+ const propVal = this[prop];
435
+ if (Array.isArray(propVal)) {
436
+ const idx = propVal.indexOf(resource);
437
+ if (idx !== -1) {
438
+ if (!outputs[prop]) {
439
+ outputs[prop] = [];
440
+ }
441
+ outputs[prop][idx] = res;
442
+ }
443
+ }
444
+ else {
445
+ outputs[prop] = res;
446
+ }
447
+ }
448
+ catch (err) {
449
+ controller.abort();
450
+ throw err;
451
+ }
452
+ }
146
453
  }
147
- await resource._runBeforeDestroy();
148
- const res = await resource.destroy();
149
- await resource._runAfterDestroy(res);
150
- outputs[prop] = res;
454
+ printOutputs(this.constructor.name, outputs);
455
+ // Stack-level afterDestroy hook
456
+ if (typeof this.afterDestroy === "function") {
457
+ console.log(` ⚡ Running Stack-level afterDestroy hook...`);
458
+ await this.afterDestroy(outputs);
459
+ }
460
+ return outputs;
151
461
  }
152
- }
153
- printOutputs(this.constructor.name, outputs);
154
- // Stack-level afterDestroy hook
155
- if (typeof this.afterDestroy === "function") {
156
- console.log(` ⚡ Running Stack-level afterDestroy hook...`);
157
- await this.afterDestroy(outputs);
158
- }
159
- return outputs;
462
+ finally {
463
+ console.log = originalLog;
464
+ }
465
+ });
160
466
  }
161
467
  }
package/dist/index.d.ts CHANGED
@@ -3,5 +3,6 @@ export * from "./core/decorators.js";
3
3
  export * from "./core/checker.js";
4
4
  export * from "./core/resource.js";
5
5
  export { Secret } from "./core/secret.js";
6
+ export { Output } from "./core/output.js";
6
7
  export * as INVENTORY_TYPES from "./types/inventory.js";
7
8
  export { SLACK, DISCORD } from "./core/hooks.js";
package/dist/index.js CHANGED
@@ -3,5 +3,6 @@ export * from "./core/decorators.js";
3
3
  export * from "./core/checker.js";
4
4
  export * from "./core/resource.js";
5
5
  export { Secret } from "./core/secret.js";
6
+ export { Output } from "./core/output.js";
6
7
  export * as INVENTORY_TYPES from "./types/inventory.js";
7
8
  export { SLACK, DISCORD } from "./core/hooks.js";
@@ -15,26 +15,106 @@ import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
15
15
  import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
16
16
  import { SNSClient } from "@aws-sdk/client-sns";
17
17
  import { Config } from "../../core/config.js";
18
+ import { withRetry } from "../../core/retry.js";
19
+ import { resourceContextStorage } from "../../core/context.js";
18
20
  function getRegion() {
19
21
  const region = Config.get().providers.aws?.region;
20
- if (!region)
22
+ if (!region) {
23
+ if (Config.isOfflineMode() || Config.isGlobalDryRun()) {
24
+ return "us-east-1";
25
+ }
21
26
  throw new Error('AWS region not configured. Call AWS.init({ region: "..." })');
27
+ }
22
28
  return region;
23
29
  }
24
- export const getS3Client = (region) => new S3Client({ region: region ?? getRegion() });
30
+ function createAwsOfflineMock(command) {
31
+ const name = command.constructor.name;
32
+ if (name.includes("RunInstances")) {
33
+ return {
34
+ Instances: [
35
+ {
36
+ InstanceId: "i-mock1234567890abcdef0",
37
+ State: { Name: "running" },
38
+ PublicIpAddress: "54.210.12.34",
39
+ PrivateIpAddress: "10.0.1.10",
40
+ }
41
+ ]
42
+ };
43
+ }
44
+ if (name.includes("CreateVpc")) {
45
+ return { Vpc: { VpcId: "vpc-mock123456" } };
46
+ }
47
+ if (name.includes("CreateSubnet")) {
48
+ return { Subnet: { SubnetId: "subnet-mock123456" } };
49
+ }
50
+ if (name.includes("CreateSecurityGroup")) {
51
+ return { GroupId: "sg-mock123456" };
52
+ }
53
+ if (name.includes("CreateBucket")) {
54
+ return { Location: "/mock-bucket" };
55
+ }
56
+ if (name.includes("CreateKeyPair")) {
57
+ return { KeyMaterial: "mock-private-key", KeyName: "mock-key" };
58
+ }
59
+ const mockProxy = new Proxy({}, {
60
+ get(target, prop) {
61
+ if (prop === "then")
62
+ return undefined;
63
+ if (prop === "CertificateArn")
64
+ return "arn:aws:acm:us-east-1:123456789012:certificate/mock-cert-uuid";
65
+ if (prop === "HostedZoneId")
66
+ return "Z2FDTNDATAQYW2";
67
+ if (prop === "Id" || prop === "id")
68
+ return "mock-id-12345";
69
+ if (prop === "Arn" || prop === "arn")
70
+ return `arn:aws:mock:::resource/mock-id`;
71
+ if (prop === "Status" || prop === "status")
72
+ return "Active";
73
+ if (prop === "DNSName")
74
+ return "mock.cloudfront.net";
75
+ if (prop.endsWith("s"))
76
+ return [];
77
+ return `mock-${prop.toLowerCase()}`;
78
+ }
79
+ });
80
+ return mockProxy;
81
+ }
82
+ function wrapClient(client) {
83
+ const originalSend = client.send;
84
+ client.send = function (command, options) {
85
+ const context = resourceContextStorage.getStore();
86
+ const abortSignal = context?.abortSignal;
87
+ if (Config.isOfflineMode() || Config.isGlobalDryRun()) {
88
+ return Promise.resolve(createAwsOfflineMock(command));
89
+ }
90
+ const opts = abortSignal ? { abortSignal, ...options } : options;
91
+ return withRetry(() => originalSend.call(client, command, opts), {
92
+ retryable: (err) => {
93
+ const code = err.name || err.code;
94
+ const status = err.$metadata?.httpStatusCode;
95
+ return (code === "ThrottlingException" ||
96
+ code === "ProvisionedThroughputExceededException" ||
97
+ code === "RequestLimitExceeded" ||
98
+ (status && status >= 500));
99
+ }
100
+ });
101
+ };
102
+ return client;
103
+ }
104
+ export const getS3Client = (region) => wrapClient(new S3Client({ region: region ?? getRegion() }));
25
105
  // CloudFront, Route53, ACM, Route53 Domains, and IAM are all global - must use us-east-1
26
- export const getCFClient = () => new CloudFrontClient({ region: "us-east-1" });
27
- export const getR53Client = () => new Route53Client({ region: "us-east-1" });
28
- export const getR53DomainsClient = () => new Route53DomainsClient({ region: "us-east-1" });
29
- export const getACMClient = () => new ACMClient({ region: "us-east-1" });
30
- export const getIAMClient = () => new IAMClient({ region: "us-east-1" });
31
- export const getLambdaClient = (region) => new LambdaClient({ region: region ?? getRegion() });
32
- export const getAPIGWClient = (region) => new ApiGatewayV2Client({ region: region ?? getRegion() });
33
- export const getECSClient = (region) => new ECSClient({ region: region ?? getRegion() });
34
- export const getEC2Client = (region) => new EC2Client({ region: region ?? getRegion() });
35
- export const getCWLogsClient = (region) => new CloudWatchLogsClient({ region: region ?? getRegion() });
36
- export const getRDSClient = (region) => new RDSClient({ region: region ?? getRegion() });
37
- export const getSQSClient = (region) => new SQSClient({ region: region ?? getRegion() });
38
- export const getSecretsClient = (region) => new SecretsManagerClient({ region: region ?? getRegion() });
39
- export const getCWClient = (region) => new CloudWatchClient({ region: region ?? getRegion() });
40
- export const getSNSClient = (region) => new SNSClient({ region: region ?? getRegion() });
106
+ export const getCFClient = () => wrapClient(new CloudFrontClient({ region: "us-east-1" }));
107
+ export const getR53Client = () => wrapClient(new Route53Client({ region: "us-east-1" }));
108
+ export const getR53DomainsClient = () => wrapClient(new Route53DomainsClient({ region: "us-east-1" }));
109
+ export const getACMClient = () => wrapClient(new ACMClient({ region: "us-east-1" }));
110
+ export const getIAMClient = () => wrapClient(new IAMClient({ region: "us-east-1" }));
111
+ export const getLambdaClient = (region) => wrapClient(new LambdaClient({ region: region ?? getRegion() }));
112
+ export const getAPIGWClient = (region) => wrapClient(new ApiGatewayV2Client({ region: region ?? getRegion() }));
113
+ export const getECSClient = (region) => wrapClient(new ECSClient({ region: region ?? getRegion() }));
114
+ export const getEC2Client = (region) => wrapClient(new EC2Client({ region: region ?? getRegion() }));
115
+ export const getCWLogsClient = (region) => wrapClient(new CloudWatchLogsClient({ region: region ?? getRegion() }));
116
+ export const getRDSClient = (region) => wrapClient(new RDSClient({ region: region ?? getRegion() }));
117
+ export const getSQSClient = (region) => wrapClient(new SQSClient({ region: region ?? getRegion() }));
118
+ export const getSecretsClient = (region) => wrapClient(new SecretsManagerClient({ region: region ?? getRegion() }));
119
+ export const getCWClient = (region) => wrapClient(new CloudWatchClient({ region: region ?? getRegion() }));
120
+ export const getSNSClient = (region) => wrapClient(new SNSClient({ region: region ?? getRegion() }));
@@ -1,5 +1,6 @@
1
1
  import { BaseBuilder } from "../../core/resource.js";
2
2
  import { Output } from "../../core/output.js";
3
+ import { EC2TemplateBuilder } from "./template.js";
3
4
  export declare class EC2VMBuilder extends BaseBuilder {
4
5
  readonly out: {
5
6
  ip: Output<string>;
@@ -7,6 +8,7 @@ export declare class EC2VMBuilder extends BaseBuilder {
7
8
  };
8
9
  private _instanceType;
9
10
  private _ami;
11
+ private _templateSource?;
10
12
  private _keyName?;
11
13
  private _subnetId?;
12
14
  private _securityGroupIds?;
@@ -19,6 +21,7 @@ export declare class EC2VMBuilder extends BaseBuilder {
19
21
  constructor(name: string);
20
22
  instanceType(type: string): this;
21
23
  ami(amiId: string): this;
24
+ fromTemplate(template: EC2TemplateBuilder): this;
22
25
  keyName(name: string): this;
23
26
  subnetId(id: string): this;
24
27
  securityGroupIds(ids: string[]): this;