relight-cli 0.1.0 → 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 (45) hide show
  1. package/README.md +77 -34
  2. package/package.json +12 -4
  3. package/src/cli.js +350 -1
  4. package/src/commands/apps.js +128 -0
  5. package/src/commands/auth.js +13 -4
  6. package/src/commands/config.js +282 -0
  7. package/src/commands/cost.js +593 -0
  8. package/src/commands/db.js +775 -0
  9. package/src/commands/deploy.js +264 -0
  10. package/src/commands/doctor.js +69 -13
  11. package/src/commands/domains.js +223 -0
  12. package/src/commands/logs.js +111 -0
  13. package/src/commands/open.js +42 -0
  14. package/src/commands/ps.js +121 -0
  15. package/src/commands/scale.js +132 -0
  16. package/src/commands/service.js +227 -0
  17. package/src/lib/clouds/aws.js +309 -35
  18. package/src/lib/clouds/cf.js +401 -2
  19. package/src/lib/clouds/gcp.js +255 -4
  20. package/src/lib/clouds/neon.js +147 -0
  21. package/src/lib/clouds/slicervm.js +139 -0
  22. package/src/lib/config.js +200 -2
  23. package/src/lib/docker.js +34 -0
  24. package/src/lib/link.js +31 -5
  25. package/src/lib/providers/aws/app.js +481 -0
  26. package/src/lib/providers/aws/db.js +504 -0
  27. package/src/lib/providers/aws/dns.js +232 -0
  28. package/src/lib/providers/aws/registry.js +59 -0
  29. package/src/lib/providers/cf/app.js +596 -0
  30. package/src/lib/providers/cf/bundle.js +70 -0
  31. package/src/lib/providers/cf/db.js +181 -0
  32. package/src/lib/providers/cf/dns.js +148 -0
  33. package/src/lib/providers/cf/registry.js +17 -0
  34. package/src/lib/providers/gcp/app.js +429 -0
  35. package/src/lib/providers/gcp/db.js +372 -0
  36. package/src/lib/providers/gcp/dns.js +166 -0
  37. package/src/lib/providers/gcp/registry.js +30 -0
  38. package/src/lib/providers/neon/db.js +306 -0
  39. package/src/lib/providers/resolve.js +79 -0
  40. package/src/lib/providers/slicervm/app.js +396 -0
  41. package/src/lib/providers/slicervm/db.js +33 -0
  42. package/src/lib/providers/slicervm/dns.js +58 -0
  43. package/src/lib/providers/slicervm/registry.js +7 -0
  44. package/worker-template/package.json +10 -0
  45. package/worker-template/src/index.js +260 -0
@@ -1,11 +1,17 @@
1
1
  import { createSign } from "crypto";
2
2
  import { readFileSync } from "fs";
3
3
 
4
- var RUN_API = "https://run.googleapis.com/v2";
4
+ export var RUN_API = "https://run.googleapis.com/v2";
5
5
  var CRM_API = "https://cloudresourcemanager.googleapis.com/v1";
6
6
  var TOKEN_URI = "https://oauth2.googleapis.com/token";
7
7
  var SCOPE = "https://www.googleapis.com/auth/cloud-platform";
8
8
 
9
+ export var AR_API = "https://artifactregistry.googleapis.com/v1";
10
+ export var SQLADMIN_API = "https://sqladmin.googleapis.com/v1";
11
+ export var DNS_API = "https://dns.googleapis.com/dns/v1";
12
+ export var LOGGING_API = "https://logging.googleapis.com/v2";
13
+ export var MONITORING_API = "https://monitoring.googleapis.com/v3";
14
+
9
15
  // --- Service account key file ---
10
16
 
11
17
  export function readKeyFile(path) {
@@ -25,7 +31,7 @@ export function readKeyFile(path) {
25
31
  };
26
32
  }
27
33
 
28
- // --- JWT access token ---
34
+ // --- JWT -> access token ---
29
35
 
30
36
  export async function mintAccessToken(clientEmail, privateKey) {
31
37
  var now = Math.floor(Date.now() / 1000);
@@ -53,7 +59,7 @@ export async function mintAccessToken(clientEmail, privateKey) {
53
59
  var res = await fetch(TOKEN_URI, {
54
60
  method: "POST",
55
61
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
56
- body: `grant_type=${encodeURIComponent("urn:ietf:params:oauth:grant_type:jwt-bearer")}&assertion=${encodeURIComponent(jwt)}`,
62
+ body: `grant_type=${encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer")}&assertion=${encodeURIComponent(jwt)}`,
57
63
  });
58
64
 
59
65
  if (!res.ok) {
@@ -95,10 +101,51 @@ export async function gcpApi(method, url, body, token) {
95
101
  return res.json();
96
102
  }
97
103
 
104
+ // --- LRO polling ---
105
+
106
+ export async function waitForOperation(token, operationName, apiBase) {
107
+ var url = apiBase
108
+ ? `${apiBase}/${operationName}`
109
+ : `https://run.googleapis.com/v2/${operationName}`;
110
+ while (true) {
111
+ var op = await gcpApi("GET", url, null, token);
112
+ if (op.done) {
113
+ if (op.error) {
114
+ throw new Error(`Operation failed: ${op.error.message || JSON.stringify(op.error)}`);
115
+ }
116
+ return op.response || op;
117
+ }
118
+ await new Promise((r) => setTimeout(r, 2000));
119
+ }
120
+ }
121
+
122
+ export async function waitForSqlOperation(token, project, opName) {
123
+ while (true) {
124
+ var op = await gcpApi(
125
+ "GET",
126
+ `${SQLADMIN_API}/projects/${project}/operations/${opName}`,
127
+ null,
128
+ token
129
+ );
130
+ if (op.status === "DONE") {
131
+ if (op.error) {
132
+ var msgs = (op.error.errors || []).map((e) => e.message).join(", ");
133
+ throw new Error(`SQL operation failed: ${msgs || JSON.stringify(op.error)}`);
134
+ }
135
+ return op;
136
+ }
137
+ await new Promise((r) => setTimeout(r, 3000));
138
+ }
139
+ }
140
+
141
+ // --- Project verification ---
142
+
98
143
  export async function verifyProject(token, project) {
99
144
  return gcpApi("GET", `${CRM_API}/projects/${project}`, null, token);
100
145
  }
101
146
 
147
+ // --- Cloud Run ---
148
+
102
149
  export async function listRegions(token, project) {
103
150
  var res = await gcpApi(
104
151
  "GET",
@@ -117,4 +164,208 @@ export async function listServices(token, project, region) {
117
164
  token
118
165
  );
119
166
  return res.services || [];
120
- }
167
+ }
168
+
169
+ export async function listAllServices(token, project) {
170
+ var res = await gcpApi(
171
+ "GET",
172
+ `${RUN_API}/projects/${project}/locations/-/services`,
173
+ null,
174
+ token
175
+ );
176
+ return res.services || [];
177
+ }
178
+
179
+ export async function getService(token, serviceName) {
180
+ return gcpApi("GET", `${RUN_API}/${serviceName}`, null, token);
181
+ }
182
+
183
+ export async function createService(token, project, region, serviceId, body) {
184
+ var op = await gcpApi(
185
+ "POST",
186
+ `${RUN_API}/projects/${project}/locations/${region}/services?serviceId=${serviceId}`,
187
+ body,
188
+ token
189
+ );
190
+ return waitForOperation(token, op.name);
191
+ }
192
+
193
+ export async function updateService(token, serviceName, body) {
194
+ var op = await gcpApi("PATCH", `${RUN_API}/${serviceName}`, body, token);
195
+ return waitForOperation(token, op.name);
196
+ }
197
+
198
+ export async function deleteService(token, serviceName) {
199
+ var op = await gcpApi("DELETE", `${RUN_API}/${serviceName}`, null, token);
200
+ return waitForOperation(token, op.name);
201
+ }
202
+
203
+ export async function setIamPolicy(token, serviceName, policy) {
204
+ return gcpApi(
205
+ "POST",
206
+ `${RUN_API}/${serviceName}:setIamPolicy`,
207
+ { policy },
208
+ token
209
+ );
210
+ }
211
+
212
+ // --- Artifact Registry ---
213
+
214
+ export async function getRepository(token, project, location, repoName) {
215
+ return gcpApi(
216
+ "GET",
217
+ `${AR_API}/projects/${project}/locations/${location}/repositories/${repoName}`,
218
+ null,
219
+ token
220
+ );
221
+ }
222
+
223
+ export async function createRepository(token, project, location, repoName) {
224
+ var op = await gcpApi(
225
+ "POST",
226
+ `${AR_API}/projects/${project}/locations/${location}/repositories?repositoryId=${repoName}`,
227
+ { format: "DOCKER" },
228
+ token
229
+ );
230
+ if (op.done) return op.response || op;
231
+ return waitForOperation(token, op.name, AR_API);
232
+ }
233
+
234
+ // --- Cloud SQL ---
235
+
236
+ export async function createSqlInstance(token, project, body) {
237
+ var op = await gcpApi(
238
+ "POST",
239
+ `${SQLADMIN_API}/projects/${project}/instances`,
240
+ body,
241
+ token
242
+ );
243
+ return waitForSqlOperation(token, project, op.name);
244
+ }
245
+
246
+ export async function getSqlInstance(token, project, instanceName) {
247
+ return gcpApi(
248
+ "GET",
249
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}`,
250
+ null,
251
+ token
252
+ );
253
+ }
254
+
255
+ export async function deleteSqlInstance(token, project, instanceName) {
256
+ var op = await gcpApi(
257
+ "DELETE",
258
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}`,
259
+ null,
260
+ token
261
+ );
262
+ return waitForSqlOperation(token, project, op.name);
263
+ }
264
+
265
+ export async function createSqlDatabase(token, project, instanceName, dbName) {
266
+ var op = await gcpApi(
267
+ "POST",
268
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/databases`,
269
+ { name: dbName },
270
+ token
271
+ );
272
+ return waitForSqlOperation(token, project, op.name);
273
+ }
274
+
275
+ export async function deleteSqlDatabase(token, project, instanceName, dbName) {
276
+ var op = await gcpApi(
277
+ "DELETE",
278
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/databases/${dbName}`,
279
+ null,
280
+ token
281
+ );
282
+ return waitForSqlOperation(token, project, op.name);
283
+ }
284
+
285
+ export async function createSqlUser(token, project, instanceName, userName, password) {
286
+ var op = await gcpApi(
287
+ "POST",
288
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/users`,
289
+ { name: userName, password },
290
+ token
291
+ );
292
+ return waitForSqlOperation(token, project, op.name);
293
+ }
294
+
295
+ export async function updateSqlUser(token, project, instanceName, userName, password) {
296
+ var op = await gcpApi(
297
+ "PUT",
298
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/users?name=${encodeURIComponent(userName)}`,
299
+ { name: userName, password },
300
+ token
301
+ );
302
+ return waitForSqlOperation(token, project, op.name);
303
+ }
304
+
305
+ export async function deleteSqlUser(token, project, instanceName, userName) {
306
+ var op = await gcpApi(
307
+ "DELETE",
308
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/users?name=${encodeURIComponent(userName)}`,
309
+ null,
310
+ token
311
+ );
312
+ return waitForSqlOperation(token, project, op.name);
313
+ }
314
+
315
+ export async function listSqlDatabases(token, project, instanceName) {
316
+ var res = await gcpApi(
317
+ "GET",
318
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/databases`,
319
+ null,
320
+ token
321
+ );
322
+ return res.items || [];
323
+ }
324
+
325
+ // --- Cloud DNS ---
326
+
327
+ export async function listManagedZones(token, project) {
328
+ var res = await gcpApi(
329
+ "GET",
330
+ `${DNS_API}/projects/${project}/managedZones`,
331
+ null,
332
+ token
333
+ );
334
+ return res.managedZones || [];
335
+ }
336
+
337
+ export async function createDnsChange(token, project, zoneName, change) {
338
+ return gcpApi(
339
+ "POST",
340
+ `${DNS_API}/projects/${project}/managedZones/${zoneName}/changes`,
341
+ change,
342
+ token
343
+ );
344
+ }
345
+
346
+ export async function listResourceRecordSets(token, project, zoneName) {
347
+ var res = await gcpApi(
348
+ "GET",
349
+ `${DNS_API}/projects/${project}/managedZones/${zoneName}/rrsets`,
350
+ null,
351
+ token
352
+ );
353
+ return res.rrsets || [];
354
+ }
355
+
356
+ // --- Cloud Logging ---
357
+
358
+ export async function listLogEntries(token, body) {
359
+ return gcpApi("POST", `${LOGGING_API}/entries:list`, body, token);
360
+ }
361
+
362
+ // --- Cloud Monitoring ---
363
+
364
+ export async function queryTimeSeries(token, project, body) {
365
+ return gcpApi(
366
+ "POST",
367
+ `${MONITORING_API}/projects/${project}/timeSeries:query`,
368
+ body,
369
+ token
370
+ );
371
+ }
@@ -0,0 +1,147 @@
1
+ var API_BASE = "https://console.neon.tech/api/v2";
2
+
3
+ export async function neonApi(apiKey, method, path, body) {
4
+ var headers = {
5
+ Authorization: `Bearer ${apiKey}`,
6
+ Accept: "application/json",
7
+ };
8
+
9
+ if (body && typeof body === "object") {
10
+ headers["Content-Type"] = "application/json";
11
+ body = JSON.stringify(body);
12
+ }
13
+
14
+ var res = await fetch(`${API_BASE}${path}`, {
15
+ method,
16
+ headers,
17
+ body: method === "GET" || method === "DELETE" ? undefined : body,
18
+ });
19
+
20
+ if (!res.ok) {
21
+ var text = await res.text();
22
+ throw new Error(`Neon API ${method} ${path}: ${res.status} ${text}`);
23
+ }
24
+
25
+ if (res.status === 204) return null;
26
+ return res.json();
27
+ }
28
+
29
+ // --- Projects ---
30
+
31
+ export async function listProjects(apiKey) {
32
+ var data = await neonApi(apiKey, "GET", "/projects");
33
+ return data.projects || [];
34
+ }
35
+
36
+ export async function createProject(apiKey, opts = {}) {
37
+ var body = {
38
+ project: {
39
+ name: opts.name || "relight",
40
+ pg_version: opts.pgVersion || 16,
41
+ },
42
+ };
43
+ if (opts.regionId) body.project.region_id = opts.regionId;
44
+ return neonApi(apiKey, "POST", "/projects", body);
45
+ }
46
+
47
+ export async function getProject(apiKey, projectId) {
48
+ return neonApi(apiKey, "GET", `/projects/${projectId}`);
49
+ }
50
+
51
+ export async function deleteProject(apiKey, projectId) {
52
+ return neonApi(apiKey, "DELETE", `/projects/${projectId}`);
53
+ }
54
+
55
+ // --- Branches ---
56
+
57
+ export async function listBranches(apiKey, projectId) {
58
+ var data = await neonApi(apiKey, "GET", `/projects/${projectId}/branches`);
59
+ return data.branches || [];
60
+ }
61
+
62
+ // --- Databases ---
63
+
64
+ export async function listDatabases(apiKey, projectId, branchId) {
65
+ var data = await neonApi(
66
+ apiKey,
67
+ "GET",
68
+ `/projects/${projectId}/branches/${branchId}/databases`
69
+ );
70
+ return data.databases || [];
71
+ }
72
+
73
+ export async function createDatabase(apiKey, projectId, branchId, dbName, ownerName) {
74
+ return neonApi(
75
+ apiKey,
76
+ "POST",
77
+ `/projects/${projectId}/branches/${branchId}/databases`,
78
+ { database: { name: dbName, owner_name: ownerName } }
79
+ );
80
+ }
81
+
82
+ export async function deleteDatabase(apiKey, projectId, branchId, dbName) {
83
+ return neonApi(
84
+ apiKey,
85
+ "DELETE",
86
+ `/projects/${projectId}/branches/${branchId}/databases/${dbName}`
87
+ );
88
+ }
89
+
90
+ // --- Roles ---
91
+
92
+ export async function createRole(apiKey, projectId, branchId, roleName) {
93
+ return neonApi(
94
+ apiKey,
95
+ "POST",
96
+ `/projects/${projectId}/branches/${branchId}/roles`,
97
+ { role: { name: roleName } }
98
+ );
99
+ }
100
+
101
+ export async function deleteRole(apiKey, projectId, branchId, roleName) {
102
+ return neonApi(
103
+ apiKey,
104
+ "DELETE",
105
+ `/projects/${projectId}/branches/${branchId}/roles/${roleName}`
106
+ );
107
+ }
108
+
109
+ export async function getRolePassword(apiKey, projectId, branchId, roleName) {
110
+ var data = await neonApi(
111
+ apiKey,
112
+ "GET",
113
+ `/projects/${projectId}/branches/${branchId}/roles/${roleName}/reveal_password`
114
+ );
115
+ return data.password;
116
+ }
117
+
118
+ export async function resetRolePassword(apiKey, projectId, branchId, roleName) {
119
+ var data = await neonApi(
120
+ apiKey,
121
+ "POST",
122
+ `/projects/${projectId}/branches/${branchId}/roles/${roleName}/reset_password`
123
+ );
124
+ return data.password;
125
+ }
126
+
127
+ // --- Connection URI ---
128
+
129
+ export async function getConnectionUri(apiKey, projectId, dbName, roleName) {
130
+ var params = new URLSearchParams({
131
+ database_name: dbName,
132
+ role_name: roleName,
133
+ });
134
+ var data = await neonApi(
135
+ apiKey,
136
+ "GET",
137
+ `/projects/${projectId}/connection_uri?${params}`
138
+ );
139
+ return data.uri;
140
+ }
141
+
142
+ // --- Verification ---
143
+
144
+ export async function verifyApiKey(apiKey) {
145
+ var projects = await listProjects(apiKey);
146
+ return projects;
147
+ }
@@ -0,0 +1,139 @@
1
+ // SlicerVM API client
2
+ // Supports two connection modes:
3
+ // - Unix socket: cfg.socketPath (local dev, no auth)
4
+ // - HTTP: cfg.apiUrl + cfg.apiToken (remote, bearer token auth)
5
+
6
+ import http from "node:http";
7
+
8
+ function socketRequest(socketPath, method, path, body, headers) {
9
+ return new Promise((resolve, reject) => {
10
+ var opts = { socketPath, method, path, headers: headers || {} };
11
+ var req = http.request(opts, (res) => {
12
+ var chunks = [];
13
+ res.on("data", (c) => chunks.push(c));
14
+ res.on("end", () => {
15
+ resolve({
16
+ ok: res.statusCode >= 200 && res.statusCode < 300,
17
+ status: res.statusCode,
18
+ headers: res.headers,
19
+ text: () => Promise.resolve(Buffer.concat(chunks).toString()),
20
+ json: () => Promise.resolve(JSON.parse(Buffer.concat(chunks).toString())),
21
+ body: null,
22
+ });
23
+ });
24
+ });
25
+ req.on("error", reject);
26
+ if (body) req.write(body);
27
+ req.end();
28
+ });
29
+ }
30
+
31
+ export async function slicerFetch(cfg, method, path, body, opts = {}) {
32
+ var headers = {};
33
+
34
+ if (cfg.apiToken) {
35
+ headers.Authorization = `Bearer ${cfg.apiToken}`;
36
+ }
37
+
38
+ var rawBody = body;
39
+ if (opts.contentType) {
40
+ headers["Content-Type"] = opts.contentType;
41
+ } else if (body && typeof body === "object" && !Buffer.isBuffer(body)) {
42
+ headers["Content-Type"] = "application/json";
43
+ rawBody = JSON.stringify(body);
44
+ }
45
+
46
+ var res;
47
+ if (cfg.socketPath) {
48
+ res = await socketRequest(cfg.socketPath, method, path, method === "GET" ? undefined : rawBody, headers);
49
+ } else {
50
+ res = await fetch(`${cfg.apiUrl}${path}`, {
51
+ method,
52
+ headers,
53
+ body: method === "GET" ? undefined : rawBody,
54
+ });
55
+ }
56
+
57
+ if (!res.ok) {
58
+ var text = await res.text();
59
+ throw new Error(`Slicer API ${method} ${path}: ${res.status} ${text}`);
60
+ }
61
+
62
+ var ct = (res.headers instanceof Headers)
63
+ ? res.headers.get("content-type") || ""
64
+ : res.headers["content-type"] || "";
65
+
66
+ if (ct.includes("application/json")) {
67
+ return res.json();
68
+ }
69
+ if (opts.stream) {
70
+ return res;
71
+ }
72
+ return res.text();
73
+ }
74
+
75
+ // --- Nodes ---
76
+
77
+ export async function listNodes(cfg) {
78
+ return slicerFetch(cfg, "GET", "/nodes");
79
+ }
80
+
81
+ export async function createNode(cfg, hostGroup, opts = {}) {
82
+ return slicerFetch(cfg, "POST", `/hostgroup/${hostGroup}/nodes`, {
83
+ tags: opts.tags || [],
84
+ vcpu: opts.vcpu,
85
+ memory: opts.memory,
86
+ });
87
+ }
88
+
89
+ export async function deleteNode(cfg, hostGroup, hostname) {
90
+ return slicerFetch(cfg, "DELETE", `/hostgroup/${hostGroup}/nodes/${hostname}`);
91
+ }
92
+
93
+ // --- VM lifecycle ---
94
+
95
+ export async function resumeVM(cfg, hostname) {
96
+ return slicerFetch(cfg, "POST", `/vm/${hostname}/resume`);
97
+ }
98
+
99
+ export async function pauseVM(cfg, hostname) {
100
+ return slicerFetch(cfg, "POST", `/vm/${hostname}/pause`);
101
+ }
102
+
103
+ export async function healthCheck(cfg, hostname) {
104
+ return slicerFetch(cfg, "GET", `/vm/${hostname}/health`);
105
+ }
106
+
107
+ // --- Exec ---
108
+
109
+ export async function execInVM(cfg, hostname, cmd, args, opts = {}) {
110
+ var qs = new URLSearchParams();
111
+ qs.set("cmd", cmd);
112
+ for (var arg of (args || [])) {
113
+ qs.append("args", arg);
114
+ }
115
+ if (opts.uid !== undefined) qs.set("uid", String(opts.uid));
116
+ if (opts.gid !== undefined) qs.set("gid", String(opts.gid));
117
+ if (opts.workdir) qs.set("cwd", opts.workdir);
118
+ return slicerFetch(cfg, "POST", `/vm/${hostname}/exec?${qs}`, null, {
119
+ stream: opts.stream,
120
+ });
121
+ }
122
+
123
+ // --- File upload ---
124
+
125
+ export async function uploadToVM(cfg, hostname, path, tarBuffer, opts = {}) {
126
+ var qs = new URLSearchParams({ path });
127
+ if (opts.uid !== undefined) qs.set("uid", String(opts.uid));
128
+ if (opts.gid !== undefined) qs.set("gid", String(opts.gid));
129
+ if (opts.mode) qs.set("mode", opts.mode);
130
+ return slicerFetch(cfg, "POST", `/vm/${hostname}/cp?${qs}`, tarBuffer, {
131
+ contentType: "application/x-tar",
132
+ });
133
+ }
134
+
135
+ // --- Auth verification ---
136
+
137
+ export async function verifyConnection(cfg) {
138
+ return listNodes(cfg);
139
+ }