@mybase-dev/cli 1.0.1 → 1.0.2
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 +165 -7
- package/package.json +1 -1
package/dist/mybase.js
CHANGED
|
@@ -139,7 +139,7 @@ function parseDotenv(text) {
|
|
|
139
139
|
// package.json
|
|
140
140
|
var package_default = {
|
|
141
141
|
name: "@mybase-dev/cli",
|
|
142
|
-
version: "1.0.
|
|
142
|
+
version: "1.0.2",
|
|
143
143
|
description: "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
|
|
144
144
|
type: "module",
|
|
145
145
|
repository: {
|
|
@@ -3875,6 +3875,8 @@ configs:
|
|
|
3875
3875
|
metadata_dir = "/var/lib/garage/meta"
|
|
3876
3876
|
data_dir = "/var/lib/garage/data"
|
|
3877
3877
|
db_engine = "lmdb"
|
|
3878
|
+
# Garage maps 1 TiB by default; Raspberry Pi kernels give processes only 512 GiB (39-bit VA).
|
|
3879
|
+
lmdb_map_size = "64G"
|
|
3878
3880
|
replication_factor = 1
|
|
3879
3881
|
rpc_bind_addr = "[::]:3901"
|
|
3880
3882
|
rpc_public_addr = "127.0.0.1:3901"
|
|
@@ -4604,6 +4606,22 @@ async function writeCompose(dir, interactive, replace) {
|
|
|
4604
4606
|
await Bun.write(file, compose_default);
|
|
4605
4607
|
return true;
|
|
4606
4608
|
}
|
|
4609
|
+
function pullHint(output) {
|
|
4610
|
+
const failed = /Image (\S+) Error/.exec(output)?.[1];
|
|
4611
|
+
if (/unauthorized|denied|requested access to the resource/i.test(output)) {
|
|
4612
|
+
const image = failed ?? "an image";
|
|
4613
|
+
return failed?.startsWith("ghcr.io/") ? `
|
|
4614
|
+
|
|
4615
|
+
${image} is private on GitHub's registry (or doesn't exist). Its owner has to make the package public (GitHub \u2192 Packages \u2192 Package settings \u2192 Change visibility), or sign in first: docker login ghcr.io` : `
|
|
4616
|
+
|
|
4617
|
+
The registry refused ${image}: check the image name (MYBASE_IMAGE / MYBASE_POSTGRES_IMAGE in .env), or docker login.`;
|
|
4618
|
+
}
|
|
4619
|
+
if (/timeout|no such host|network|TLS handshake/i.test(output))
|
|
4620
|
+
return `
|
|
4621
|
+
|
|
4622
|
+
This machine couldn't reach the registry. Check its internet connection and DNS, then run setup again.`;
|
|
4623
|
+
return "";
|
|
4624
|
+
}
|
|
4607
4625
|
async function missingImages(dir) {
|
|
4608
4626
|
const images = await compose(dir, ["config", "--images"]);
|
|
4609
4627
|
const missing = [];
|
|
@@ -4615,6 +4633,14 @@ async function missingImages(dir) {
|
|
|
4615
4633
|
return missing;
|
|
4616
4634
|
}
|
|
4617
4635
|
var imageExists = (image) => Bun.spawnSync(["docker", "image", "inspect", image], { stdout: "ignore", stderr: "ignore" }).exitCode === 0;
|
|
4636
|
+
async function brokenServices(dir) {
|
|
4637
|
+
const ps = await compose(dir, ["ps", "-a", "--format", "json"]);
|
|
4638
|
+
if (!ps.ok || !ps.stdout)
|
|
4639
|
+
return [];
|
|
4640
|
+
const rows = ps.stdout.startsWith("[") ? JSON.parse(ps.stdout) : ps.stdout.split(`
|
|
4641
|
+
`).filter(Boolean).map((l) => JSON.parse(l));
|
|
4642
|
+
return rows.filter((r) => r.State !== "running" || r.Health === "unhealthy").map((r) => r.Service);
|
|
4643
|
+
}
|
|
4618
4644
|
async function startStack(dir, env, pull = true) {
|
|
4619
4645
|
const s = ui.spinner();
|
|
4620
4646
|
if (pull) {
|
|
@@ -4625,7 +4651,7 @@ async function startStack(dir, env, pull = true) {
|
|
|
4625
4651
|
if (missing.length) {
|
|
4626
4652
|
s.error("Couldn't download the images");
|
|
4627
4653
|
throw new CliError(`couldn't download ${missing.join(", ")}:
|
|
4628
|
-
${tail(pulled.stderr || pulled.stdout)}`);
|
|
4654
|
+
${tail(pulled.stderr || pulled.stdout)}${pullHint(pulled.stderr || pulled.stdout)}`);
|
|
4629
4655
|
}
|
|
4630
4656
|
s.stop("Using the images already on this machine (couldn't download newer ones)");
|
|
4631
4657
|
} else
|
|
@@ -4642,11 +4668,21 @@ ${tail(up.stderr || up.stdout)}`);
|
|
|
4642
4668
|
const version = await waitHealthy(localUrl(env));
|
|
4643
4669
|
if (!version) {
|
|
4644
4670
|
s.error("mybase didn't come up");
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4671
|
+
const broken = await brokenServices(dir);
|
|
4672
|
+
const sections = [];
|
|
4673
|
+
for (const service of broken.length ? broken : ["mybase"]) {
|
|
4674
|
+
const logs = await compose(dir, ["logs", "--no-log-prefix", "--tail", "25", service]);
|
|
4675
|
+
sections.push(`\u2500\u2500 ${service} \u2500\u2500
|
|
4676
|
+
${logs.stdout || logs.stderr || "(no output)"}`);
|
|
4677
|
+
}
|
|
4678
|
+
const which = broken.length ? `Not running: ${broken.join(", ")}.` : "Every container is up, but the API isn't answering.";
|
|
4679
|
+
throw new CliError(`mybase didn't answer at ${localUrl(env)}/health within 3 minutes. ${which}
|
|
4680
|
+
|
|
4681
|
+
${sections.join(`
|
|
4682
|
+
|
|
4683
|
+
`)}
|
|
4648
4684
|
|
|
4649
|
-
Fix the problem, then run \`mybase server setup --dir ${dir}\` again (keys are kept).`);
|
|
4685
|
+
Fix the problem, then run \`mybase server setup --dir ${dir}\` again (keys are kept). \`mybase server status\` shows every container.`);
|
|
4650
4686
|
}
|
|
4651
4687
|
s.stop(`mybase ${version} is running`);
|
|
4652
4688
|
return version;
|
|
@@ -4929,7 +4965,7 @@ async function update(args) {
|
|
|
4929
4965
|
const version = await startStack(dir, values);
|
|
4930
4966
|
const minor = (v) => v.split(".").slice(0, 2).join(".");
|
|
4931
4967
|
if (minor(version) !== minor(CLI_VERSION)) {
|
|
4932
|
-
ui.warn(`The server is ${version}, this CLI ${CLI_VERSION}. Update the CLI:
|
|
4968
|
+
ui.warn(`The server is ${version}, this CLI ${CLI_VERSION}. Update the CLI: mybase update --cli`);
|
|
4933
4969
|
}
|
|
4934
4970
|
if (jsonMode)
|
|
4935
4971
|
return print({ dir, version, composeUpdated: replaced }, () => {});
|
|
@@ -5005,6 +5041,125 @@ async function server(command, args) {
|
|
|
5005
5041
|
return true;
|
|
5006
5042
|
}
|
|
5007
5043
|
|
|
5044
|
+
// src/update.ts
|
|
5045
|
+
import { chmod as chmod3, rename, rm } from "fs/promises";
|
|
5046
|
+
import { join as join5 } from "path";
|
|
5047
|
+
var updateUsage = ` update [--version <v>] [--cli] [--yes]
|
|
5048
|
+
Update this CLI to the latest release (or <v>), then the server on this
|
|
5049
|
+
machine, if there is one. --cli: only the CLI`;
|
|
5050
|
+
var REPO = "landonharter/mybase";
|
|
5051
|
+
var isCompiled = () => Bun.main.startsWith("/$bunfs/") || Bun.main.includes("~BUN");
|
|
5052
|
+
function assetName(platform = process.platform, arch = process.arch) {
|
|
5053
|
+
const os = platform === "win32" ? "windows" : platform;
|
|
5054
|
+
if (!["linux", "darwin", "windows"].includes(os))
|
|
5055
|
+
throw new CliError(`no mybase binary for ${platform}`);
|
|
5056
|
+
const cpu = arch === "x64" ? "x64" : arch === "arm64" ? "arm64" : null;
|
|
5057
|
+
if (!cpu)
|
|
5058
|
+
throw new CliError(`no mybase binary for ${arch} CPUs`);
|
|
5059
|
+
return `mybase-${os}-${cpu}${os === "windows" ? ".exe" : ""}`;
|
|
5060
|
+
}
|
|
5061
|
+
async function latestVersion() {
|
|
5062
|
+
const res = await fetch(`https://github.com/${REPO}/releases/latest`, { redirect: "manual" }).catch(() => {
|
|
5063
|
+
throw new CliError("can't reach github.com to check for updates");
|
|
5064
|
+
});
|
|
5065
|
+
const tag = /\/tag\/v?([^/?#]+)$/.exec(res.headers.get("location") ?? "")?.[1];
|
|
5066
|
+
if (!tag)
|
|
5067
|
+
throw new CliError(`couldn't find the latest release (GitHub answered ${res.status})`);
|
|
5068
|
+
return tag;
|
|
5069
|
+
}
|
|
5070
|
+
function newer(a, b) {
|
|
5071
|
+
const pa = a.split(/[.-]/).map((n) => Number.parseInt(n, 10) || 0);
|
|
5072
|
+
const pb = b.split(/[.-]/).map((n) => Number.parseInt(n, 10) || 0);
|
|
5073
|
+
for (let i = 0;i < 3; i++)
|
|
5074
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0))
|
|
5075
|
+
return (pa[i] ?? 0) > (pb[i] ?? 0);
|
|
5076
|
+
return false;
|
|
5077
|
+
}
|
|
5078
|
+
async function replaceBinary(version) {
|
|
5079
|
+
const target = process.execPath;
|
|
5080
|
+
const url = `https://github.com/${REPO}/releases/download/v${version}/${assetName()}`;
|
|
5081
|
+
const res = await fetch(url).catch(() => {
|
|
5082
|
+
throw new CliError(`can't download ${url}`);
|
|
5083
|
+
});
|
|
5084
|
+
if (!res.ok)
|
|
5085
|
+
throw new CliError(`can't download ${url} (${res.status})`);
|
|
5086
|
+
const temp = `${target}.new`;
|
|
5087
|
+
try {
|
|
5088
|
+
const writer = Bun.file(temp).writer();
|
|
5089
|
+
for await (const chunk of res.body)
|
|
5090
|
+
writer.write(chunk);
|
|
5091
|
+
await writer.end();
|
|
5092
|
+
await chmod3(temp, 493);
|
|
5093
|
+
} catch (err) {
|
|
5094
|
+
const code = err.code;
|
|
5095
|
+
if (code === "EACCES" || code === "EPERM")
|
|
5096
|
+
throw new CliError(`can't write next to ${target}. Run it with sudo, or reinstall: curl -fsSL https://raw.githubusercontent.com/${REPO}/master/scripts/install.sh | sh`);
|
|
5097
|
+
throw err;
|
|
5098
|
+
}
|
|
5099
|
+
const check = Bun.spawnSync([temp, "help"], { stdout: "ignore", stderr: "ignore" });
|
|
5100
|
+
if (check.exitCode !== 0) {
|
|
5101
|
+
await rm(temp, { force: true });
|
|
5102
|
+
throw new CliError(`the downloaded binary doesn't run on this machine; kept ${CLI_VERSION}`);
|
|
5103
|
+
}
|
|
5104
|
+
if (process.platform === "win32") {
|
|
5105
|
+
await rm(`${target}.old`, { force: true });
|
|
5106
|
+
await rename(target, `${target}.old`);
|
|
5107
|
+
}
|
|
5108
|
+
await rename(temp, target);
|
|
5109
|
+
}
|
|
5110
|
+
async function updateServer(bin, dir) {
|
|
5111
|
+
const args = [...bin, "server", "update", "--dir", dir, ...jsonMode ? ["--json", "--yes"] : []];
|
|
5112
|
+
const proc = Bun.spawn(args, { stdio: [jsonMode ? "ignore" : "inherit", jsonMode ? "ignore" : "inherit", "inherit"] });
|
|
5113
|
+
return await proc.exited === 0;
|
|
5114
|
+
}
|
|
5115
|
+
async function update2(args) {
|
|
5116
|
+
const versionIndex = args.indexOf("--version");
|
|
5117
|
+
const wanted = versionIndex === -1 ? null : args[versionIndex + 1]?.replace(/^v/, "");
|
|
5118
|
+
if (versionIndex !== -1 && !wanted)
|
|
5119
|
+
throw new CliError("--version needs a value", 2);
|
|
5120
|
+
const yes = args.includes("--yes");
|
|
5121
|
+
const result = {
|
|
5122
|
+
cli: { from: CLI_VERSION, to: CLI_VERSION, updated: false },
|
|
5123
|
+
server: null
|
|
5124
|
+
};
|
|
5125
|
+
if (!isCompiled()) {
|
|
5126
|
+
if (!jsonMode)
|
|
5127
|
+
console.log("This CLI was installed with npm/bun. Update it with: bun add -g @mybase-dev/cli@latest (or npm i -g)");
|
|
5128
|
+
} else {
|
|
5129
|
+
const target = wanted ?? await latestVersion();
|
|
5130
|
+
result.cli.to = target;
|
|
5131
|
+
if (target === CLI_VERSION || !wanted && !newer(target, CLI_VERSION)) {
|
|
5132
|
+
if (!jsonMode)
|
|
5133
|
+
console.log(`CLI ${CLI_VERSION} is up to date.`);
|
|
5134
|
+
} else {
|
|
5135
|
+
if (!jsonMode)
|
|
5136
|
+
console.log(`Updating the CLI: ${CLI_VERSION} \u2192 ${target}`);
|
|
5137
|
+
await replaceBinary(target);
|
|
5138
|
+
result.cli.updated = true;
|
|
5139
|
+
if (!jsonMode)
|
|
5140
|
+
console.log(`CLI ${target} installed at ${process.execPath}`);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
const dir = (await loadConfig()).server;
|
|
5144
|
+
const installed = dir && await Bun.file(join5(dir, "compose.yml")).exists();
|
|
5145
|
+
if (installed && !args.includes("--cli")) {
|
|
5146
|
+
let go = yes || jsonMode;
|
|
5147
|
+
if (!go && process.stdin.isTTY) {
|
|
5148
|
+
const answer = prompt(`Update the server in ${dir} too? (new images, restart) [Y/n]`);
|
|
5149
|
+
go = answer === null || /^(y|yes|)$/i.test(answer.trim());
|
|
5150
|
+
}
|
|
5151
|
+
if (go) {
|
|
5152
|
+
const bin = isCompiled() ? [process.execPath] : [process.execPath, Bun.main];
|
|
5153
|
+
const ok = await updateServer(bin, dir);
|
|
5154
|
+
result.server = { dir, updated: ok };
|
|
5155
|
+
if (!ok)
|
|
5156
|
+
throw new CliError(`the server update failed${result.cli.updated ? ` (the CLI is ${result.cli.to} now)` : ""}. Run \`mybase server update\` to see why.`);
|
|
5157
|
+
} else if (!jsonMode)
|
|
5158
|
+
console.log("Server left as it is. Update it later with: mybase server update");
|
|
5159
|
+
}
|
|
5160
|
+
print(result, () => {});
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5008
5163
|
// src/users.ts
|
|
5009
5164
|
var usersUsage = ` users enable <project> Turn on users: an auth schema for Better Auth, plus setup steps
|
|
5010
5165
|
users init <project> [--env <secrets env>] Print an auth.ts for Better Auth; --env stores a new BETTER_AUTH_SECRET there
|
|
@@ -5145,6 +5300,7 @@ var projectUsage = ` project create <name> Create a new project (datab
|
|
|
5145
5300
|
var USAGE = {
|
|
5146
5301
|
auth: authUsage,
|
|
5147
5302
|
server: serverUsage,
|
|
5303
|
+
update: updateUsage,
|
|
5148
5304
|
token: tokenUsage,
|
|
5149
5305
|
project: projectUsage,
|
|
5150
5306
|
context: contextUsage,
|
|
@@ -5198,6 +5354,8 @@ async function main(args) {
|
|
|
5198
5354
|
return;
|
|
5199
5355
|
if (command === "run")
|
|
5200
5356
|
return run(args.slice(1));
|
|
5357
|
+
if (command === "update")
|
|
5358
|
+
return update2(args.slice(1));
|
|
5201
5359
|
if (command === "doctor")
|
|
5202
5360
|
return doctor(args.slice(1));
|
|
5203
5361
|
if (command === "audit")
|
package/package.json
CHANGED