puls-dev 0.1.0 → 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 (50) 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/config.test.d.ts +1 -0
  5. package/dist/core/config.test.js +21 -0
  6. package/dist/core/decorators.js +8 -2
  7. package/dist/core/output.test.d.ts +1 -0
  8. package/dist/core/output.test.js +18 -0
  9. package/dist/core/resource.js +2 -2
  10. package/dist/core/stack.d.ts +1 -1
  11. package/dist/core/stack.js +2 -2
  12. package/dist/providers/aws/acm.d.ts +1 -1
  13. package/dist/providers/aws/acm.js +27 -23
  14. package/dist/providers/aws/api.d.ts +14 -14
  15. package/dist/providers/aws/api.js +21 -21
  16. package/dist/providers/aws/apigateway.d.ts +2 -2
  17. package/dist/providers/aws/apigateway.js +33 -29
  18. package/dist/providers/aws/cloudfront.d.ts +3 -3
  19. package/dist/providers/aws/cloudfront.js +49 -34
  20. package/dist/providers/aws/fargate.d.ts +2 -2
  21. package/dist/providers/aws/fargate.js +99 -52
  22. package/dist/providers/aws/lambda.d.ts +2 -2
  23. package/dist/providers/aws/lambda.js +63 -32
  24. package/dist/providers/aws/rds.d.ts +1 -1
  25. package/dist/providers/aws/rds.js +77 -39
  26. package/dist/providers/aws/route53.d.ts +5 -5
  27. package/dist/providers/aws/route53.js +42 -35
  28. package/dist/providers/aws/s3.d.ts +2 -2
  29. package/dist/providers/aws/s3.js +40 -33
  30. package/dist/providers/aws/secrets.js +15 -7
  31. package/dist/providers/aws/sqs.d.ts +1 -1
  32. package/dist/providers/aws/sqs.js +47 -23
  33. package/dist/providers/do/domain.d.ts +4 -4
  34. package/dist/providers/do/domain.js +15 -11
  35. package/dist/providers/firebase/auth.d.ts +1 -1
  36. package/dist/providers/firebase/auth.js +65 -33
  37. package/dist/providers/firebase/firestore.d.ts +2 -2
  38. package/dist/providers/firebase/firestore.js +45 -28
  39. package/dist/providers/firebase/functions.d.ts +1 -1
  40. package/dist/providers/firebase/functions.js +75 -42
  41. package/dist/providers/firebase/hosting.d.ts +1 -1
  42. package/dist/providers/firebase/hosting.js +92 -52
  43. package/dist/providers/firebase/remoteconfig.d.ts +1 -1
  44. package/dist/providers/firebase/remoteconfig.js +42 -33
  45. package/dist/providers/firebase/storage.d.ts +1 -1
  46. package/dist/providers/firebase/storage.js +38 -24
  47. package/dist/providers/proxmox/vm.d.ts +1 -1
  48. package/dist/providers/proxmox/vm.js +43 -24
  49. package/dist/types/aws.js +1 -1
  50. package/package.json +3 -2
@@ -1,8 +1,8 @@
1
- import { readFileSync } from 'node:fs';
2
- import { BaseBuilder } from '../../core/resource.js';
3
- import { cloudFetch, getProjectId } from './api.js';
4
- const RULES_BASE = 'https://firebaserules.googleapis.com/v1';
5
- const GCS_BASE = 'https://storage.googleapis.com/storage/v1';
1
+ import { readFileSync } from "node:fs";
2
+ import { BaseBuilder } from "../../core/resource.js";
3
+ import { cloudFetch, getProjectId } from "./api.js";
4
+ const RULES_BASE = "https://firebaserules.googleapis.com/v1";
5
+ const GCS_BASE = "https://storage.googleapis.com/storage/v1";
6
6
  export class FirebaseStorageBuilder extends BaseBuilder {
7
7
  _rulesPath;
8
8
  _cors = [];
@@ -10,13 +10,22 @@ export class FirebaseStorageBuilder extends BaseBuilder {
10
10
  _resolvedBucket;
11
11
  constructor(bucket) {
12
12
  // Bucket name resolved lazily in deploy() if not provided (needs projectId)
13
- super(bucket ?? '__default__');
13
+ super(bucket ?? "__default__");
14
14
  this._resolvedBucket = bucket;
15
15
  this.discoveryPromise = Promise.resolve(null);
16
16
  }
17
- rules(filePath) { this._rulesPath = filePath; return this; }
18
- cors(rules) { this._cors = rules; return this; }
19
- lifecycle(rule) { this._lifecycle = rule; return this; }
17
+ rules(filePath) {
18
+ this._rulesPath = filePath;
19
+ return this;
20
+ }
21
+ cors(rules) {
22
+ this._cors = rules;
23
+ return this;
24
+ }
25
+ lifecycle(rule) {
26
+ this._lifecycle = rule;
27
+ return this;
28
+ }
20
29
  bucket() {
21
30
  return this._resolvedBucket ?? `${getProjectId()}.appspot.com`;
22
31
  }
@@ -27,26 +36,31 @@ export class FirebaseStorageBuilder extends BaseBuilder {
27
36
  async deployRules(dryRun) {
28
37
  if (!this._rulesPath)
29
38
  return;
30
- const source = readFileSync(this._rulesPath, 'utf8');
39
+ const source = readFileSync(this._rulesPath, "utf8");
31
40
  if (dryRun) {
32
41
  console.log(` 📝 [PLAN] Deploy Storage rules from "${this._rulesPath}" → ${this.bucket()}`);
33
42
  return;
34
43
  }
35
44
  const ruleset = await cloudFetch(RULES_BASE, `/projects/${getProjectId()}/rulesets`, {
36
- method: 'POST',
37
- body: JSON.stringify({ source: { files: [{ name: 'storage.rules', content: source }] } }),
45
+ method: "POST",
46
+ body: JSON.stringify({
47
+ source: { files: [{ name: "storage.rules", content: source }] },
48
+ }),
38
49
  });
39
50
  await cloudFetch(RULES_BASE, `/${this.releaseName()}`, {
40
- method: 'PUT',
41
- body: JSON.stringify({ name: this.releaseName(), rulesetName: ruleset.name }),
51
+ method: "PUT",
52
+ body: JSON.stringify({
53
+ name: this.releaseName(),
54
+ rulesetName: ruleset.name,
55
+ }),
42
56
  });
43
- console.log(` ✅ Storage rules deployed (ruleset: ${ruleset.name.split('/').pop()})`);
57
+ console.log(` ✅ Storage rules deployed (ruleset: ${ruleset.name.split("/").pop()})`);
44
58
  }
45
59
  // ── CORS ──────────────────────────────────────────────────────────────────
46
60
  async deployCors(dryRun) {
47
61
  if (this._cors.length === 0)
48
62
  return;
49
- const corsBody = this._cors.map(r => ({
63
+ const corsBody = this._cors.map((r) => ({
50
64
  origin: r.origin,
51
65
  method: r.method,
52
66
  responseHeader: r.responseHeader ?? [],
@@ -55,12 +69,12 @@ export class FirebaseStorageBuilder extends BaseBuilder {
55
69
  if (dryRun) {
56
70
  console.log(` 📝 [PLAN] Set CORS on bucket "${this.bucket()}" (${this._cors.length} rule(s))`);
57
71
  for (const r of this._cors) {
58
- console.log(` └─ origins: [${r.origin.join(', ')}], methods: [${r.method.join(', ')}]`);
72
+ console.log(` └─ origins: [${r.origin.join(", ")}], methods: [${r.method.join(", ")}]`);
59
73
  }
60
74
  return;
61
75
  }
62
76
  await cloudFetch(GCS_BASE, `/b/${this.bucket()}`, {
63
- method: 'PATCH',
77
+ method: "PATCH",
64
78
  body: JSON.stringify({ cors: corsBody }),
65
79
  });
66
80
  console.log(` ✅ CORS configured on "${this.bucket()}"`);
@@ -70,7 +84,7 @@ export class FirebaseStorageBuilder extends BaseBuilder {
70
84
  if (!this._lifecycle)
71
85
  return;
72
86
  const rule = {
73
- action: { type: 'Delete' },
87
+ action: { type: "Delete" },
74
88
  condition: {},
75
89
  };
76
90
  if (this._lifecycle.deleteAfterDays !== undefined)
@@ -82,12 +96,12 @@ export class FirebaseStorageBuilder extends BaseBuilder {
82
96
  if (this._lifecycle.deleteAfterDays)
83
97
  parts.push(`delete after ${this._lifecycle.deleteAfterDays} days`);
84
98
  if (this._lifecycle.matchesPrefix)
85
- parts.push(`prefix: [${this._lifecycle.matchesPrefix.join(', ')}]`);
86
- console.log(` 📝 [PLAN] Set lifecycle on "${this.bucket()}": ${parts.join(', ')}`);
99
+ parts.push(`prefix: [${this._lifecycle.matchesPrefix.join(", ")}]`);
100
+ console.log(` 📝 [PLAN] Set lifecycle on "${this.bucket()}": ${parts.join(", ")}`);
87
101
  return;
88
102
  }
89
103
  await cloudFetch(GCS_BASE, `/b/${this.bucket()}`, {
90
- method: 'PATCH',
104
+ method: "PATCH",
91
105
  body: JSON.stringify({ lifecycle: { rule: [rule] } }),
92
106
  });
93
107
  console.log(` ✅ Lifecycle configured on "${this.bucket()}"`);
@@ -107,10 +121,10 @@ export class FirebaseStorageBuilder extends BaseBuilder {
107
121
  const dryRun = this.isDryRunActive();
108
122
  console.log(`\nđŸ—‘ī¸ Destroying Firebase Storage config "${this.bucket()}"...`);
109
123
  if (dryRun) {
110
- console.log(` â„šī¸ Storage buckets cannot be deleted via API — remove manually in the GCP console`);
124
+ console.log(` â„šī¸ Storage buckets cannot be deleted via API - remove manually in the GCP console`);
111
125
  }
112
126
  else {
113
- console.log(` â„šī¸ Storage buckets cannot be deleted via API — remove manually in the GCP console`);
127
+ console.log(` â„šī¸ Storage buckets cannot be deleted via API - remove manually in the GCP console`);
114
128
  }
115
129
  return { destroyed: this.bucket() };
116
130
  }
@@ -24,7 +24,7 @@ export declare class VMBuilder extends BaseBuilder {
24
24
  image(os: OSImage): this;
25
25
  cores(n: number): this;
26
26
  memory(mb: number): this;
27
- provision(playbookPath: string): this;
27
+ provision(playbookPath: string | string[]): this;
28
28
  replace(oldVmName: string): this;
29
29
  node(n: string): this;
30
30
  storage(pool: string): this;
@@ -106,15 +106,19 @@ export class VMBuilder extends BaseBuilder {
106
106
  console.log(` └─ Cores: ${this._cores} Memory: ${this._memory} MB`);
107
107
  if (this._vlan)
108
108
  console.log(` └─ VLAN: ${this._vlan}`);
109
- if (this._provision)
110
- console.log(` └─ Provision: ${this._provision}`);
109
+ if (this._provision) {
110
+ const p = Array.isArray(this._provision)
111
+ ? this._provision.join(", ")
112
+ : this._provision;
113
+ console.log(` └─ Provision: ${p}`);
114
+ }
111
115
  if (this._replace)
112
116
  console.log(` └─ Replace: "${this._replace}" after creation`);
113
117
  this.out.vmid.resolve(-1);
114
118
  this.out.ip.resolve(this._ip?.split("/")[0] ?? "0.0.0.0");
115
119
  return { name: this.name, vmid: "PENDING" };
116
120
  }
117
- // Find the template — match by VMID (numeric string) or name substring
121
+ // Find the template - match by VMID (numeric string) or name substring
118
122
  const resources = await pm.get("/cluster/resources?type=vm");
119
123
  const isVmid = this._image && /^\d+$/.test(this._image);
120
124
  const template = this._image
@@ -152,7 +156,7 @@ export class VMBuilder extends BaseBuilder {
152
156
  storage,
153
157
  format: "raw",
154
158
  });
155
- // Clone is async — wait for the Proxmox task to finish before configuring
159
+ // Clone is async - wait for the Proxmox task to finish before configuring
156
160
  await this.waitForTask(node, taskId, pm);
157
161
  }
158
162
  else {
@@ -180,11 +184,11 @@ export class VMBuilder extends BaseBuilder {
180
184
  console.log(` 🔍 DNS: ${this.name}.${domain} → ${addr}`);
181
185
  }
182
186
  catch {
183
- // Not in DNS — will fall through to DHCP
187
+ // Not in DNS - will fall through to DHCP
184
188
  }
185
189
  }
186
190
  }
187
- // Build net0 string — VirtIO on vmbr1, optional VLAN tag
191
+ // Build net0 string - VirtIO on vmbr1, optional VLAN tag
188
192
  const net0 = `virtio,bridge=vmbr1${this._vlan ? `,tag=${this._vlan}` : ""}`;
189
193
  const configPatch = {
190
194
  onboot: 1,
@@ -240,7 +244,12 @@ export class VMBuilder extends BaseBuilder {
240
244
  if (this._provision) {
241
245
  await this.waitFor(`SSH on ${this.resolvedIp} to be ready`, () => this.checkPort(this.resolvedIp, 22), { intervalMs: 10_000, timeoutMs: 300_000 });
242
246
  await this.waitFor(`cloud-init to finish on ${this.resolvedIp}`, () => this.checkCloudInit(this.resolvedIp), { intervalMs: 15_000, timeoutMs: 300_000 });
243
- await this.runProvisioner(this.resolvedIp, this._provision);
247
+ const scripts = Array.isArray(this._provision)
248
+ ? this._provision
249
+ : [this._provision];
250
+ for (const script of scripts) {
251
+ await this.runProvisioner(this.resolvedIp, script);
252
+ }
244
253
  }
245
254
  if (this._replace) {
246
255
  await this.destroyVmByName(this._replace, pm);
@@ -280,7 +289,7 @@ export class VMBuilder extends BaseBuilder {
280
289
  const resources = await pm.get("/cluster/resources?type=vm");
281
290
  const vm = (resources ?? []).find((r) => r.name === name && !r.template);
282
291
  if (!vm) {
283
- console.log(` â„šī¸ VM "${name}" not found — already gone`);
292
+ console.log(` â„šī¸ VM "${name}" not found - already gone`);
284
293
  return;
285
294
  }
286
295
  if (vm.status === "running") {
@@ -325,27 +334,37 @@ export class VMBuilder extends BaseBuilder {
325
334
  }
326
335
  checkCloudInit(ip) {
327
336
  const keyPath = this.sshKeyPath();
328
- return new Promise(resolve => {
329
- const proc = spawn('ssh', [
330
- '-i', keyPath,
331
- '-o', 'StrictHostKeyChecking=no',
332
- '-o', 'ConnectTimeout=10',
333
- '-o', 'BatchMode=yes',
337
+ return new Promise((resolve) => {
338
+ const proc = spawn("ssh", [
339
+ "-i",
340
+ keyPath,
341
+ "-o",
342
+ "StrictHostKeyChecking=no",
343
+ "-o",
344
+ "ConnectTimeout=10",
345
+ "-o",
346
+ "BatchMode=yes",
334
347
  `root@${ip}`,
335
- 'cloud-init status',
336
- ], { stdio: ['ignore', 'pipe', 'ignore'] });
337
- let out = '';
338
- proc.stdout.on('data', (d) => out += d.toString());
339
- proc.on('close', () => resolve(out.includes('done') || out.includes('error')));
340
- proc.on('error', () => resolve(false));
348
+ "cloud-init status",
349
+ ], { stdio: ["ignore", "pipe", "ignore"] });
350
+ let out = "";
351
+ proc.stdout.on("data", (d) => (out += d.toString()));
352
+ proc.on("close", () => resolve(out.includes("done") || out.includes("error")));
353
+ proc.on("error", () => resolve(false));
341
354
  });
342
355
  }
343
356
  checkPort(ip, port) {
344
- return new Promise(resolve => {
357
+ return new Promise((resolve) => {
345
358
  const socket = createConnection({ host: ip, port, timeout: 3_000 });
346
- socket.on('connect', () => { socket.destroy(); resolve(true); });
347
- socket.on('timeout', () => { socket.destroy(); resolve(false); });
348
- socket.on('error', () => resolve(false));
359
+ socket.on("connect", () => {
360
+ socket.destroy();
361
+ resolve(true);
362
+ });
363
+ socket.on("timeout", () => {
364
+ socket.destroy();
365
+ resolve(false);
366
+ });
367
+ socket.on("error", () => resolve(false));
349
368
  });
350
369
  }
351
370
  sshKeyPath() {
package/dist/types/aws.js CHANGED
@@ -1,4 +1,4 @@
1
- // Define your secret IDs here — reference them everywhere instead of raw strings.
1
+ // Define your secret IDs here - reference them everywhere instead of raw strings.
2
2
  // Example:
3
3
  // export const SECRETS = {
4
4
  // API_KEY: "myapp/api-key",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puls-dev",
3
- "version": "0.1.0",
3
+ "version": "0.1.8",
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",
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "build": "tsc",
16
16
  "prepublishOnly": "npm run build",
17
- "test": "echo \"Error: no test specified\" && exit 1"
17
+ "test": "tsx --test src/**/*.test.ts"
18
18
  },
19
19
  "keywords": [
20
20
  "iac",
@@ -50,6 +50,7 @@
50
50
  "@aws-sdk/client-sqs": "^3.1045.0",
51
51
  "dotenv": "^17.4.2",
52
52
  "google-auth-library": "^10.6.2",
53
+ "mkdocs": "^0.0.1",
53
54
  "reflect-metadata": "^0.2.2",
54
55
  "undici": "^8.2.0"
55
56
  }