@mybase-dev/cli 1.0.6 → 1.0.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/dist/mybase.js +371 -17
- package/package.json +1 -1
package/dist/mybase.js
CHANGED
|
@@ -116,6 +116,7 @@ var KEY_RESOURCES = [
|
|
|
116
116
|
{ id: "vector", name: "Vector search", description: "Indexes, documents, and queries (read), and changing them (write)" },
|
|
117
117
|
{ id: "users", name: "Users", description: "Your app's users and sessions (read), revoking and deleting them, and sending sign-in events (write)" },
|
|
118
118
|
{ id: "captcha", name: "Captcha", description: "Captcha settings (read), and verifying tokens (write)" },
|
|
119
|
+
{ id: "realtime", name: "Realtime", description: "Channels, members, and settings (read), and publishing messages (write)" },
|
|
119
120
|
{ id: "audit", name: "Audit log", description: "Who did what: sign-ins, key and project changes, secret reads (read only)" }
|
|
120
121
|
];
|
|
121
122
|
function parseDotenv(text) {
|
|
@@ -139,7 +140,7 @@ function parseDotenv(text) {
|
|
|
139
140
|
// package.json
|
|
140
141
|
var package_default = {
|
|
141
142
|
name: "@mybase-dev/cli",
|
|
142
|
-
version: "1.0.
|
|
143
|
+
version: "1.0.8",
|
|
143
144
|
description: "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
|
|
144
145
|
type: "module",
|
|
145
146
|
repository: {
|
|
@@ -652,6 +653,9 @@ function contextMarkdown(c) {
|
|
|
652
653
|
}
|
|
653
654
|
if (c.captcha)
|
|
654
655
|
out.push("## Captcha", "", `Script: ${c.captcha.scriptUrl} (data-project="${c.project}"). Check tokens with \`createCaptchaClient\` (\`captcha:write\` key) or Better Auth's captcha plugin via \`mybaseCaptcha()\`.`, "");
|
|
656
|
+
if (c.realtime) {
|
|
657
|
+
out.push("## Realtime", "", `Browsers: \`connectRealtime({ apiUrl, project: "${c.project}", token })\` (socket ${c.realtime.socketUrl}) with tokens your server signs via \`createRealtimeToken(MYBASE_REALTIME_SECRET, \u2026)\`. Publish from the server: POST ${c.realtime.publishUrl} (\`realtime:write\` key, \`createRealtimeClient\`).`, "");
|
|
658
|
+
}
|
|
655
659
|
if (c.analytics)
|
|
656
660
|
out.push("## Analytics", "", `Public write key \`${c.analytics.writeKey}\`, script ${c.analytics.scriptUrl}`, "");
|
|
657
661
|
out.push("## Env vars", "", ...c.env.map((e) => `- \`${e.name}\`: ${e.description}`), "");
|
|
@@ -2077,6 +2081,145 @@ async function sync4(file, dryRun) {
|
|
|
2077
2081
|
printSync(config.project, dryRun, await api(`${base6(config.project)}/rules`, { method: "PUT", body: JSON.stringify(body) }));
|
|
2078
2082
|
}
|
|
2079
2083
|
|
|
2084
|
+
// src/realtime.ts
|
|
2085
|
+
var realtimeUsage = ` realtime enable <project> Turn on realtime and print the socket URL and secret
|
|
2086
|
+
realtime info <project> Settings, connections, and messages in the last 24 hours
|
|
2087
|
+
realtime key <project> [--rotate] Show (or replace) the secret that signs browser tokens and webhooks
|
|
2088
|
+
(rotating closes every open connection)
|
|
2089
|
+
realtime settings <project> [--origins a,b|none] [--public on|off] [--webhook <url>|none]
|
|
2090
|
+
[--events message,presence,channel] [--max-connections n]
|
|
2091
|
+
[--max-channels n] [--max-message-bytes n] [--max-rate n]
|
|
2092
|
+
realtime channels <project> [--prefix p] Channels with subscribers right now
|
|
2093
|
+
realtime publish <project> <channel> <event> [json] Send a message to everyone subscribed
|
|
2094
|
+
realtime disable <project> [--yes] Turn it off (closes every connection)`;
|
|
2095
|
+
var base7 = (project) => `/projects/${encodeURIComponent(project)}/realtime`;
|
|
2096
|
+
function printSettings3(s) {
|
|
2097
|
+
console.log(` socket ${s.socketUrl} (connectRealtime in @mybase-dev/sdk/realtime)`);
|
|
2098
|
+
console.log(` publish POST ${s.publishUrl} (realtime:write key)`);
|
|
2099
|
+
console.log(` secret ${s.secret} (server-side only: MYBASE_REALTIME_SECRET)`);
|
|
2100
|
+
console.log(` public ${s.publicChannels ? "on: public:\u2026 channels need no token" : "off: every connection needs a token"}`);
|
|
2101
|
+
console.log(` origins ${s.allowedOrigins.length ? s.allowedOrigins.join(", ") : "any"}`);
|
|
2102
|
+
console.log(` webhook ${s.webhookUrl ? `${s.webhookUrl} (${s.webhookEvents.join(", ") || "no events"})` : "none"}`);
|
|
2103
|
+
console.log(` limits ${s.maxConnections} connections, ${s.maxChannels} channels each, ${s.maxMessageBytes} B frames, ${s.maxRate}/s`);
|
|
2104
|
+
}
|
|
2105
|
+
var list2 = (value) => value === "none" ? [] : value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
2106
|
+
function onOff(value, name) {
|
|
2107
|
+
if (value === undefined)
|
|
2108
|
+
return;
|
|
2109
|
+
if (value === "on" || value === "off")
|
|
2110
|
+
return value === "on";
|
|
2111
|
+
throw usageError(`--${name} takes on or off`);
|
|
2112
|
+
}
|
|
2113
|
+
async function realtime(sub, args) {
|
|
2114
|
+
const { positional, flags } = parseArgs(args);
|
|
2115
|
+
const [project, channel, event, data] = positional;
|
|
2116
|
+
switch (sub) {
|
|
2117
|
+
case "enable": {
|
|
2118
|
+
const p = need(project, "project name");
|
|
2119
|
+
const { linked } = await enableService(p, "realtime", flags);
|
|
2120
|
+
const s = await api(base7(p));
|
|
2121
|
+
print({ enabled: "realtime", project: p, linked, ...s }, () => {
|
|
2122
|
+
console.log(`Realtime enabled for "${p}"
|
|
2123
|
+
`);
|
|
2124
|
+
printSettings3(s);
|
|
2125
|
+
printLinked(p, linked, flags);
|
|
2126
|
+
});
|
|
2127
|
+
return true;
|
|
2128
|
+
}
|
|
2129
|
+
case "info": {
|
|
2130
|
+
const p = need(project, "project name");
|
|
2131
|
+
const [s, stats] = await Promise.all([api(base7(p)), api(`${base7(p)}/stats`)]);
|
|
2132
|
+
const { secret: _secret, ...settings } = s;
|
|
2133
|
+
print({ ...settings, stats }, () => {
|
|
2134
|
+
printSettings3({ ...s, secret: "(mybase realtime key)" });
|
|
2135
|
+
const out = stats.hours.reduce((n, h) => n + h.messagesOut, 0);
|
|
2136
|
+
console.log(`
|
|
2137
|
+
now ${stats.connections} connections (peak ${stats.peakConnections}), ${stats.channels} channels`);
|
|
2138
|
+
console.log(` 24 hours ${out} messages delivered`);
|
|
2139
|
+
if (stats.webhook.lastError)
|
|
2140
|
+
console.log(` webhook failing: ${stats.webhook.lastError} (${stats.webhook.queued} queued, ${stats.webhook.dropped} dropped)`);
|
|
2141
|
+
});
|
|
2142
|
+
return true;
|
|
2143
|
+
}
|
|
2144
|
+
case "key": {
|
|
2145
|
+
const p = need(project, "project name");
|
|
2146
|
+
if (flags.has("rotate"))
|
|
2147
|
+
confirm(args.includes("--yes"), `Every open realtime connection on "${p}" closes, and tokens signed with the old secret stop working.`, p, "the project name");
|
|
2148
|
+
const s = flags.has("rotate") ? await api(`${base7(p)}/secret/rotate`, { method: "POST" }) : await api(base7(p));
|
|
2149
|
+
print({ secret: s.secret }, () => {
|
|
2150
|
+
if (flags.has("rotate"))
|
|
2151
|
+
console.log(`Rotated. Update MYBASE_REALTIME_SECRET where your server signs tokens.
|
|
2152
|
+
`);
|
|
2153
|
+
console.log(s.secret);
|
|
2154
|
+
});
|
|
2155
|
+
return true;
|
|
2156
|
+
}
|
|
2157
|
+
case "settings": {
|
|
2158
|
+
const p = need(project, "project name");
|
|
2159
|
+
const body = {};
|
|
2160
|
+
const origins = flagString(flags, "origins");
|
|
2161
|
+
if (origins !== undefined)
|
|
2162
|
+
body.allowedOrigins = list2(origins);
|
|
2163
|
+
const pub = onOff(flagString(flags, "public"), "public");
|
|
2164
|
+
if (pub !== undefined)
|
|
2165
|
+
body.publicChannels = pub;
|
|
2166
|
+
const webhook = flagString(flags, "webhook");
|
|
2167
|
+
if (webhook !== undefined)
|
|
2168
|
+
body.webhookUrl = webhook === "none" ? null : webhook;
|
|
2169
|
+
const events = flagString(flags, "events");
|
|
2170
|
+
if (events !== undefined)
|
|
2171
|
+
body.webhookEvents = list2(events);
|
|
2172
|
+
const ints = { maxConnections: "max-connections", maxChannels: "max-channels", maxMessageBytes: "max-message-bytes", maxRate: "max-rate" };
|
|
2173
|
+
for (const [key, flag] of Object.entries(ints)) {
|
|
2174
|
+
const n = flagInt(flags, flag);
|
|
2175
|
+
if (n !== undefined)
|
|
2176
|
+
body[key] = n;
|
|
2177
|
+
}
|
|
2178
|
+
const s = Object.keys(body).length ? await api(base7(p), { method: "PATCH", body: JSON.stringify(body) }) : await api(base7(p));
|
|
2179
|
+
const { secret: _secret, ...shown } = s;
|
|
2180
|
+
print(shown, () => printSettings3({ ...s, secret: "(mybase realtime key)" }));
|
|
2181
|
+
return true;
|
|
2182
|
+
}
|
|
2183
|
+
case "channels": {
|
|
2184
|
+
const p = need(project, "project name");
|
|
2185
|
+
const prefix = flagString(flags, "prefix");
|
|
2186
|
+
const { channels } = await api(`${base7(p)}/channels${prefix ? `?prefix=${encodeURIComponent(prefix)}` : ""}`);
|
|
2187
|
+
print(channels, () => {
|
|
2188
|
+
if (!channels.length)
|
|
2189
|
+
return console.log("No channels have subscribers right now.");
|
|
2190
|
+
for (const c of channels)
|
|
2191
|
+
console.log(`${c.channel} ${c.subscribers} subscribers${c.members === undefined ? "" : `, ${c.members} members`}`);
|
|
2192
|
+
});
|
|
2193
|
+
return true;
|
|
2194
|
+
}
|
|
2195
|
+
case "publish": {
|
|
2196
|
+
const p = need(project, "project name");
|
|
2197
|
+
let payload = null;
|
|
2198
|
+
if (data !== undefined) {
|
|
2199
|
+
try {
|
|
2200
|
+
payload = JSON.parse(data);
|
|
2201
|
+
} catch {
|
|
2202
|
+
payload = data;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
const { ids } = await api(`${base7(p)}/publish`, {
|
|
2206
|
+
method: "POST",
|
|
2207
|
+
body: JSON.stringify({ channel: need(channel, "channel"), event: need(event, "event"), data: payload })
|
|
2208
|
+
});
|
|
2209
|
+
print({ id: ids[0] }, () => console.log(`Published ${event} to ${channel}`));
|
|
2210
|
+
return true;
|
|
2211
|
+
}
|
|
2212
|
+
case "disable": {
|
|
2213
|
+
const p = need(project, "project name");
|
|
2214
|
+
confirm(args.includes("--yes"), `Every open realtime connection on "${p}" closes, and pages listening for updates stop getting them.`, p, "the project name");
|
|
2215
|
+
await api(`/projects/${encodeURIComponent(p)}/services/realtime`, { method: "DELETE" });
|
|
2216
|
+
print({ disabled: "realtime", project: p }, () => console.log(`Realtime disabled for "${p}"`));
|
|
2217
|
+
return true;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
return false;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2080
2223
|
// src/run.ts
|
|
2081
2224
|
import { constants } from "os";
|
|
2082
2225
|
|
|
@@ -2113,8 +2256,8 @@ async function target(flags, needEnv = true) {
|
|
|
2113
2256
|
}
|
|
2114
2257
|
return { project, env: env ?? "" };
|
|
2115
2258
|
}
|
|
2116
|
-
var
|
|
2117
|
-
var envPath = (project, env, rest = "") => `${
|
|
2259
|
+
var base8 = (project) => `/projects/${encodeURIComponent(project)}/secrets`;
|
|
2260
|
+
var envPath = (project, env, rest = "") => `${base8(project)}/envs/${encodeURIComponent(env)}${rest}`;
|
|
2118
2261
|
var fetchSecrets = (project, env) => api(`/secrets/${encodeURIComponent(project)}/${encodeURIComponent(env)}`);
|
|
2119
2262
|
var describeEnv = (e) => [e.name, e.parent ? `branch of ${e.parent}` : "root", `${e.keys} keys`, `rev ${e.revision}`, e.linked ? "linked" : ""].filter(Boolean).join("\t");
|
|
2120
2263
|
async function readStdin() {
|
|
@@ -2133,7 +2276,7 @@ async function secrets(sub, args) {
|
|
|
2133
2276
|
case "enable": {
|
|
2134
2277
|
const p = need(positional[0] ?? flagString(flags, "project"), "project name");
|
|
2135
2278
|
await api(`/projects/${encodeURIComponent(p)}/services/secrets`, { method: "PUT" });
|
|
2136
|
-
const overview = await api(
|
|
2279
|
+
const overview = await api(base8(p));
|
|
2137
2280
|
print({ enabled: "secrets", project: p, ...overview }, () => {
|
|
2138
2281
|
console.log(`Secrets enabled for "${p}": ${overview.envs.map((e) => e.name).join(", ")}
|
|
2139
2282
|
`);
|
|
@@ -2153,7 +2296,7 @@ async function secrets(sub, args) {
|
|
|
2153
2296
|
}
|
|
2154
2297
|
case "setup": {
|
|
2155
2298
|
const { project, env } = await target(flags);
|
|
2156
|
-
const overview = await api(
|
|
2299
|
+
const overview = await api(base8(project));
|
|
2157
2300
|
if (!overview.envs.some((e) => e.name === env)) {
|
|
2158
2301
|
throw usageError(`"${project}" has no env "${env}" (envs: ${overview.envs.map((e) => e.name).join(", ")})`);
|
|
2159
2302
|
}
|
|
@@ -2163,7 +2306,7 @@ async function secrets(sub, args) {
|
|
|
2163
2306
|
}
|
|
2164
2307
|
case "envs": {
|
|
2165
2308
|
const { project } = await target(flags, false);
|
|
2166
|
-
const overview = await api(
|
|
2309
|
+
const overview = await api(base8(project));
|
|
2167
2310
|
print(overview.envs, () => {
|
|
2168
2311
|
for (const e of overview.envs)
|
|
2169
2312
|
console.log(describeEnv(e));
|
|
@@ -2174,7 +2317,7 @@ async function secrets(sub, args) {
|
|
|
2174
2317
|
const [action, name, next] = positional;
|
|
2175
2318
|
const { project } = await target(flags, false);
|
|
2176
2319
|
if (action === "create") {
|
|
2177
|
-
const env = await api(`${
|
|
2320
|
+
const env = await api(`${base8(project)}/envs`, {
|
|
2178
2321
|
method: "POST",
|
|
2179
2322
|
body: JSON.stringify({ name: need(name, "env name"), parent: flagString(flags, "from") ?? null })
|
|
2180
2323
|
});
|
|
@@ -2277,7 +2420,7 @@ async function secrets(sub, args) {
|
|
|
2277
2420
|
const { project, env } = await target(flags);
|
|
2278
2421
|
const envs = env.split(",").map((e) => e.trim()).filter(Boolean);
|
|
2279
2422
|
confirm(yes, `This rotates the credentials of "${project}" (database password, storage key, cache password if the cache is on, and the API key link made before) and stores the new ones, with the other services' keys, in ${envs.join(", ")} plus any env linked before. Apps still using the old credentials will stop working.`, project, "the project name");
|
|
2280
|
-
const result = await api(`${
|
|
2423
|
+
const result = await api(`${base8(project)}/link`, { method: "POST", body: JSON.stringify({ envs }) });
|
|
2281
2424
|
print(result, () => {
|
|
2282
2425
|
console.log(`Rotated and stored in ${result.envs.join(", ")}: ${result.keys.join(", ")}`);
|
|
2283
2426
|
console.log("Restart your apps with `mybase run` to pick them up.");
|
|
@@ -2287,7 +2430,7 @@ async function secrets(sub, args) {
|
|
|
2287
2430
|
case "unlink": {
|
|
2288
2431
|
const { project, env } = await target(flags);
|
|
2289
2432
|
confirm(yes, `This removes the project credentials from "${env}". They stay valid; nothing is rotated.`, env, "the env name");
|
|
2290
|
-
await api(`${
|
|
2433
|
+
await api(`${base8(project)}/link/${encodeURIComponent(env)}`, { method: "DELETE" });
|
|
2291
2434
|
print({ unlinked: env, project }, () => console.log(`Removed the credentials from "${env}"`));
|
|
2292
2435
|
return true;
|
|
2293
2436
|
}
|
|
@@ -3717,6 +3860,110 @@ ${i}
|
|
|
3717
3860
|
import { join as join4 } from "path";
|
|
3718
3861
|
import { styleText as styleText4 } from "util";
|
|
3719
3862
|
|
|
3863
|
+
// ../../packages/shared/src/logger.ts
|
|
3864
|
+
var LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
3865
|
+
var isLogLevel = (value) => (value in LEVELS);
|
|
3866
|
+
var levelAtLeast = (level, min) => LEVELS[level] >= LEVELS[min];
|
|
3867
|
+
var ansi = (code) => (s) => useColor() ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
3868
|
+
var color = {
|
|
3869
|
+
dim: ansi(2),
|
|
3870
|
+
bold: ansi(1),
|
|
3871
|
+
red: ansi(31),
|
|
3872
|
+
green: ansi(32),
|
|
3873
|
+
yellow: ansi(33),
|
|
3874
|
+
blue: ansi(34),
|
|
3875
|
+
magenta: ansi(35),
|
|
3876
|
+
cyan: ansi(36),
|
|
3877
|
+
gray: ansi(90)
|
|
3878
|
+
};
|
|
3879
|
+
var LEVEL_STYLE = {
|
|
3880
|
+
debug: color.gray,
|
|
3881
|
+
info: color.green,
|
|
3882
|
+
warn: color.yellow,
|
|
3883
|
+
error: color.red
|
|
3884
|
+
};
|
|
3885
|
+
var SERVICE_COLORS = {
|
|
3886
|
+
api: color.cyan,
|
|
3887
|
+
dashboard: color.magenta,
|
|
3888
|
+
cli: color.blue,
|
|
3889
|
+
cron: color.yellow,
|
|
3890
|
+
analytics: color.green,
|
|
3891
|
+
cache: color.red,
|
|
3892
|
+
ratelimit: color.magenta,
|
|
3893
|
+
queue: color.blue,
|
|
3894
|
+
search: color.cyan,
|
|
3895
|
+
mcp: color.green,
|
|
3896
|
+
docs: color.gray,
|
|
3897
|
+
audit: color.yellow,
|
|
3898
|
+
alerts: color.red,
|
|
3899
|
+
users: color.green,
|
|
3900
|
+
captcha: color.blue,
|
|
3901
|
+
realtime: color.magenta,
|
|
3902
|
+
"db-public": color.cyan,
|
|
3903
|
+
dev: color.gray
|
|
3904
|
+
};
|
|
3905
|
+
var FALLBACK_COLORS = [color.cyan, color.magenta, color.blue, color.yellow, color.green];
|
|
3906
|
+
function serviceColor(service) {
|
|
3907
|
+
const known = SERVICE_COLORS[service];
|
|
3908
|
+
if (known)
|
|
3909
|
+
return known;
|
|
3910
|
+
let hash = 0;
|
|
3911
|
+
for (const ch of service)
|
|
3912
|
+
hash = hash * 31 + ch.charCodeAt(0) >>> 0;
|
|
3913
|
+
return FALLBACK_COLORS[hash % FALLBACK_COLORS.length];
|
|
3914
|
+
}
|
|
3915
|
+
function useColor() {
|
|
3916
|
+
if (process.env.NO_COLOR)
|
|
3917
|
+
return false;
|
|
3918
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0")
|
|
3919
|
+
return true;
|
|
3920
|
+
return Boolean(process.stdout.isTTY);
|
|
3921
|
+
}
|
|
3922
|
+
function formatValue(value) {
|
|
3923
|
+
if (typeof value === "string")
|
|
3924
|
+
return /\s|=|"/.test(value) ? JSON.stringify(value) : value;
|
|
3925
|
+
if (value instanceof Error)
|
|
3926
|
+
return JSON.stringify(value.message);
|
|
3927
|
+
return JSON.stringify(value);
|
|
3928
|
+
}
|
|
3929
|
+
function formatPretty(service, level, msg, fields, at = new Date) {
|
|
3930
|
+
const time = color.dim(at.toISOString().slice(11, 23));
|
|
3931
|
+
const lvl = LEVEL_STYLE[level](level.toUpperCase().padEnd(5));
|
|
3932
|
+
const tag = process.env.MYBASE_LOG_PREFIXED ? "" : `${serviceColor(service)(`[${service}]`)} `;
|
|
3933
|
+
let errorStack = "";
|
|
3934
|
+
const pairs = [];
|
|
3935
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3936
|
+
if (value === undefined)
|
|
3937
|
+
continue;
|
|
3938
|
+
if (value instanceof Error && value.stack)
|
|
3939
|
+
errorStack = `
|
|
3940
|
+
${color.dim(value.stack)}`;
|
|
3941
|
+
pairs.push(`${color.gray(`${key}=`)}${formatValue(value)}`);
|
|
3942
|
+
}
|
|
3943
|
+
return `${time} ${lvl} ${tag}${msg}${pairs.length ? ` ${pairs.join(" ")}` : ""}${errorStack}`;
|
|
3944
|
+
}
|
|
3945
|
+
function prettyJsonLine(line) {
|
|
3946
|
+
if (!line.startsWith("{"))
|
|
3947
|
+
return null;
|
|
3948
|
+
let entry;
|
|
3949
|
+
try {
|
|
3950
|
+
entry = JSON.parse(line);
|
|
3951
|
+
} catch {
|
|
3952
|
+
return null;
|
|
3953
|
+
}
|
|
3954
|
+
const { time, level, service, msg, ...fields } = entry;
|
|
3955
|
+
if (typeof level !== "string" || !isLogLevel(level) || typeof service !== "string" || typeof msg !== "string")
|
|
3956
|
+
return null;
|
|
3957
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3958
|
+
const err = value;
|
|
3959
|
+
if (err && typeof err === "object" && typeof err.message === "string" && typeof err.stack === "string") {
|
|
3960
|
+
fields[key] = Object.assign(new Error(err.message), { name: String(err.name ?? "Error"), stack: err.stack });
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
const at = typeof time === "string" && !Number.isNaN(Date.parse(time)) ? new Date(time) : new Date;
|
|
3964
|
+
return { level, entry, text: formatPretty(service, level, msg, fields, at) };
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3720
3967
|
// src/server-docker.ts
|
|
3721
3968
|
import { existsSync, readFileSync } from "fs";
|
|
3722
3969
|
import { statfs } from "fs/promises";
|
|
@@ -4278,6 +4525,35 @@ function tunnelConnections(logs) {
|
|
|
4278
4525
|
}
|
|
4279
4526
|
return [...conns.values()].sort((a, b) => a.index - b.index);
|
|
4280
4527
|
}
|
|
4528
|
+
var LOG_SERVICES = ["mybase", "postgres", "garage", "valkey", "searxng", "cloudflared"];
|
|
4529
|
+
var LOG_ALIASES = {
|
|
4530
|
+
api: "mybase",
|
|
4531
|
+
dashboard: "mybase",
|
|
4532
|
+
db: "postgres",
|
|
4533
|
+
database: "postgres",
|
|
4534
|
+
storage: "garage",
|
|
4535
|
+
s3: "garage",
|
|
4536
|
+
cache: "valkey",
|
|
4537
|
+
search: "searxng",
|
|
4538
|
+
tunnel: "cloudflared"
|
|
4539
|
+
};
|
|
4540
|
+
function logService(name) {
|
|
4541
|
+
const lower = name.toLowerCase();
|
|
4542
|
+
return LOG_SERVICES.includes(lower) ? lower : LOG_ALIASES[lower] ?? null;
|
|
4543
|
+
}
|
|
4544
|
+
function splitComposeLine(line) {
|
|
4545
|
+
const m = /^([a-z0-9][\w.-]*?)(?:[-_]\d+)?\s+\| ?(.*)$/i.exec(line);
|
|
4546
|
+
return m ? { service: m[1], text: m[2] } : null;
|
|
4547
|
+
}
|
|
4548
|
+
function guessLevel(text) {
|
|
4549
|
+
if (/\b(ERR|ERROR|FATAL|PANIC|CRIT|CRITICAL)\b|level=(error|fatal)/.test(text))
|
|
4550
|
+
return "error";
|
|
4551
|
+
if (/\b(WRN|WARN|WARNING)\b|level=warn/.test(text))
|
|
4552
|
+
return "warn";
|
|
4553
|
+
if (/\b(DBG|DEBUG)\b|level=debug/.test(text))
|
|
4554
|
+
return "debug";
|
|
4555
|
+
return "info";
|
|
4556
|
+
}
|
|
4281
4557
|
|
|
4282
4558
|
// src/server-setup.ts
|
|
4283
4559
|
import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
|
|
@@ -5070,6 +5346,9 @@ var serverUsage = ` server setup [--dir <dir>] Install mybase on this machi
|
|
|
5070
5346
|
server status [--dir <dir>] Containers, health, and whether mybase starts on boot
|
|
5071
5347
|
server update [--dir <dir>] Download new images and restart (also refreshes compose.yml)
|
|
5072
5348
|
server restart [--dir <dir>] Restart every container (API, Postgres, storage, cache, search, tunnel)
|
|
5349
|
+
server logs [<service>\u2026] [--dir <dir>] Live logs (all services, or api|postgres|storage|cache|search|tunnel)
|
|
5350
|
+
[-f | --no-follow] [--tail <n>|all] [--since <10m|timestamp>] [--level warn] [--raw]
|
|
5351
|
+
Follows in a terminal; without one prints the tail and exits
|
|
5073
5352
|
server tunnel [--dir <dir>] Add or replace the Cloudflare Tunnel [--tunnel-token <t> --domain <d>] | --off
|
|
5074
5353
|
server database-public [--dir <dir>] A normal postgres:// URL from anywhere: router port forward +
|
|
5075
5354
|
Let's Encrypt TLS + DNS kept on your IP (Cloudflare)
|
|
@@ -5181,6 +5460,74 @@ ${res.stderr}`);
|
|
|
5181
5460
|
console.log(`mybase ${version} is running at ${localUrl(values)}`);
|
|
5182
5461
|
});
|
|
5183
5462
|
}
|
|
5463
|
+
var LOGS_VALUE_FLAGS = new Set(["--dir", "--tail", "--since", "--level"]);
|
|
5464
|
+
async function logs(args) {
|
|
5465
|
+
const dir = await serverDir(args);
|
|
5466
|
+
await loadEnv(dir);
|
|
5467
|
+
if (!await stackExists(dir))
|
|
5468
|
+
throw new CliError("no containers yet. Start them: mybase server update", 4);
|
|
5469
|
+
const services = [];
|
|
5470
|
+
for (let i = 0;i < args.length; i++) {
|
|
5471
|
+
const arg = args[i];
|
|
5472
|
+
if (LOGS_VALUE_FLAGS.has(arg))
|
|
5473
|
+
i++;
|
|
5474
|
+
if (arg.startsWith("-"))
|
|
5475
|
+
continue;
|
|
5476
|
+
const service = logService(arg);
|
|
5477
|
+
if (!service)
|
|
5478
|
+
throw usageError(`unknown service "${arg}": one of ${LOG_SERVICES.join(", ")} (or api, db, storage, cache, search, tunnel)`);
|
|
5479
|
+
if (!services.includes(service))
|
|
5480
|
+
services.push(service);
|
|
5481
|
+
}
|
|
5482
|
+
const min = flag(args, "--level") ?? "debug";
|
|
5483
|
+
if (!isLogLevel(min))
|
|
5484
|
+
throw usageError("--level is one of debug, info, warn, error");
|
|
5485
|
+
const tail = flag(args, "--tail") ?? "100";
|
|
5486
|
+
if (tail !== "all" && !/^\d+$/.test(tail))
|
|
5487
|
+
throw usageError("--tail is a number of lines, or all");
|
|
5488
|
+
const since = flag(args, "--since");
|
|
5489
|
+
const follow = args.includes("-f") || args.includes("--follow") || !args.includes("--no-follow") && !jsonMode && process.stdout.isTTY === true;
|
|
5490
|
+
const raw = args.includes("--raw");
|
|
5491
|
+
const cmd = ["docker", "compose", "logs", "--no-color", "--tail", tail, ...since ? ["--since", since] : [], ...follow ? ["--follow"] : [], ...services];
|
|
5492
|
+
const proc = Bun.spawn(cmd, { cwd: dir, stdout: "pipe", stderr: "pipe", env: composeEnv() });
|
|
5493
|
+
const colored = !jsonMode && color.red("x") !== "x";
|
|
5494
|
+
const width = Math.max(...(services.length > 0 ? services : LOG_SERVICES).map((s) => s.length));
|
|
5495
|
+
const show = (line) => {
|
|
5496
|
+
if (!colored)
|
|
5497
|
+
line = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5498
|
+
const parts = splitComposeLine(line);
|
|
5499
|
+
if (!parts)
|
|
5500
|
+
return line && console.error(line);
|
|
5501
|
+
const ours = prettyJsonLine(parts.text);
|
|
5502
|
+
const level = ours?.level ?? guessLevel(parts.text);
|
|
5503
|
+
if (!levelAtLeast(level, min))
|
|
5504
|
+
return;
|
|
5505
|
+
if (jsonMode)
|
|
5506
|
+
console.log(JSON.stringify(ours ? { container: parts.service, ...ours.entry } : { container: parts.service, level, line: parts.text }));
|
|
5507
|
+
else if (raw)
|
|
5508
|
+
console.log(line);
|
|
5509
|
+
else
|
|
5510
|
+
console.log(`${serviceColor(parts.service)(parts.service.padEnd(width))} ${color.dim("|")} ${ours?.text ?? parts.text}`);
|
|
5511
|
+
};
|
|
5512
|
+
await Promise.all([eachLine(proc.stdout, show), eachLine(proc.stderr, show)]);
|
|
5513
|
+
if (await proc.exited !== 0)
|
|
5514
|
+
process.exitCode = 1;
|
|
5515
|
+
}
|
|
5516
|
+
async function eachLine(stream, fn) {
|
|
5517
|
+
const decoder = new TextDecoder;
|
|
5518
|
+
let buffer = "";
|
|
5519
|
+
for await (const chunk of stream) {
|
|
5520
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
5521
|
+
for (let i = buffer.indexOf(`
|
|
5522
|
+
`);i !== -1; i = buffer.indexOf(`
|
|
5523
|
+
`)) {
|
|
5524
|
+
fn(buffer.slice(0, i).replace(/\r$/, ""));
|
|
5525
|
+
buffer = buffer.slice(i + 1);
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
if (buffer)
|
|
5529
|
+
fn(buffer);
|
|
5530
|
+
}
|
|
5184
5531
|
async function tunnel(args) {
|
|
5185
5532
|
const dir = await serverDir(args);
|
|
5186
5533
|
const { text, values } = await loadEnv(dir);
|
|
@@ -5339,6 +5686,8 @@ async function server(command, args) {
|
|
|
5339
5686
|
await update(args);
|
|
5340
5687
|
else if (command === "restart")
|
|
5341
5688
|
await restart(args);
|
|
5689
|
+
else if (command === "logs")
|
|
5690
|
+
await logs(args);
|
|
5342
5691
|
else if (command === "tunnel")
|
|
5343
5692
|
await tunnel(args);
|
|
5344
5693
|
else if (command === "database-public")
|
|
@@ -5477,7 +5826,7 @@ var usersUsage = ` users enable <project> Turn on users: an auth schema
|
|
|
5477
5826
|
users revoke <project> <user-id> [<session-id>] Sign a user out (one session, or everywhere)
|
|
5478
5827
|
users delete <project> <user-id> [--yes] Delete a user and their sessions
|
|
5479
5828
|
users disable <project> [--yes] Turn it off, deleting every user account`;
|
|
5480
|
-
var
|
|
5829
|
+
var base9 = (project) => `/projects/${encodeURIComponent(project)}/users`;
|
|
5481
5830
|
function authSnippet(project, apiUrl) {
|
|
5482
5831
|
return `// auth.ts: npm i better-auth pg @mybase-dev/sdk
|
|
5483
5832
|
import { betterAuth } from "better-auth";
|
|
@@ -5508,7 +5857,7 @@ async function users(sub, args) {
|
|
|
5508
5857
|
case "enable": {
|
|
5509
5858
|
const p = need(project, "project name");
|
|
5510
5859
|
const { linked } = await enableService(p, "users", flags);
|
|
5511
|
-
const o = await api(
|
|
5860
|
+
const o = await api(base9(p));
|
|
5512
5861
|
print({ enabled: "users", project: p, linked, ...o }, () => {
|
|
5513
5862
|
console.log(`Users enabled for "${p}". Better Auth's tables go in the "${o.schema}" schema.
|
|
5514
5863
|
`);
|
|
@@ -5522,7 +5871,7 @@ async function users(sub, args) {
|
|
|
5522
5871
|
}
|
|
5523
5872
|
case "init": {
|
|
5524
5873
|
const p = need(project, "project name");
|
|
5525
|
-
const o = await api(
|
|
5874
|
+
const o = await api(base9(p));
|
|
5526
5875
|
const env = flagString(flags, "env");
|
|
5527
5876
|
let stored = null;
|
|
5528
5877
|
if (env) {
|
|
@@ -5545,7 +5894,7 @@ Set BETTER_AUTH_SECRET to 32+ random bytes (openssl rand -base64 32), or rerun w
|
|
|
5545
5894
|
case "list": {
|
|
5546
5895
|
const p = need(project, "project name");
|
|
5547
5896
|
const search = flagString(flags, "search");
|
|
5548
|
-
const list = await api(`${
|
|
5897
|
+
const list = await api(`${base9(p)}/accounts?limit=100${search ? `&search=${encodeURIComponent(search)}` : ""}`);
|
|
5549
5898
|
print(list, () => {
|
|
5550
5899
|
if (list.users.length === 0)
|
|
5551
5900
|
console.log(search ? "No users match." : "No users yet.");
|
|
@@ -5558,7 +5907,7 @@ Set BETTER_AUTH_SECRET to 32+ random bytes (openssl rand -base64 32), or rerun w
|
|
|
5558
5907
|
}
|
|
5559
5908
|
case "show": {
|
|
5560
5909
|
const p = need(project, "project name");
|
|
5561
|
-
const u = await api(`${
|
|
5910
|
+
const u = await api(`${base9(p)}/accounts/${encodeURIComponent(need(userId, "user id"))}`);
|
|
5562
5911
|
print(u, () => {
|
|
5563
5912
|
console.log(`${u.email ?? u.id}${u.name ? ` (${u.name})` : ""}${u.banned ? " BANNED" : ""}`);
|
|
5564
5913
|
console.log(` id ${u.id}`);
|
|
@@ -5576,10 +5925,10 @@ Active sessions (${u.sessions.length}):`);
|
|
|
5576
5925
|
const p = need(project, "project name");
|
|
5577
5926
|
const id = encodeURIComponent(need(userId, "user id"));
|
|
5578
5927
|
if (sessionId) {
|
|
5579
|
-
await api(`${
|
|
5928
|
+
await api(`${base9(p)}/accounts/${id}/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
|
|
5580
5929
|
print({ revoked: 1 }, () => console.log("Signed out that session."));
|
|
5581
5930
|
} else {
|
|
5582
|
-
const r = await api(`${
|
|
5931
|
+
const r = await api(`${base9(p)}/accounts/${id}/sessions`, { method: "DELETE" });
|
|
5583
5932
|
print(r, () => console.log(`Signed out ${r.revoked} session(s).`));
|
|
5584
5933
|
}
|
|
5585
5934
|
return true;
|
|
@@ -5588,7 +5937,7 @@ Active sessions (${u.sessions.length}):`);
|
|
|
5588
5937
|
const p = need(project, "project name");
|
|
5589
5938
|
const id = need(userId, "user id");
|
|
5590
5939
|
confirm(args.includes("--yes"), `This deletes user ${id} of "${p}" and signs them out.`, id, "the user id");
|
|
5591
|
-
await api(`${
|
|
5940
|
+
await api(`${base9(p)}/accounts/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
5592
5941
|
print({ deleted: id }, () => console.log(`Deleted user ${id}`));
|
|
5593
5942
|
return true;
|
|
5594
5943
|
}
|
|
@@ -5630,6 +5979,7 @@ var USAGE = {
|
|
|
5630
5979
|
vector: vectorUsage,
|
|
5631
5980
|
users: usersUsage,
|
|
5632
5981
|
captcha: captchaUsage,
|
|
5982
|
+
realtime: realtimeUsage,
|
|
5633
5983
|
run: runUsage
|
|
5634
5984
|
};
|
|
5635
5985
|
var GLOBAL_FLAGS = ` --json Print results as JSON (errors too, on stderr)
|
|
@@ -5762,6 +6112,10 @@ async function main(args) {
|
|
|
5762
6112
|
if (await captcha(sub, rest))
|
|
5763
6113
|
return;
|
|
5764
6114
|
}
|
|
6115
|
+
if (command === "realtime" && sub) {
|
|
6116
|
+
if (await realtime(sub, rest))
|
|
6117
|
+
return;
|
|
6118
|
+
}
|
|
5765
6119
|
if (command && command !== "help")
|
|
5766
6120
|
throw usageError(`unknown command: mybase ${args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" ")}
|
|
5767
6121
|
|
package/package.json
CHANGED