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.
- package/README.md +10 -8
- package/dist/core/checker.d.ts +1 -1
- package/dist/core/checker.js +88 -56
- package/dist/core/config.test.d.ts +1 -0
- package/dist/core/config.test.js +21 -0
- package/dist/core/decorators.js +8 -2
- package/dist/core/output.test.d.ts +1 -0
- package/dist/core/output.test.js +18 -0
- 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 +1 -1
- package/dist/providers/firebase/hosting.js +92 -52
- 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.d.ts +1 -1
- package/dist/providers/proxmox/vm.js +43 -24
- package/dist/types/aws.js +1 -1
- package/package.json +3 -2
|
@@ -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
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
187
|
+
// Not in DNS - will fall through to DHCP
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
}
|
|
187
|
-
// Build net0 string
|
|
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
|
-
|
|
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
|
|
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(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
336
|
-
], { stdio: [
|
|
337
|
-
let out =
|
|
338
|
-
proc.stdout.on(
|
|
339
|
-
proc.on(
|
|
340
|
-
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));
|
|
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(
|
|
347
|
-
|
|
348
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puls-dev",
|
|
3
|
-
"version": "0.1.
|
|
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": "
|
|
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
|
}
|