puls-dev 0.3.3 → 0.3.5

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 (64) hide show
  1. package/dist/bin/install-shell.d.ts +2 -0
  2. package/dist/bin/install-shell.js +136 -0
  3. package/dist/bin/puls.d.ts +1 -0
  4. package/dist/bin/puls.js +145 -0
  5. package/dist/core/checker.js +74 -0
  6. package/dist/core/config.d.ts +3 -0
  7. package/dist/core/context.d.ts +1 -0
  8. package/dist/core/decorators.d.ts +1 -0
  9. package/dist/core/decorators.js +39 -5
  10. package/dist/core/output.js +8 -1
  11. package/dist/core/production.test.js +1 -0
  12. package/dist/core/resource.d.ts +35 -0
  13. package/dist/core/resource.js +57 -1
  14. package/dist/core/secret.d.ts +1 -0
  15. package/dist/core/secret.js +5 -0
  16. package/dist/core/stack.d.ts +11 -0
  17. package/dist/core/stack.js +141 -90
  18. package/dist/index.d.ts +2 -1
  19. package/dist/index.js +1 -1
  20. package/dist/providers/aws/api.js +3 -0
  21. package/dist/providers/aws/ec2.d.ts +5 -0
  22. package/dist/providers/aws/ec2.js +7 -0
  23. package/dist/providers/aws/lambda.d.ts +5 -0
  24. package/dist/providers/aws/lambda.js +24 -0
  25. package/dist/providers/aws/list.js +15 -3
  26. package/dist/providers/aws/rds.d.ts +9 -0
  27. package/dist/providers/aws/rds.js +19 -0
  28. package/dist/providers/do/database.d.ts +9 -0
  29. package/dist/providers/do/database.js +19 -0
  30. package/dist/providers/do/domain.js +1 -1
  31. package/dist/providers/do/droplet.d.ts +10 -0
  32. package/dist/providers/do/droplet.js +28 -3
  33. package/dist/providers/do/droplet.test.js +1 -1
  34. package/dist/providers/do/list.js +25 -2
  35. package/dist/providers/do/load_balancer.d.ts +5 -0
  36. package/dist/providers/do/load_balancer.js +7 -0
  37. package/dist/providers/do/vpc.d.ts +5 -0
  38. package/dist/providers/do/vpc.js +8 -0
  39. package/dist/providers/firebase/functions.d.ts +9 -0
  40. package/dist/providers/firebase/functions.js +28 -0
  41. package/dist/providers/firebase/list.js +34 -2
  42. package/dist/providers/gcp/api.js +6 -0
  43. package/dist/providers/gcp/cloudrun.d.ts +13 -0
  44. package/dist/providers/gcp/cloudrun.js +30 -0
  45. package/dist/providers/gcp/cloudsql.d.ts +9 -0
  46. package/dist/providers/gcp/cloudsql.js +20 -0
  47. package/dist/providers/gcp/list.js +12 -2
  48. package/dist/providers/gcp/template.d.ts +3 -0
  49. package/dist/providers/gcp/template.js +13 -1
  50. package/dist/providers/gcp/vm.d.ts +8 -0
  51. package/dist/providers/gcp/vm.js +22 -2
  52. package/dist/providers/proxmox/api.d.ts +1 -0
  53. package/dist/providers/proxmox/api.js +18 -3
  54. package/dist/providers/proxmox/base.d.ts +16 -0
  55. package/dist/providers/proxmox/base.js +121 -0
  56. package/dist/providers/proxmox/list.js +8 -1
  57. package/dist/providers/proxmox/template.d.ts +3 -10
  58. package/dist/providers/proxmox/template.js +51 -139
  59. package/dist/providers/proxmox/vm.d.ts +18 -10
  60. package/dist/providers/proxmox/vm.js +73 -152
  61. package/dist/types/diff.d.ts +17 -0
  62. package/dist/types/diff.js +1 -0
  63. package/dist/types/inventory.d.ts +65 -0
  64. package/package.json +7 -22
@@ -1,14 +1,10 @@
1
- import { readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { spawn } from "node:child_process";
4
- import { BaseBuilder } from "../../core/resource.js";
1
+ import { ProxmoxBaseBuilder } from "./base.js";
5
2
  import { Config } from "../../core/config.js";
6
3
  import { Output } from "../../core/output.js";
7
- import { getPMClient } from "./api.js";
4
+ import { getPMClient, withVmidAllocation } from "./api.js";
8
5
  import { getFileHash, parseProvisionMetadata, mergeProvisionMetadata } from "./hash.js";
9
- import { checkPort, runProvisioner } from "../../core/provisioner.js";
10
6
  import { resourceContextStorage } from "../../core/context.js";
11
- export class VMBuilder extends BaseBuilder {
7
+ export class VMBuilder extends ProxmoxBaseBuilder {
12
8
  out = {
13
9
  ip: new Output(),
14
10
  vmid: new Output(),
@@ -26,7 +22,7 @@ export class VMBuilder extends BaseBuilder {
26
22
  _storage;
27
23
  _vlan;
28
24
  _ip;
29
- _sshKeys;
25
+ _gateway;
30
26
  _machine = "q35";
31
27
  _forceConfigCheck = false;
32
28
  constructor(name) {
@@ -42,6 +38,9 @@ export class VMBuilder extends BaseBuilder {
42
38
  try {
43
39
  const config = await pm.get(`/nodes/${match.node}/qemu/${match.vmid}/config`);
44
40
  match.description = config.description ?? "";
41
+ match.cfgCores = config.cores;
42
+ match.cfgMemory = config.memory;
43
+ match.cfgMachine = config.machine ?? "q35";
45
44
  }
46
45
  catch (err) {
47
46
  console.warn(` ⚠️ Could not fetch VM config for ${match.vmid}: ${err.message}`);
@@ -97,8 +96,8 @@ export class VMBuilder extends BaseBuilder {
97
96
  this._ip = address;
98
97
  return this;
99
98
  }
100
- sshKey(keys) {
101
- this._sshKeys = Array.isArray(keys) ? [...keys] : keys;
99
+ gateway(gw) {
100
+ this._gateway = gw;
102
101
  return this;
103
102
  }
104
103
  machine(type) {
@@ -109,7 +108,30 @@ export class VMBuilder extends BaseBuilder {
109
108
  this._forceConfigCheck = true;
110
109
  return this;
111
110
  }
111
+ getDiff(existing) {
112
+ const diffs = [];
113
+ if (existing.cfgCores !== undefined && existing.cfgCores !== this._cores) {
114
+ diffs.push({ field: "cores", declared: this._cores, live: existing.cfgCores });
115
+ }
116
+ if (existing.cfgMemory !== undefined && existing.cfgMemory !== this._memory) {
117
+ diffs.push({ field: "memory", declared: `${this._memory} MB`, live: `${existing.cfgMemory} MB` });
118
+ }
119
+ if (existing.cfgMachine !== undefined && this._machine !== existing.cfgMachine) {
120
+ diffs.push({ field: "machine", declared: this._machine, live: existing.cfgMachine });
121
+ }
122
+ return diffs;
123
+ }
112
124
  async deploy() {
125
+ try {
126
+ return await this._deploy();
127
+ }
128
+ catch (err) {
129
+ this.out.vmid.reject(err);
130
+ this.out.ip.reject(err);
131
+ throw err;
132
+ }
133
+ }
134
+ async _deploy() {
113
135
  const dryRun = this.isDryRunActive();
114
136
  const existing = await this.discoveryPromise;
115
137
  const pm = getPMClient();
@@ -121,8 +143,8 @@ export class VMBuilder extends BaseBuilder {
121
143
  this.resolvedIp = await this.resolveExistingIp(existing.node, existing.vmid, pm);
122
144
  if (this.resolvedIp) {
123
145
  this.out.ip.resolve(this.resolvedIp);
146
+ this.registerHost();
124
147
  }
125
- this.registerHost();
126
148
  const activeIp = this.resolvedIp ?? "0.0.0.0";
127
149
  // 1. Calculate hashes and check if playbooks need to run
128
150
  const appliedHashes = parseProvisionMetadata(existing.description ?? "");
@@ -146,10 +168,10 @@ export class VMBuilder extends BaseBuilder {
146
168
  }
147
169
  }
148
170
  else {
149
- console.log(` 🔄 Running ${playbooksToRun.length} playbook changes → ${activeIp}`);
150
171
  if (activeIp === "0.0.0.0") {
151
172
  throw new Error(`Failed to resolve IP for existing VM "${this.name}" to run playbooks`);
152
173
  }
174
+ console.log(` 🔄 Running ${playbooksToRun.length} playbook changes → ${activeIp}`);
153
175
  // Wait for SSH
154
176
  await this.waitFor(`SSH on ${activeIp} to be ready`, () => this.checkPort(activeIp, 22), { intervalMs: 10_000, timeoutMs: 300_000 });
155
177
  // Execute each playbook
@@ -171,7 +193,7 @@ export class VMBuilder extends BaseBuilder {
171
193
  ip: activeIp,
172
194
  };
173
195
  }
174
- // No playbook changes!
196
+ // No playbook changes
175
197
  console.log(`\n🖥️ Finalizing Proxmox VM "${this.name}"...`);
176
198
  console.log(` ✅ VM "${this.name}" already exists (vmid=${existing.vmid}, node=${existing.node}, status=${existing.status})`);
177
199
  console.log(` ✅ Configuration and playbooks are up to date.`);
@@ -225,34 +247,7 @@ export class VMBuilder extends BaseBuilder {
225
247
  : `Create a template whose name contains "${sourceVmid}".`));
226
248
  }
227
249
  // Resolve target node: explicit → cluster-aware (online & max free RAM) → configured nodes list → template's node → API discovery
228
- let node = this._node;
229
- if (!node) {
230
- try {
231
- const nodesList = await pm.get("/nodes");
232
- const configuredNodes = Config.get().providers.proxmox?.nodes;
233
- const onlineNodes = (nodesList ?? []).filter((n) => {
234
- if (n.status !== "online")
235
- return false;
236
- if (configuredNodes && configuredNodes.length > 0) {
237
- return configuredNodes.includes(n.node);
238
- }
239
- return true;
240
- });
241
- if (onlineNodes.length > 0) {
242
- // Sort descending by free memory (maxmem - mem)
243
- onlineNodes.sort((a, b) => {
244
- const freeA = (a.maxmem ?? 0) - (a.mem ?? 0);
245
- const freeB = (b.maxmem ?? 0) - (b.mem ?? 0);
246
- return freeB - freeA;
247
- });
248
- node = onlineNodes[0].node;
249
- 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)`);
250
- }
251
- }
252
- catch (err) {
253
- // Fallback silently to configured nodes list or discovery
254
- }
255
- }
250
+ let node = this._node ?? await this.selectBestNode(pm);
256
251
  if (!node) {
257
252
  const configuredNodes = Config.get().providers.proxmox?.nodes;
258
253
  node = configuredNodes?.[0] ?? template?.node;
@@ -263,31 +258,38 @@ export class VMBuilder extends BaseBuilder {
263
258
  }
264
259
  if (!node)
265
260
  throw new Error("No Proxmox nodes available");
266
- const newVmid = await pm.get("/cluster/nextid");
267
261
  const storage = this._storage ?? "rbd_pool";
268
- if (template) {
269
- console.log(` 📋 Cloning template "${template.name}" (vmid=${template.vmid}) "${this.name}" (vmid=${newVmid})`);
270
- const taskId = await pm.post(`/nodes/${template.node || node}/qemu/${template.vmid}/clone`, {
271
- newid: newVmid,
272
- name: this.name,
273
- full: 1,
274
- storage,
275
- format: "raw",
276
- target: node,
277
- });
278
- // Clone is async - wait for the Proxmox task to finish before configuring
279
- await this.waitForTask(template.node || node, taskId, pm);
280
- }
281
- else {
282
- console.log(` 🆕 Creating blank VM "${this.name}" (vmid=${newVmid})`);
283
- await pm.post(`/nodes/${node}/qemu`, {
284
- vmid: newVmid,
285
- name: this.name,
286
- cores: this._cores,
287
- memory: this._memory,
288
- net0: `virtio,bridge=vmbr1${this._vlan ? `,tag=${this._vlan}` : ""}`,
289
- ostype: "l26",
290
- });
262
+ // Allocate VMID and immediately issue the create/clone request while holding the lock
263
+ // to prevent parallel VMs from claiming the same VMID.
264
+ const { newVmid, cloneTaskId, cloneNode } = await withVmidAllocation(async () => {
265
+ const vmid = await pm.get("/cluster/nextid");
266
+ if (template) {
267
+ console.log(` 📋 Cloning template "${template.name}" (vmid=${template.vmid}) → "${this.name}" (vmid=${vmid})`);
268
+ const taskId = await pm.post(`/nodes/${template.node || node}/qemu/${template.vmid}/clone`, {
269
+ newid: vmid,
270
+ name: this.name,
271
+ full: 1,
272
+ storage,
273
+ format: "raw",
274
+ target: node,
275
+ });
276
+ return { newVmid: vmid, cloneTaskId: taskId, cloneNode: template.node || node };
277
+ }
278
+ else {
279
+ console.log(` 🆕 Creating blank VM "${this.name}" (vmid=${vmid})`);
280
+ await pm.post(`/nodes/${node}/qemu`, {
281
+ vmid,
282
+ name: this.name,
283
+ cores: this._cores,
284
+ memory: this._memory,
285
+ net0: `virtio,bridge=vmbr1${this._vlan ? `,tag=${this._vlan}` : ""}`,
286
+ ostype: "l26",
287
+ });
288
+ return { newVmid: vmid, cloneTaskId: null, cloneNode: null };
289
+ }
290
+ });
291
+ if (cloneTaskId && cloneNode) {
292
+ await this.waitForTask(cloneNode, cloneTaskId, pm);
291
293
  }
292
294
  this.resolvedVmid = newVmid;
293
295
  this.resolvedNode = node;
@@ -318,7 +320,7 @@ export class VMBuilder extends BaseBuilder {
318
320
  ipconfig0: this._ip
319
321
  ? (() => {
320
322
  const [addr, prefix = "24"] = this._ip.split("/");
321
- const gw = addr.split(".").slice(0, 3).join(".") + ".1";
323
+ const gw = this._gateway ?? (addr.split(".").slice(0, 3).join(".") + ".1");
322
324
  return `gw=${gw},ip=${addr}/${prefix}`;
323
325
  })()
324
326
  : "ip=dhcp",
@@ -356,8 +358,9 @@ export class VMBuilder extends BaseBuilder {
356
358
  return false;
357
359
  }
358
360
  }, { intervalMs: 10_000, timeoutMs: 300_000 });
359
- if (this.resolvedIp)
361
+ if (this.resolvedIp) {
360
362
  this.out.ip.resolve(this.resolvedIp);
363
+ }
361
364
  console.log(` 🌐 IP: ${this.resolvedIp}`);
362
365
  }
363
366
  this.registerHost();
@@ -413,13 +416,12 @@ export class VMBuilder extends BaseBuilder {
413
416
  }
414
417
  registerHost() {
415
418
  const context = resourceContextStorage.getStore();
416
- if (context && context.hosts) {
417
- const activeIp = this.resolvedIp ?? "0.0.0.0";
419
+ if (context && context.hosts && this.resolvedIp) {
418
420
  if (!context.hosts.some((h) => h.name === this.name)) {
419
421
  context.hosts.push({
420
422
  name: this.name,
421
- ip: activeIp,
422
- user: "root",
423
+ ip: this.resolvedIp,
424
+ user: this.resolveUser(),
423
425
  sshKey: this.sshKeyPath(),
424
426
  provider: "proxmox",
425
427
  });
@@ -442,19 +444,6 @@ export class VMBuilder extends BaseBuilder {
442
444
  await this.destroyVmByName(this.name, pm);
443
445
  return { destroyed: this.name };
444
446
  }
445
- // Poll a Proxmox task UPID until it exits, then throw if it failed
446
- async waitForTask(node, upid, pm) {
447
- const encoded = encodeURIComponent(upid);
448
- await this.waitFor(`clone task to complete`, async () => {
449
- const status = await pm.get(`/nodes/${node}/tasks/${encoded}/status`);
450
- if (status?.status !== "stopped")
451
- return false;
452
- if (status.exitstatus && status.exitstatus !== "OK") {
453
- throw new Error(`Clone task failed: ${status.exitstatus}`);
454
- }
455
- return true;
456
- }, { intervalMs: 5_000, timeoutMs: 300_000 });
457
- }
458
447
  async destroyVmByName(name, pm) {
459
448
  const resources = await pm.get("/cluster/resources?type=vm");
460
449
  const vm = (resources ?? []).find((r) => r.name === name && !r.template);
@@ -472,72 +461,4 @@ export class VMBuilder extends BaseBuilder {
472
461
  await pm.delete(`/nodes/${vm.node}/qemu/${vm.vmid}?purge=1&destroy-unreferenced-disks=1`);
473
462
  console.log(` 🗑️ Removed VM "${name}" (vmid=${vm.vmid})`);
474
463
  }
475
- resolvePublicKeys() {
476
- const input = this._sshKeys;
477
- if (!input) {
478
- // Default: read ~/.ssh/id_rsa.pub if it exists
479
- try {
480
- return [
481
- readFileSync(`${homedir()}/.ssh/id_ed25519.pub`, "utf-8").trim(),
482
- ];
483
- }
484
- catch {
485
- return [];
486
- }
487
- }
488
- if (Array.isArray(input))
489
- return input.map((k) => k.trim()).filter(Boolean);
490
- // Single string: key literal (starts with ssh-) or file path
491
- if (input.startsWith("ssh-") ||
492
- input.startsWith("ecdsa-") ||
493
- input.startsWith("sk-")) {
494
- return [input.trim()];
495
- }
496
- try {
497
- return [
498
- readFileSync(input.replace(/^~/, homedir()), "utf-8").trim(),
499
- ];
500
- }
501
- catch {
502
- return [];
503
- }
504
- }
505
- checkCloudInit(ip) {
506
- const keyPath = this.sshKeyPath();
507
- return new Promise((resolve) => {
508
- const proc = spawn("ssh", [
509
- "-i",
510
- keyPath,
511
- "-o",
512
- "StrictHostKeyChecking=no",
513
- "-o",
514
- "ConnectTimeout=10",
515
- "-o",
516
- "BatchMode=yes",
517
- `root@${ip}`,
518
- "cloud-init status",
519
- ], { stdio: ["ignore", "pipe", "ignore"] });
520
- let out = "";
521
- proc.stdout.on("data", (d) => (out += d.toString()));
522
- proc.on("close", () => resolve(out.includes("done") || out.includes("error")));
523
- proc.on("error", () => resolve(false));
524
- });
525
- }
526
- async checkPort(ip, port) {
527
- return checkPort(ip, port);
528
- }
529
- async runProvisioner(ip, script) {
530
- return runProvisioner(ip, "root", this._sshKeys, script);
531
- }
532
- sshKeyPath() {
533
- const keyInput = Array.isArray(this._sshKeys)
534
- ? null
535
- : this._sshKeys;
536
- return (keyInput &&
537
- !keyInput.startsWith("ssh-") &&
538
- !keyInput.startsWith("ecdsa-") &&
539
- !keyInput.startsWith("sk-")
540
- ? keyInput.replace(/\.pub$/, "")
541
- : `${homedir()}/.ssh/id_ed25519`).replace(/^~/, homedir());
542
- }
543
464
  }
@@ -0,0 +1,17 @@
1
+ export interface FieldDiff {
2
+ field: string;
3
+ declared: any;
4
+ live: any;
5
+ }
6
+ export type ResourceStatus = "missing" | "in-sync" | "drift" | "adopted";
7
+ export interface ResourceDiff {
8
+ prop: string;
9
+ resource: string;
10
+ status: ResourceStatus;
11
+ changes: FieldDiff[];
12
+ }
13
+ export interface StackDiff {
14
+ stackName: string;
15
+ resources: ResourceDiff[];
16
+ hasDrift: boolean;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -6,8 +6,14 @@ export interface ProxmoxVm {
6
6
  maxmem: number;
7
7
  maxdisk: number;
8
8
  }
9
+ export interface ProxmoxTemplate {
10
+ name: string;
11
+ vmid: number;
12
+ node: string;
13
+ }
9
14
  export interface ProxmoxInventory {
10
15
  vms: ProxmoxVm[];
16
+ templates: ProxmoxTemplate[];
11
17
  }
12
18
  export interface DoDroplet {
13
19
  id: number;
@@ -34,11 +40,34 @@ export interface DoDomain {
34
40
  name: string;
35
41
  ttl: number;
36
42
  }
43
+ export interface DoDatabase {
44
+ id: string;
45
+ name: string;
46
+ engine: string;
47
+ region: string;
48
+ status: string;
49
+ nodeCount: number;
50
+ }
51
+ export interface DoApp {
52
+ id: string;
53
+ name: string;
54
+ liveUrl: string;
55
+ status: string;
56
+ }
57
+ export interface DoVpc {
58
+ id: string;
59
+ name: string;
60
+ region: string;
61
+ ipRange: string;
62
+ }
37
63
  export interface DoInventory {
38
64
  droplets: DoDroplet[];
39
65
  firewalls: DoFirewall[];
40
66
  loadBalancers: DoLoadBalancer[];
41
67
  domains: DoDomain[];
68
+ databases: DoDatabase[];
69
+ apps: DoApp[];
70
+ vpcs: DoVpc[];
42
71
  totalMonthlyCost: number;
43
72
  }
44
73
  export interface AwsDistribution {
@@ -67,6 +96,13 @@ export interface AwsHostedZone {
67
96
  id: string;
68
97
  recordCount: number;
69
98
  }
99
+ export interface AwsEc2Instance {
100
+ id: string;
101
+ name: string;
102
+ type: string;
103
+ state: string;
104
+ publicIp?: string;
105
+ }
70
106
  export interface AwsInventory {
71
107
  region: string;
72
108
  distributions: AwsDistribution[];
@@ -74,6 +110,7 @@ export interface AwsInventory {
74
110
  lambdas: AwsLambdaFn[];
75
111
  rdsInstances: AwsRdsInstance[];
76
112
  hostedZones: AwsHostedZone[];
113
+ ec2Instances: AwsEc2Instance[];
77
114
  }
78
115
  export interface GcpVM {
79
116
  name: string;
@@ -97,11 +134,19 @@ export interface GcpCloudDNS {
97
134
  name: string;
98
135
  dnsName: string;
99
136
  }
137
+ export interface GcpPubSubTopic {
138
+ name: string;
139
+ }
140
+ export interface GcpSecret {
141
+ name: string;
142
+ }
100
143
  export interface GcpInventory {
101
144
  vms: GcpVM[];
102
145
  rdsInstances: GcpCloudSQL[];
103
146
  distributions: GcpCloudRun[];
104
147
  hostedZones: GcpCloudDNS[];
148
+ pubSubTopics: GcpPubSubTopic[];
149
+ secrets: GcpSecret[];
105
150
  }
106
151
  export interface FirebaseHosting {
107
152
  site: string;
@@ -112,9 +157,29 @@ export interface FirebaseFunction {
112
157
  entryPoint: string;
113
158
  runtime: string;
114
159
  }
160
+ export interface FirebaseFirestoreDb {
161
+ name: string;
162
+ type: string;
163
+ state: string;
164
+ }
165
+ export interface FirebaseStorageBucket {
166
+ name: string;
167
+ location: string;
168
+ }
169
+ export interface FirebaseAuthProvider {
170
+ providerId: string;
171
+ }
172
+ export interface FirebaseRemoteConfig {
173
+ parameterCount: number;
174
+ version: string;
175
+ }
115
176
  export interface FirebaseInventory {
116
177
  hostingSites: FirebaseHosting[];
117
178
  functions: FirebaseFunction[];
179
+ firestoreDbs: FirebaseFirestoreDb[];
180
+ storageBuckets: FirebaseStorageBucket[];
181
+ authProviders: FirebaseAuthProvider[];
182
+ remoteConfig?: FirebaseRemoteConfig;
118
183
  }
119
184
  export interface InventoryError {
120
185
  provider: "proxmox" | "do" | "aws" | "gcp" | "firebase";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puls-dev",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
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",
@@ -37,8 +37,12 @@
37
37
  "README.md",
38
38
  "LICENSE"
39
39
  ],
40
+ "bin": {
41
+ "puls": "dist/bin/puls.js"
42
+ },
40
43
  "scripts": {
41
44
  "build": "tsc",
45
+ "postbuild": "node -e \"const fs=require('fs'),f='dist/bin/puls.js',c=fs.readFileSync(f,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);\" && chmod +x dist/bin/puls.js",
42
46
  "prepublishOnly": "npm run build",
43
47
  "test": "tsx --test \"src/**/*.test.ts\""
44
48
  },
@@ -54,29 +58,10 @@
54
58
  "author": "Bia",
55
59
  "license": "ISC",
56
60
  "devDependencies": {
57
- "@aws-sdk/client-acm": "^3.1053.0",
58
- "@aws-sdk/client-apigatewayv2": "^3.1053.0",
59
- "@aws-sdk/client-cloudfront": "^3.1053.0",
60
- "@aws-sdk/client-cloudwatch": "^3.1053.0",
61
- "@aws-sdk/client-cloudwatch-logs": "^3.1053.0",
62
- "@aws-sdk/client-ec2": "^3.1053.0",
63
- "@aws-sdk/client-ecs": "^3.1053.0",
64
- "@aws-sdk/client-iam": "^3.1053.0",
65
- "@aws-sdk/client-lambda": "^3.1053.0",
66
- "@aws-sdk/client-rds": "^3.1053.0",
67
- "@aws-sdk/client-route-53": "^3.1053.0",
68
- "@aws-sdk/client-route-53-domains": "^3.1053.0",
69
- "@aws-sdk/client-s3": "^3.1053.0",
70
- "@aws-sdk/client-secrets-manager": "^3.1053.0",
71
- "@aws-sdk/client-sns": "^3.1053.0",
72
- "@aws-sdk/client-sqs": "^3.1053.0",
73
- "@aws-sdk/client-ssm": "^3.1053.0",
74
61
  "@types/node": "^25.6.2",
75
- "google-auth-library": "^10.6.2",
76
62
  "ts-node": "^10.9.2",
77
- "tsx": "^4.21.0",
78
- "typescript": "^6.0.3",
79
- "undici": "^8.3.0"
63
+ "tsx": "^4.22.4",
64
+ "typescript": "^6.0.3"
80
65
  },
81
66
  "dependencies": {
82
67
  "@aws-sdk/client-acm": "^3.1053.0",