relight-cli 0.1.0 → 0.2.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.
- package/README.md +77 -34
- package/package.json +12 -4
- package/src/cli.js +305 -1
- package/src/commands/apps.js +128 -0
- package/src/commands/auth.js +75 -4
- package/src/commands/config.js +282 -0
- package/src/commands/cost.js +593 -0
- package/src/commands/db.js +531 -0
- package/src/commands/deploy.js +298 -0
- package/src/commands/doctor.js +41 -9
- package/src/commands/domains.js +223 -0
- package/src/commands/logs.js +111 -0
- package/src/commands/open.js +42 -0
- package/src/commands/ps.js +121 -0
- package/src/commands/scale.js +132 -0
- package/src/lib/clouds/aws.js +309 -35
- package/src/lib/clouds/cf.js +401 -2
- package/src/lib/clouds/gcp.js +234 -3
- package/src/lib/clouds/slicervm.js +139 -0
- package/src/lib/config.js +40 -0
- package/src/lib/docker.js +34 -0
- package/src/lib/link.js +20 -5
- package/src/lib/providers/aws/app.js +481 -0
- package/src/lib/providers/aws/db.js +513 -0
- package/src/lib/providers/aws/dns.js +232 -0
- package/src/lib/providers/aws/registry.js +59 -0
- package/src/lib/providers/cf/app.js +596 -0
- package/src/lib/providers/cf/bundle.js +70 -0
- package/src/lib/providers/cf/db.js +279 -0
- package/src/lib/providers/cf/dns.js +148 -0
- package/src/lib/providers/cf/registry.js +17 -0
- package/src/lib/providers/gcp/app.js +429 -0
- package/src/lib/providers/gcp/db.js +457 -0
- package/src/lib/providers/gcp/dns.js +166 -0
- package/src/lib/providers/gcp/registry.js +30 -0
- package/src/lib/providers/resolve.js +49 -0
- package/src/lib/providers/slicervm/app.js +396 -0
- package/src/lib/providers/slicervm/db.js +33 -0
- package/src/lib/providers/slicervm/dns.js +58 -0
- package/src/lib/providers/slicervm/registry.js +7 -0
- package/worker-template/package.json +10 -0
- package/worker-template/src/index.js +260 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { fatal, fmt } from "../lib/output.js";
|
|
2
|
+
import { resolveAppName } from "../lib/link.js";
|
|
3
|
+
import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
|
|
4
|
+
import kleur from "kleur";
|
|
5
|
+
|
|
6
|
+
export async function logs(name, options) {
|
|
7
|
+
name = resolveAppName(name);
|
|
8
|
+
var cloud = resolveCloudId(options.cloud);
|
|
9
|
+
var cfg = getCloudCfg(cloud);
|
|
10
|
+
var appProvider = await getProvider(cloud, "app");
|
|
11
|
+
|
|
12
|
+
process.stderr.write(
|
|
13
|
+
`Tailing logs for ${fmt.app(name)}... ${fmt.dim("(ctrl+c to stop)")}\n\n`
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
var tail;
|
|
17
|
+
try {
|
|
18
|
+
tail = await appProvider.streamLogs(cfg, name);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
fatal(
|
|
21
|
+
`Could not start log tail for ${fmt.app(name)}.`,
|
|
22
|
+
e.message
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Non-WebSocket path (GCP polling)
|
|
27
|
+
if (!tail.url) {
|
|
28
|
+
process.on("SIGINT", async function () {
|
|
29
|
+
process.stderr.write(`\n${fmt.dim("Stopping tail...")}\n`);
|
|
30
|
+
await tail.cleanup();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
});
|
|
33
|
+
// Keep process alive while polling happens in the background
|
|
34
|
+
await new Promise(() => {});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// WebSocket path (Cloudflare)
|
|
39
|
+
var ws = new WebSocket(tail.url, "trace-v1");
|
|
40
|
+
|
|
41
|
+
ws.addEventListener("open", function () {
|
|
42
|
+
ws.send(JSON.stringify({}));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
ws.addEventListener("message", async function (event) {
|
|
46
|
+
var raw = typeof event.data === "string" ? event.data : await event.data.text();
|
|
47
|
+
var data = JSON.parse(raw);
|
|
48
|
+
|
|
49
|
+
for (var evt of Array.isArray(data) ? data : [data]) {
|
|
50
|
+
var ts = evt.eventTimestamp
|
|
51
|
+
? new Date(evt.eventTimestamp).toISOString()
|
|
52
|
+
: new Date().toISOString();
|
|
53
|
+
var method = evt.event?.request?.method || "";
|
|
54
|
+
var url = evt.event?.request?.url || "";
|
|
55
|
+
var statusCode = evt.event?.response?.status || "";
|
|
56
|
+
var outcome = evt.outcome || "";
|
|
57
|
+
|
|
58
|
+
if (method) {
|
|
59
|
+
var statusColor =
|
|
60
|
+
statusCode >= 500
|
|
61
|
+
? kleur.red(statusCode)
|
|
62
|
+
: statusCode >= 400
|
|
63
|
+
? kleur.yellow(statusCode)
|
|
64
|
+
: kleur.green(statusCode);
|
|
65
|
+
console.log(
|
|
66
|
+
`${kleur.dim(ts)} ${kleur.bold(method)} ${url} ${statusColor} ${kleur.dim(`[${outcome}]`)}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (evt.logs) {
|
|
71
|
+
for (var log of evt.logs) {
|
|
72
|
+
var level = log.level || "log";
|
|
73
|
+
var msg = (log.message || []).join(" ");
|
|
74
|
+
var levelColor =
|
|
75
|
+
level === "error"
|
|
76
|
+
? kleur.red(`[${level}]`)
|
|
77
|
+
: level === "warn"
|
|
78
|
+
? kleur.yellow(`[${level}]`)
|
|
79
|
+
: kleur.dim(`[${level}]`);
|
|
80
|
+
console.log(`${kleur.dim(ts)} ${levelColor} ${msg}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (evt.exceptions) {
|
|
85
|
+
for (var ex of evt.exceptions) {
|
|
86
|
+
console.error(
|
|
87
|
+
`${kleur.dim(ts)} ${kleur.red("[exception]")} ${ex.name}: ${ex.message}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
ws.addEventListener("error", function (event) {
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`${kleur.red("WebSocket error:")} ${event.message || "connection failed"}\n`
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
ws.addEventListener("close", function () {
|
|
101
|
+
process.stderr.write(`\n${fmt.dim("Tail disconnected.")}\n`);
|
|
102
|
+
process.exit(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
process.on("SIGINT", async function () {
|
|
106
|
+
process.stderr.write(`\n${fmt.dim("Stopping tail...")}\n`);
|
|
107
|
+
ws.close();
|
|
108
|
+
await tail.cleanup();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { platform } from "os";
|
|
3
|
+
import { fatal, fmt } from "../lib/output.js";
|
|
4
|
+
import { resolveAppName } from "../lib/link.js";
|
|
5
|
+
import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
|
|
6
|
+
|
|
7
|
+
export async function open(name, options) {
|
|
8
|
+
name = resolveAppName(name);
|
|
9
|
+
var cloud = resolveCloudId(options.cloud);
|
|
10
|
+
var cfg = getCloudCfg(cloud);
|
|
11
|
+
var appProvider = await getProvider(cloud, "app");
|
|
12
|
+
|
|
13
|
+
var url = await appProvider.getAppUrl(cfg, name);
|
|
14
|
+
|
|
15
|
+
if (!url) {
|
|
16
|
+
fatal(
|
|
17
|
+
"Could not resolve app URL.",
|
|
18
|
+
"Ensure your app is deployed and has a URL configured."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
process.stderr.write(`Opening ${fmt.url(url)}...\n`);
|
|
23
|
+
|
|
24
|
+
var cmd;
|
|
25
|
+
switch (platform()) {
|
|
26
|
+
case "darwin":
|
|
27
|
+
cmd = "open";
|
|
28
|
+
break;
|
|
29
|
+
case "win32":
|
|
30
|
+
cmd = "start";
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
cmd = "xdg-open";
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
execSync(`${cmd} "${url}"`, { stdio: "ignore" });
|
|
39
|
+
} catch {
|
|
40
|
+
console.log(url);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { fatal, fmt, table } from "../lib/output.js";
|
|
2
|
+
import { resolveAppName } from "../lib/link.js";
|
|
3
|
+
import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
|
|
4
|
+
import kleur from "kleur";
|
|
5
|
+
|
|
6
|
+
export async function ps(name, options) {
|
|
7
|
+
name = resolveAppName(name);
|
|
8
|
+
var cloud = resolveCloudId(options.cloud);
|
|
9
|
+
var cfg = getCloudCfg(cloud);
|
|
10
|
+
var appProvider = await getProvider(cloud, "app");
|
|
11
|
+
|
|
12
|
+
var appConfig = await appProvider.getAppConfig(cfg, name);
|
|
13
|
+
|
|
14
|
+
if (!appConfig) {
|
|
15
|
+
fatal(
|
|
16
|
+
`App ${fmt.app(name)} not found.`,
|
|
17
|
+
`Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var instances = appConfig.instances || 2;
|
|
22
|
+
|
|
23
|
+
// Fetch live metrics
|
|
24
|
+
var metrics = await appProvider.getContainerStatus(cfg, name);
|
|
25
|
+
|
|
26
|
+
// Aggregate: group by region + durableObjectId, keep only active instances
|
|
27
|
+
var containers = [];
|
|
28
|
+
for (var row of metrics) {
|
|
29
|
+
var dim = row.dimensions;
|
|
30
|
+
if (!dim.active) continue;
|
|
31
|
+
var existing = containers.find(
|
|
32
|
+
(c) => c.region === dim.region && c.doId === dim.durableObjectId
|
|
33
|
+
);
|
|
34
|
+
if (existing) {
|
|
35
|
+
existing.cpuSamples++;
|
|
36
|
+
existing.cpuLoad += row.avg?.cpuLoad || 0;
|
|
37
|
+
existing.memory += row.avg?.memory || 0;
|
|
38
|
+
} else {
|
|
39
|
+
containers.push({
|
|
40
|
+
region: dim.region,
|
|
41
|
+
doId: dim.durableObjectId,
|
|
42
|
+
cpuLoad: row.avg?.cpuLoad || 0,
|
|
43
|
+
memory: row.avg?.memory || 0,
|
|
44
|
+
cpuSamples: 1,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (var c of containers) {
|
|
49
|
+
c.cpuLoad = c.cpuLoad / c.cpuSamples;
|
|
50
|
+
c.memory = c.memory / c.cpuSamples;
|
|
51
|
+
}
|
|
52
|
+
containers.sort((a, b) => a.region.localeCompare(b.region) || a.doId.localeCompare(b.doId));
|
|
53
|
+
|
|
54
|
+
if (options.json) {
|
|
55
|
+
console.log(
|
|
56
|
+
JSON.stringify(
|
|
57
|
+
{
|
|
58
|
+
name,
|
|
59
|
+
image: appConfig.image,
|
|
60
|
+
regions: appConfig.regions,
|
|
61
|
+
instances,
|
|
62
|
+
containers: containers.map((c) => ({
|
|
63
|
+
region: c.region,
|
|
64
|
+
id: c.doId,
|
|
65
|
+
cpu: +(c.cpuLoad * 100).toFixed(1),
|
|
66
|
+
memoryMiB: +(c.memory / 1024 / 1024).toFixed(0),
|
|
67
|
+
})),
|
|
68
|
+
},
|
|
69
|
+
null,
|
|
70
|
+
2
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var url = await appProvider.getAppUrl(cfg, name);
|
|
77
|
+
var customDomains = appConfig.domains || [];
|
|
78
|
+
|
|
79
|
+
console.log("");
|
|
80
|
+
console.log(`${fmt.bold("App:")} ${fmt.app(name)}`);
|
|
81
|
+
if (customDomains.length > 0) {
|
|
82
|
+
console.log(`${fmt.bold("URL:")} ${fmt.url(`https://${customDomains[0]}`)}`);
|
|
83
|
+
for (var d of customDomains.slice(1)) {
|
|
84
|
+
console.log(` ${fmt.url(`https://${d}`)}`);
|
|
85
|
+
}
|
|
86
|
+
if (url) {
|
|
87
|
+
console.log(` ${fmt.dim(url)}`);
|
|
88
|
+
}
|
|
89
|
+
} else if (url) {
|
|
90
|
+
console.log(`${fmt.bold("URL:")} ${fmt.url(url)}`);
|
|
91
|
+
}
|
|
92
|
+
console.log(
|
|
93
|
+
`${fmt.bold("Image:")} ${appConfig.image || fmt.dim("(not deployed)")}`
|
|
94
|
+
);
|
|
95
|
+
console.log(`${fmt.bold("Regions:")} ${appConfig.regions.join(", ")}`);
|
|
96
|
+
console.log(`${fmt.bold("Instances:")} ${instances} per region`);
|
|
97
|
+
|
|
98
|
+
if (appConfig.deployedAt) {
|
|
99
|
+
console.log(`${fmt.bold("Deployed:")} ${appConfig.deployedAt}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`\n${fmt.bold("Containers:")}`);
|
|
103
|
+
|
|
104
|
+
if (containers.length > 0) {
|
|
105
|
+
var headers = ["", "REGION", "ID", "CPU", "MEMORY"];
|
|
106
|
+
var rows = containers.map((c) => [
|
|
107
|
+
kleur.green("*"),
|
|
108
|
+
c.region,
|
|
109
|
+
c.doId.slice(0, 8),
|
|
110
|
+
(c.cpuLoad * 100).toFixed(1) + "%",
|
|
111
|
+
(c.memory / 1024 / 1024).toFixed(0) + " MiB",
|
|
112
|
+
]);
|
|
113
|
+
console.log(table(headers, rows));
|
|
114
|
+
} else {
|
|
115
|
+
console.log(fmt.dim(" No active containers (app may be sleeping)"));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(
|
|
119
|
+
`\n${fmt.dim("Metrics from the last 15 minutes. Expect some delay in reporting.")}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { success, fatal, fmt } from "../lib/output.js";
|
|
2
|
+
import { resolveAppName } from "../lib/link.js";
|
|
3
|
+
import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
|
|
4
|
+
|
|
5
|
+
export async function scale(name, options) {
|
|
6
|
+
name = resolveAppName(name);
|
|
7
|
+
var cloud = resolveCloudId(options.cloud);
|
|
8
|
+
var cfg = getCloudCfg(cloud);
|
|
9
|
+
var appProvider = await getProvider(cloud, "app");
|
|
10
|
+
|
|
11
|
+
var appConfig = await appProvider.getAppConfig(cfg, name);
|
|
12
|
+
|
|
13
|
+
if (!appConfig) {
|
|
14
|
+
fatal(
|
|
15
|
+
`App ${fmt.app(name)} not found.`,
|
|
16
|
+
`Run ${fmt.cmd(`relight deploy ${name} .`)} first.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var changed = false;
|
|
21
|
+
|
|
22
|
+
if (options.regions) {
|
|
23
|
+
var validRegions = appProvider.getRegions();
|
|
24
|
+
var validCodes = validRegions.map((r) => r.code);
|
|
25
|
+
var regions = options.regions.split(",").map((r) => r.trim().toLowerCase());
|
|
26
|
+
for (var r of regions) {
|
|
27
|
+
if (!validCodes.includes(r)) {
|
|
28
|
+
fatal(
|
|
29
|
+
`Invalid location hint '${r}'.`,
|
|
30
|
+
`Valid hints: ${validCodes.join(", ")}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
appConfig.regions = regions;
|
|
35
|
+
changed = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (options.instances) {
|
|
39
|
+
appConfig.instances = options.instances;
|
|
40
|
+
changed = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (options.instanceType) {
|
|
44
|
+
appConfig.instanceType = options.instanceType;
|
|
45
|
+
delete appConfig.vcpu;
|
|
46
|
+
delete appConfig.memory;
|
|
47
|
+
delete appConfig.disk;
|
|
48
|
+
changed = true;
|
|
49
|
+
}
|
|
50
|
+
if (options.vcpu) {
|
|
51
|
+
appConfig.vcpu = options.vcpu;
|
|
52
|
+
delete appConfig.instanceType;
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
if (options.memory) {
|
|
56
|
+
appConfig.memory = options.memory;
|
|
57
|
+
delete appConfig.instanceType;
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
if (options.disk) {
|
|
61
|
+
appConfig.disk = options.disk;
|
|
62
|
+
delete appConfig.instanceType;
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!changed) {
|
|
67
|
+
// Show current scale
|
|
68
|
+
if (options.json) {
|
|
69
|
+
console.log(
|
|
70
|
+
JSON.stringify(
|
|
71
|
+
{
|
|
72
|
+
regions: appConfig.regions,
|
|
73
|
+
instances: appConfig.instances,
|
|
74
|
+
instanceType: appConfig.instanceType,
|
|
75
|
+
vcpu: appConfig.vcpu,
|
|
76
|
+
memory: appConfig.memory,
|
|
77
|
+
disk: appConfig.disk,
|
|
78
|
+
},
|
|
79
|
+
null,
|
|
80
|
+
2
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`\n${fmt.bold("App:")} ${fmt.app(name)}`);
|
|
87
|
+
console.log(`${fmt.bold("Regions:")} ${appConfig.regions.join(", ")}`);
|
|
88
|
+
console.log(`${fmt.bold("Instances:")} ${appConfig.instances} per region`);
|
|
89
|
+
if (appConfig.vcpu || appConfig.memory || appConfig.disk) {
|
|
90
|
+
if (appConfig.vcpu) console.log(`${fmt.bold("vCPU:")} ${appConfig.vcpu}`);
|
|
91
|
+
if (appConfig.memory) console.log(`${fmt.bold("Memory:")} ${appConfig.memory} MiB`);
|
|
92
|
+
if (appConfig.disk) console.log(`${fmt.bold("Disk:")} ${appConfig.disk} MB`);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`${fmt.bold("Type:")} ${appConfig.instanceType || "lite"}`);
|
|
95
|
+
}
|
|
96
|
+
console.log(
|
|
97
|
+
`\n${fmt.dim("Geo-routing is automatic - requests route to the closest deployed region.")}`
|
|
98
|
+
);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await appProvider.scale(cfg, name, { appConfig });
|
|
103
|
+
|
|
104
|
+
if (options.json) {
|
|
105
|
+
console.log(
|
|
106
|
+
JSON.stringify(
|
|
107
|
+
{
|
|
108
|
+
regions: appConfig.regions,
|
|
109
|
+
instances: appConfig.instances,
|
|
110
|
+
instanceType: appConfig.instanceType,
|
|
111
|
+
vcpu: appConfig.vcpu,
|
|
112
|
+
memory: appConfig.memory,
|
|
113
|
+
disk: appConfig.disk,
|
|
114
|
+
},
|
|
115
|
+
null,
|
|
116
|
+
2
|
|
117
|
+
)
|
|
118
|
+
);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
success(`Scaled ${fmt.app(name)} (live).`);
|
|
123
|
+
process.stderr.write(` Regions: ${appConfig.regions.join(", ")}\n`);
|
|
124
|
+
process.stderr.write(` Instances: ${appConfig.instances}\n`);
|
|
125
|
+
if (appConfig.vcpu || appConfig.memory || appConfig.disk) {
|
|
126
|
+
if (appConfig.vcpu) process.stderr.write(` vCPU: ${appConfig.vcpu}\n`);
|
|
127
|
+
if (appConfig.memory) process.stderr.write(` Memory: ${appConfig.memory} MiB\n`);
|
|
128
|
+
if (appConfig.disk) process.stderr.write(` Disk: ${appConfig.disk} MB\n`);
|
|
129
|
+
} else {
|
|
130
|
+
process.stderr.write(` Type: ${appConfig.instanceType || "lite"}\n`);
|
|
131
|
+
}
|
|
132
|
+
}
|