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
@@ -87,33 +87,35 @@ export async function awsApi(method, service, host, path, body, credentials, reg
87
87
  return res.text();
88
88
  }
89
89
 
90
- export async function verifyCredentials(credentials, region) {
91
- // Use STS GetCallerIdentity to verify credentials
92
- var host = "sts.amazonaws.com";
90
+ // --- JSON API (App Runner, ECR, CloudWatch Logs) ---
91
+
92
+ export async function awsJsonApi(target, body, service, credentials, region, host) {
93
+ if (!host) host = `${service}.${region}.amazonaws.com`;
94
+
93
95
  var now = new Date();
94
96
  var amzDate = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
95
97
  var dateStamp = amzDate.slice(0, 8);
96
98
 
97
- var queryParams = new URLSearchParams({
98
- Action: "GetCallerIdentity",
99
- Version: "2011-06-15",
100
- });
101
- var queryString = queryParams.toString();
99
+ var bodyStr = JSON.stringify(body || {});
100
+ var payloadHash = sha256(bodyStr);
102
101
 
103
- var payloadHash = sha256("");
104
- var canonicalHeaders = `host:${host}\nx-amz-date:${amzDate}\n`;
105
- var signedHeaders = "host;x-amz-date";
102
+ var canonicalHeaders =
103
+ `content-type:application/x-amz-json-1.0\n` +
104
+ `host:${host}\n` +
105
+ `x-amz-date:${amzDate}\n` +
106
+ `x-amz-target:${target}\n`;
107
+ var signedHeaders = "content-type;host;x-amz-date;x-amz-target";
106
108
 
107
109
  var canonicalRequest = [
108
- "GET",
110
+ "POST",
109
111
  "/",
110
- queryString,
112
+ "",
111
113
  canonicalHeaders,
112
114
  signedHeaders,
113
115
  payloadHash,
114
116
  ].join("\n");
115
117
 
116
- var credentialScope = `${dateStamp}/us-east-1/sts/aws4_request`;
118
+ var credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
117
119
  var stringToSign = [
118
120
  "AWS4-HMAC-SHA256",
119
121
  amzDate,
@@ -124,8 +126,8 @@ export async function verifyCredentials(credentials, region) {
124
126
  var signingKey = getSignatureKey(
125
127
  credentials.secretAccessKey,
126
128
  dateStamp,
127
- "us-east-1",
128
- "sts"
129
+ region,
130
+ service
129
131
  );
130
132
  var signature = createHmac("sha256", signingKey)
131
133
  .update(stringToSign)
@@ -137,38 +139,61 @@ export async function verifyCredentials(credentials, region) {
137
139
  `SignedHeaders=${signedHeaders}, ` +
138
140
  `Signature=${signature}`;
139
141
 
140
- var res = await fetch(`https://${host}/?${queryString}`, {
141
- method: "GET",
142
+ var res = await fetch(`https://${host}/`, {
143
+ method: "POST",
142
144
  headers: {
145
+ "Content-Type": "application/x-amz-json-1.0",
143
146
  "X-Amz-Date": amzDate,
147
+ "X-Amz-Target": target,
144
148
  Authorization: authHeader,
145
149
  },
150
+ body: bodyStr,
146
151
  });
147
152
 
148
153
  if (!res.ok) {
149
154
  var text = await res.text();
150
- throw new Error(`STS GetCallerIdentity failed: ${res.status} ${text}`);
155
+ throw new Error(`AWS ${service} ${target}: ${res.status} ${text}`);
151
156
  }
152
157
 
153
- return res.text();
158
+ return res.json();
154
159
  }
155
160
 
156
- export async function checkAppRunner(credentials, region) {
157
- var host = `apprunner.${region}.amazonaws.com`;
161
+ // --- Query API (RDS, EC2, IAM) ---
162
+
163
+ var QUERY_API_VERSIONS = {
164
+ rds: "2014-10-31",
165
+ ec2: "2016-11-15",
166
+ iam: "2010-05-08",
167
+ };
168
+
169
+ export async function awsQueryApi(action, params, service, credentials, region, opts) {
170
+ opts = opts || {};
171
+ var version = opts.version || QUERY_API_VERSIONS[service] || "2012-10-17";
172
+ var host = opts.host || `${service}.${region}.amazonaws.com`;
173
+
158
174
  var now = new Date();
159
175
  var amzDate = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
160
176
  var dateStamp = amzDate.slice(0, 8);
161
177
 
162
- var bodyStr = JSON.stringify({});
178
+ var formParams = new URLSearchParams();
179
+ formParams.set("Action", action);
180
+ formParams.set("Version", version);
181
+ if (params) {
182
+ for (var [k, v] of Object.entries(params)) {
183
+ formParams.set(k, v);
184
+ }
185
+ }
186
+ var bodyStr = formParams.toString();
163
187
  var payloadHash = sha256(bodyStr);
164
188
 
165
- var target = "AppRunner.ListServices";
166
189
  var canonicalHeaders =
167
- `content-type:application/x-amz-json-1.0\n` +
190
+ `content-type:application/x-www-form-urlencoded\n` +
168
191
  `host:${host}\n` +
169
- `x-amz-date:${amzDate}\n` +
170
- `x-amz-target:${target}\n`;
171
- var signedHeaders = "content-type;host;x-amz-date;x-amz-target";
192
+ `x-amz-date:${amzDate}\n`;
193
+ var signedHeaders = "content-type;host;x-amz-date";
194
+
195
+ // Use us-east-1 for global services like IAM
196
+ var signingRegion = service === "iam" ? "us-east-1" : region;
172
197
 
173
198
  var canonicalRequest = [
174
199
  "POST",
@@ -179,7 +204,7 @@ export async function checkAppRunner(credentials, region) {
179
204
  payloadHash,
180
205
  ].join("\n");
181
206
 
182
- var credentialScope = `${dateStamp}/${region}/apprunner/aws4_request`;
207
+ var credentialScope = `${dateStamp}/${signingRegion}/${service}/aws4_request`;
183
208
  var stringToSign = [
184
209
  "AWS4-HMAC-SHA256",
185
210
  amzDate,
@@ -190,8 +215,8 @@ export async function checkAppRunner(credentials, region) {
190
215
  var signingKey = getSignatureKey(
191
216
  credentials.secretAccessKey,
192
217
  dateStamp,
193
- region,
194
- "apprunner"
218
+ signingRegion,
219
+ service
195
220
  );
196
221
  var signature = createHmac("sha256", signingKey)
197
222
  .update(stringToSign)
@@ -206,9 +231,8 @@ export async function checkAppRunner(credentials, region) {
206
231
  var res = await fetch(`https://${host}/`, {
207
232
  method: "POST",
208
233
  headers: {
209
- "Content-Type": "application/x-amz-json-1.0",
234
+ "Content-Type": "application/x-www-form-urlencoded",
210
235
  "X-Amz-Date": amzDate,
211
- "X-Amz-Target": target,
212
236
  Authorization: authHeader,
213
237
  },
214
238
  body: bodyStr,
@@ -216,8 +240,258 @@ export async function checkAppRunner(credentials, region) {
216
240
 
217
241
  if (!res.ok) {
218
242
  var text = await res.text();
219
- throw new Error(`App Runner ListServices failed: ${res.status} ${text}`);
243
+ throw new Error(`AWS ${service} ${action}: ${res.status} ${text}`);
220
244
  }
221
245
 
222
- return res.json();
246
+ return res.text();
247
+ }
248
+
249
+ // --- REST XML API (Route 53) ---
250
+
251
+ export async function awsRestXmlApi(method, path, body, credentials) {
252
+ var host = "route53.amazonaws.com";
253
+ var signingRegion = "us-east-1";
254
+ var service = "route53";
255
+
256
+ var now = new Date();
257
+ var amzDate = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
258
+ var dateStamp = amzDate.slice(0, 8);
259
+
260
+ var bodyStr = body || "";
261
+ var payloadHash = sha256(bodyStr);
262
+
263
+ var canonicalHeaders;
264
+ var signedHeaders;
265
+ if (method === "GET") {
266
+ canonicalHeaders =
267
+ `host:${host}\n` +
268
+ `x-amz-date:${amzDate}\n`;
269
+ signedHeaders = "host;x-amz-date";
270
+ } else {
271
+ canonicalHeaders =
272
+ `content-type:application/xml\n` +
273
+ `host:${host}\n` +
274
+ `x-amz-date:${amzDate}\n`;
275
+ signedHeaders = "content-type;host;x-amz-date";
276
+ }
277
+
278
+ var canonicalRequest = [
279
+ method,
280
+ path,
281
+ "",
282
+ canonicalHeaders,
283
+ signedHeaders,
284
+ payloadHash,
285
+ ].join("\n");
286
+
287
+ var credentialScope = `${dateStamp}/${signingRegion}/${service}/aws4_request`;
288
+ var stringToSign = [
289
+ "AWS4-HMAC-SHA256",
290
+ amzDate,
291
+ credentialScope,
292
+ sha256(canonicalRequest),
293
+ ].join("\n");
294
+
295
+ var signingKey = getSignatureKey(
296
+ credentials.secretAccessKey,
297
+ dateStamp,
298
+ signingRegion,
299
+ service
300
+ );
301
+ var signature = createHmac("sha256", signingKey)
302
+ .update(stringToSign)
303
+ .digest("hex");
304
+
305
+ var authHeader =
306
+ `AWS4-HMAC-SHA256 ` +
307
+ `Credential=${credentials.accessKeyId}/${credentialScope}, ` +
308
+ `SignedHeaders=${signedHeaders}, ` +
309
+ `Signature=${signature}`;
310
+
311
+ var headers = {
312
+ "X-Amz-Date": amzDate,
313
+ Authorization: authHeader,
314
+ };
315
+ if (method !== "GET") {
316
+ headers["Content-Type"] = "application/xml";
317
+ }
318
+
319
+ var res = await fetch(`https://${host}${path}`, {
320
+ method,
321
+ headers,
322
+ body: method === "GET" ? undefined : bodyStr,
323
+ });
324
+
325
+ if (!res.ok) {
326
+ var text = await res.text();
327
+ throw new Error(`AWS Route 53 ${method} ${path}: ${res.status} ${text}`);
328
+ }
329
+
330
+ return res.text();
331
+ }
332
+
333
+ // --- XML helpers ---
334
+
335
+ export function xmlVal(xml, tag) {
336
+ var match = xml.match(new RegExp(`<${tag}>([^<]*)</${tag}>`));
337
+ return match ? match[1] : null;
338
+ }
339
+
340
+ export function xmlList(xml, tag) {
341
+ var results = [];
342
+ var re = new RegExp(`<${tag}>[\\s\\S]*?</${tag}>`, "g");
343
+ var match;
344
+ while ((match = re.exec(xml)) !== null) {
345
+ results.push(match[0]);
346
+ }
347
+ return results;
348
+ }
349
+
350
+ export function xmlBlock(xml, tag) {
351
+ var match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
352
+ return match ? match[1] : null;
353
+ }
354
+
355
+ // --- STS / Credential verification ---
356
+
357
+ export async function verifyCredentials(credentials, region) {
358
+ // Use STS GetCallerIdentity to verify credentials
359
+ var host = "sts.amazonaws.com";
360
+ var now = new Date();
361
+ var amzDate = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
362
+ var dateStamp = amzDate.slice(0, 8);
363
+
364
+ var queryParams = new URLSearchParams({
365
+ Action: "GetCallerIdentity",
366
+ Version: "2011-06-15",
367
+ });
368
+ var queryString = queryParams.toString();
369
+
370
+ var payloadHash = sha256("");
371
+ var canonicalHeaders = `host:${host}\nx-amz-date:${amzDate}\n`;
372
+ var signedHeaders = "host;x-amz-date";
373
+
374
+ var canonicalRequest = [
375
+ "GET",
376
+ "/",
377
+ queryString,
378
+ canonicalHeaders,
379
+ signedHeaders,
380
+ payloadHash,
381
+ ].join("\n");
382
+
383
+ var credentialScope = `${dateStamp}/us-east-1/sts/aws4_request`;
384
+ var stringToSign = [
385
+ "AWS4-HMAC-SHA256",
386
+ amzDate,
387
+ credentialScope,
388
+ sha256(canonicalRequest),
389
+ ].join("\n");
390
+
391
+ var signingKey = getSignatureKey(
392
+ credentials.secretAccessKey,
393
+ dateStamp,
394
+ "us-east-1",
395
+ "sts"
396
+ );
397
+ var signature = createHmac("sha256", signingKey)
398
+ .update(stringToSign)
399
+ .digest("hex");
400
+
401
+ var authHeader =
402
+ `AWS4-HMAC-SHA256 ` +
403
+ `Credential=${credentials.accessKeyId}/${credentialScope}, ` +
404
+ `SignedHeaders=${signedHeaders}, ` +
405
+ `Signature=${signature}`;
406
+
407
+ var res = await fetch(`https://${host}/?${queryString}`, {
408
+ method: "GET",
409
+ headers: {
410
+ "X-Amz-Date": amzDate,
411
+ Authorization: authHeader,
412
+ },
413
+ });
414
+
415
+ if (!res.ok) {
416
+ var text = await res.text();
417
+ throw new Error(`STS GetCallerIdentity failed: ${res.status} ${text}`);
418
+ }
419
+
420
+ return res.text();
421
+ }
422
+
423
+ // --- Account ID ---
424
+
425
+ export async function getAccountId(credentials, region) {
426
+ var xml = await verifyCredentials(credentials, region);
427
+ return xmlVal(xml, "Account");
428
+ }
429
+
430
+ // --- IAM: ensure ECR access role for App Runner ---
431
+
432
+ export async function ensureEcrAccessRole(credentials, region) {
433
+ var roleName = "relight-apprunner-ecr";
434
+ var cr = { accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey };
435
+
436
+ // Check if role exists
437
+ try {
438
+ var xml = await awsQueryApi("GetRole", { RoleName: roleName }, "iam", cr, region, {
439
+ host: "iam.amazonaws.com",
440
+ });
441
+ var arn = xmlVal(xml, "Arn");
442
+ if (arn) return arn;
443
+ } catch {
444
+ // Role doesn't exist, create it
445
+ }
446
+
447
+ // Trust policy for App Runner build service
448
+ var trustPolicy = JSON.stringify({
449
+ Version: "2012-10-17",
450
+ Statement: [
451
+ {
452
+ Effect: "Allow",
453
+ Principal: { Service: "build.apprunner.amazonaws.com" },
454
+ Action: "sts:AssumeRole",
455
+ },
456
+ ],
457
+ });
458
+
459
+ var createXml = await awsQueryApi(
460
+ "CreateRole",
461
+ {
462
+ RoleName: roleName,
463
+ AssumeRolePolicyDocument: trustPolicy,
464
+ Description: "Allows App Runner to pull images from ECR",
465
+ },
466
+ "iam",
467
+ cr,
468
+ region,
469
+ { host: "iam.amazonaws.com" }
470
+ );
471
+
472
+ var arn = xmlVal(createXml, "Arn");
473
+
474
+ // Attach ECR access policy
475
+ await awsQueryApi(
476
+ "AttachRolePolicy",
477
+ {
478
+ RoleName: roleName,
479
+ PolicyArn: "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess",
480
+ },
481
+ "iam",
482
+ cr,
483
+ region,
484
+ { host: "iam.amazonaws.com" }
485
+ );
486
+
487
+ // Wait a moment for IAM propagation
488
+ await new Promise((r) => setTimeout(r, 5000));
489
+
490
+ return arn;
491
+ }
492
+
493
+ // --- App Runner check (refactored to use awsJsonApi) ---
494
+
495
+ export async function checkAppRunner(credentials, region) {
496
+ return awsJsonApi("AppRunner.ListServices", {}, "apprunner", credentials, region);
223
497
  }