puls-dev 0.1.7 ā 0.1.9
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.
- package/README.md +10 -8
- package/dist/core/checker.d.ts +1 -1
- package/dist/core/checker.js +88 -56
- package/dist/core/decorators.js +8 -2
- package/dist/core/resource.js +2 -2
- package/dist/core/stack.d.ts +1 -1
- package/dist/core/stack.js +2 -2
- package/dist/providers/aws/acm.d.ts +1 -1
- package/dist/providers/aws/acm.js +27 -23
- package/dist/providers/aws/api.d.ts +14 -14
- package/dist/providers/aws/api.js +21 -21
- package/dist/providers/aws/apigateway.d.ts +2 -2
- package/dist/providers/aws/apigateway.js +33 -29
- package/dist/providers/aws/cloudfront.d.ts +3 -3
- package/dist/providers/aws/cloudfront.js +49 -34
- package/dist/providers/aws/fargate.d.ts +2 -2
- package/dist/providers/aws/fargate.js +99 -52
- package/dist/providers/aws/lambda.d.ts +2 -2
- package/dist/providers/aws/lambda.js +63 -32
- package/dist/providers/aws/rds.d.ts +1 -1
- package/dist/providers/aws/rds.js +77 -39
- package/dist/providers/aws/route53.d.ts +5 -5
- package/dist/providers/aws/route53.js +42 -35
- package/dist/providers/aws/s3.d.ts +2 -2
- package/dist/providers/aws/s3.js +40 -33
- package/dist/providers/aws/secrets.js +15 -7
- package/dist/providers/aws/sqs.d.ts +1 -1
- package/dist/providers/aws/sqs.js +47 -23
- package/dist/providers/do/domain.d.ts +4 -4
- package/dist/providers/do/domain.js +15 -11
- package/dist/providers/firebase/auth.d.ts +1 -1
- package/dist/providers/firebase/auth.js +65 -33
- package/dist/providers/firebase/firestore.d.ts +2 -2
- package/dist/providers/firebase/firestore.js +45 -28
- package/dist/providers/firebase/functions.d.ts +1 -1
- package/dist/providers/firebase/functions.js +75 -42
- package/dist/providers/firebase/hosting.d.ts +3 -1
- package/dist/providers/firebase/hosting.js +102 -56
- package/dist/providers/firebase/remoteconfig.d.ts +1 -1
- package/dist/providers/firebase/remoteconfig.js +42 -33
- package/dist/providers/firebase/storage.d.ts +1 -1
- package/dist/providers/firebase/storage.js +38 -24
- package/dist/providers/proxmox/vm.js +34 -22
- package/dist/types/aws.js +1 -1
- package/package.json +2 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { BaseBuilder } from
|
|
3
|
-
import { cloudFetch, getProjectId } from
|
|
4
|
-
const RULES_BASE =
|
|
5
|
-
const GCS_BASE =
|
|
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 ??
|
|
13
|
+
super(bucket ?? "__default__");
|
|
14
14
|
this._resolvedBucket = bucket;
|
|
15
15
|
this.discoveryPromise = Promise.resolve(null);
|
|
16
16
|
}
|
|
17
|
-
rules(filePath) {
|
|
18
|
-
|
|
19
|
-
|
|
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,
|
|
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:
|
|
37
|
-
body: JSON.stringify({
|
|
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:
|
|
41
|
-
body: JSON.stringify({
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -118,7 +118,7 @@ export class VMBuilder extends BaseBuilder {
|
|
|
118
118
|
this.out.ip.resolve(this._ip?.split("/")[0] ?? "0.0.0.0");
|
|
119
119
|
return { name: this.name, vmid: "PENDING" };
|
|
120
120
|
}
|
|
121
|
-
// Find the template
|
|
121
|
+
// Find the template - match by VMID (numeric string) or name substring
|
|
122
122
|
const resources = await pm.get("/cluster/resources?type=vm");
|
|
123
123
|
const isVmid = this._image && /^\d+$/.test(this._image);
|
|
124
124
|
const template = this._image
|
|
@@ -156,7 +156,7 @@ export class VMBuilder extends BaseBuilder {
|
|
|
156
156
|
storage,
|
|
157
157
|
format: "raw",
|
|
158
158
|
});
|
|
159
|
-
// Clone is async
|
|
159
|
+
// Clone is async - wait for the Proxmox task to finish before configuring
|
|
160
160
|
await this.waitForTask(node, taskId, pm);
|
|
161
161
|
}
|
|
162
162
|
else {
|
|
@@ -184,11 +184,11 @@ export class VMBuilder extends BaseBuilder {
|
|
|
184
184
|
console.log(` š DNS: ${this.name}.${domain} ā ${addr}`);
|
|
185
185
|
}
|
|
186
186
|
catch {
|
|
187
|
-
// Not in DNS
|
|
187
|
+
// Not in DNS - will fall through to DHCP
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
-
// Build net0 string
|
|
191
|
+
// Build net0 string - VirtIO on vmbr1, optional VLAN tag
|
|
192
192
|
const net0 = `virtio,bridge=vmbr1${this._vlan ? `,tag=${this._vlan}` : ""}`;
|
|
193
193
|
const configPatch = {
|
|
194
194
|
onboot: 1,
|
|
@@ -244,7 +244,9 @@ export class VMBuilder extends BaseBuilder {
|
|
|
244
244
|
if (this._provision) {
|
|
245
245
|
await this.waitFor(`SSH on ${this.resolvedIp} to be ready`, () => this.checkPort(this.resolvedIp, 22), { intervalMs: 10_000, timeoutMs: 300_000 });
|
|
246
246
|
await this.waitFor(`cloud-init to finish on ${this.resolvedIp}`, () => this.checkCloudInit(this.resolvedIp), { intervalMs: 15_000, timeoutMs: 300_000 });
|
|
247
|
-
const scripts = Array.isArray(this._provision)
|
|
247
|
+
const scripts = Array.isArray(this._provision)
|
|
248
|
+
? this._provision
|
|
249
|
+
: [this._provision];
|
|
248
250
|
for (const script of scripts) {
|
|
249
251
|
await this.runProvisioner(this.resolvedIp, script);
|
|
250
252
|
}
|
|
@@ -287,7 +289,7 @@ export class VMBuilder extends BaseBuilder {
|
|
|
287
289
|
const resources = await pm.get("/cluster/resources?type=vm");
|
|
288
290
|
const vm = (resources ?? []).find((r) => r.name === name && !r.template);
|
|
289
291
|
if (!vm) {
|
|
290
|
-
console.log(` ā¹ļø VM "${name}" not found
|
|
292
|
+
console.log(` ā¹ļø VM "${name}" not found - already gone`);
|
|
291
293
|
return;
|
|
292
294
|
}
|
|
293
295
|
if (vm.status === "running") {
|
|
@@ -332,27 +334,37 @@ export class VMBuilder extends BaseBuilder {
|
|
|
332
334
|
}
|
|
333
335
|
checkCloudInit(ip) {
|
|
334
336
|
const keyPath = this.sshKeyPath();
|
|
335
|
-
return new Promise(resolve => {
|
|
336
|
-
const proc = spawn(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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",
|
|
341
347
|
`root@${ip}`,
|
|
342
|
-
|
|
343
|
-
], { stdio: [
|
|
344
|
-
let out =
|
|
345
|
-
proc.stdout.on(
|
|
346
|
-
proc.on(
|
|
347
|
-
proc.on(
|
|
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));
|
|
348
354
|
});
|
|
349
355
|
}
|
|
350
356
|
checkPort(ip, port) {
|
|
351
|
-
return new Promise(resolve => {
|
|
357
|
+
return new Promise((resolve) => {
|
|
352
358
|
const socket = createConnection({ host: ip, port, timeout: 3_000 });
|
|
353
|
-
socket.on(
|
|
354
|
-
|
|
355
|
-
|
|
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));
|
|
356
368
|
});
|
|
357
369
|
}
|
|
358
370
|
sshKeyPath() {
|
package/dist/types/aws.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puls-dev",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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",
|
|
@@ -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
|
}
|