@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.
Files changed (2) hide show
  1. package/dist/mybase.js +165 -7
  2. 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.1",
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 logs = await compose(dir, ["logs", "--tail", "30", "mybase"]);
4646
- throw new CliError(`mybase didn't answer at ${localUrl(env)}/health within 3 minutes. Its last log lines:
4647
- ${logs.stdout || logs.stderr}
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: curl -fsSL https://raw.githubusercontent.com/LandonHarter/mybase/master/scripts/install.sh | sh`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mybase-dev/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
5
5
  "type": "module",
6
6
  "repository": {