@kvbox/cli 0.5.20 → 0.5.22

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/kvbox.js CHANGED
@@ -164,7 +164,7 @@ function versionOf(srcFileUrl, injected) {
164
164
  }
165
165
 
166
166
  // src/version.ts
167
- var VERSION = versionOf(import.meta.url, true ? "0.5.20" : void 0);
167
+ var VERSION = versionOf(import.meta.url, true ? "0.5.22" : void 0);
168
168
 
169
169
  // src/commands/vm/index.ts
170
170
  import { Command as Command25 } from "commander";
@@ -370,12 +370,12 @@ import { z as z2 } from "zod";
370
370
 
371
371
  // ../shared/src/sizes.ts
372
372
  var SIZES = {
373
+ xs: { vcpu: 1, memMib: 2048, diskGib: 8 },
373
374
  s: { vcpu: 2, memMib: 4096, diskGib: 8 },
374
375
  m: { vcpu: 4, memMib: 8192, diskGib: 16 },
375
376
  l: { vcpu: 8, memMib: 16384, diskGib: 32 }
376
377
  };
377
378
  var SIZE_NAMES = Object.keys(SIZES);
378
- var DEFAULT_SIZE = "s";
379
379
  function parseMib(text2) {
380
380
  const m = /^\s*(\d+(?:\.\d+)?)\s*([gm]i?b?)?\s*$/i.exec(text2);
381
381
  if (!m) return void 0;
@@ -2891,14 +2891,12 @@ import { Command as Command49, Option as Option5 } from "commander";
2891
2891
 
2892
2892
  // src/commands/deploy/config.ts
2893
2893
  import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
2894
- import { join as join3 } from "path";
2894
+ import { join as join4 } from "path";
2895
2895
  import { z as z5 } from "zod";
2896
2896
 
2897
- // src/commands/deploy/templates/react.ts
2898
- import { spawn as spawn4 } from "child_process";
2897
+ // src/commands/deploy/templates/pm.ts
2899
2898
  import { access } from "fs/promises";
2900
2899
  import { join as join2 } from "path";
2901
- var OUT_DIRS = ["dist", "build"];
2902
2900
  async function packageManager(dir) {
2903
2901
  const has = (f) => access(join2(dir, f)).then(() => true, () => false);
2904
2902
  if (await has("pnpm-lock.yaml")) return "pnpm";
@@ -2906,8 +2904,101 @@ async function packageManager(dir) {
2906
2904
  if (await has("bun.lockb") || await has("bun.lock")) return "bun";
2907
2905
  return "npm";
2908
2906
  }
2907
+ function runScript(pm, script) {
2908
+ return pm === "yarn" ? [script] : ["run", script];
2909
+ }
2910
+
2911
+ // src/commands/deploy/templates/nextjs.ts
2912
+ var APP_DIR = "/srv/app/current";
2913
+ var SERVICE = "kvbox-app";
2914
+ var PORT = 3e3;
2915
+ function commands(pm) {
2916
+ switch (pm) {
2917
+ case "pnpm":
2918
+ return { setup: ["sudo -n corepack enable pnpm"], install: "pnpm install --frozen-lockfile", build: "pnpm run build", start: "pnpm run start" };
2919
+ case "yarn":
2920
+ return { setup: ["sudo -n corepack enable yarn"], install: "yarn install --frozen-lockfile", build: "yarn build", start: "yarn start" };
2921
+ case "bun":
2922
+ return { setup: [], install: "npm install", build: "npm run build", start: "npm run start" };
2923
+ case "npm":
2924
+ return { setup: [], install: "if [ -f package-lock.json ]; then npm ci; else npm install; fi", build: "npm run build", start: "npm run start" };
2925
+ }
2926
+ }
2927
+ var nextjsTemplate = {
2928
+ id: "nextjs",
2929
+ label: "Next.js",
2930
+ hint: "source shipped; installed, built and run in the box on port 3000",
2931
+ size: "s",
2932
+ port: PORT,
2933
+ detect(pkg) {
2934
+ if (!pkg) return false;
2935
+ return "next" in { ...pkg.dependencies, ...pkg.devDependencies };
2936
+ },
2937
+ /** No local build: the box does it. Only the scripts it will need are checked here. */
2938
+ async build(ctx) {
2939
+ for (const script of ["build", "start"]) {
2940
+ if (typeof ctx.pkg?.scripts?.[script] !== "string") throw new KvboxError(`${ctx.dir}: package.json has no "${script}" script; Next.js needs "build": "next build" and "start": "next start"`);
2941
+ }
2942
+ const pm = await packageManager(ctx.dir);
2943
+ return { outDir: ctx.dir, exclude: ["node_modules", ".next", ".git"], pm, what: "the source" };
2944
+ },
2945
+ /**
2946
+ * The source lands in a staging directory and is installed and built there while the old app
2947
+ * keeps serving; only then is it swapped into place and the service restarted, so a failed
2948
+ * install or build leaves the running app alone. The unit is rewritten every time, so a changed
2949
+ * package manager or start script takes effect. /srv/app is the kvbox user's (image v0.8.0+).
2950
+ */
2951
+ remote(built) {
2952
+ const c = commands(built.pm);
2953
+ const unit = [
2954
+ "[Unit]",
2955
+ "Description=kvbox deploy: the app in " + APP_DIR,
2956
+ "After=network.target",
2957
+ "",
2958
+ "[Service]",
2959
+ "User=kvbox",
2960
+ `WorkingDirectory=${APP_DIR}`,
2961
+ `ExecStart=/usr/bin/env ${c.start}`,
2962
+ `Environment=NODE_ENV=production PORT=${PORT}`,
2963
+ "Restart=always",
2964
+ "RestartSec=2",
2965
+ "",
2966
+ "[Install]",
2967
+ "WantedBy=multi-user.target"
2968
+ ];
2969
+ return [
2970
+ "set -e",
2971
+ "test -d /srv/app -a -w /srv/app || exit 3",
2972
+ "rm -rf /srv/app/.incoming /srv/app/.old",
2973
+ "mkdir -p /srv/app/.incoming",
2974
+ "tar -xf - -C /srv/app/.incoming",
2975
+ "cd /srv/app/.incoming",
2976
+ // Not NODE_ENV=production here: the install would skip devDependencies, which the build needs (TypeScript, Tailwind).
2977
+ "export CI=1 NEXT_TELEMETRY_DISABLED=1",
2978
+ ...c.setup,
2979
+ c.install,
2980
+ c.build,
2981
+ "cd /",
2982
+ `sudo -n tee /etc/systemd/system/${SERVICE}.service >/dev/null <<'UNIT'`,
2983
+ ...unit,
2984
+ "UNIT",
2985
+ `if [ -e ${APP_DIR} ]; then mv ${APP_DIR} /srv/app/.old; fi`,
2986
+ `mv /srv/app/.incoming ${APP_DIR}`,
2987
+ "sudo -n systemctl daemon-reload",
2988
+ `sudo -n systemctl enable --quiet ${SERVICE}`,
2989
+ `sudo -n systemctl restart ${SERVICE}`,
2990
+ "rm -rf /srv/app/.old"
2991
+ ].join("\n");
2992
+ }
2993
+ };
2994
+
2995
+ // src/commands/deploy/templates/react.ts
2996
+ import { spawn as spawn4 } from "child_process";
2997
+ import { access as access2 } from "fs/promises";
2998
+ import { join as join3 } from "path";
2999
+ var OUT_DIRS = ["dist", "build"];
2909
3000
  async function runBuild(dir, pm) {
2910
- const args = pm === "yarn" ? ["build"] : ["run", "build"];
3001
+ const args = runScript(pm, "build");
2911
3002
  info(`${color.dim("$")} ${pm} ${args.join(" ")}`);
2912
3003
  const status2 = await new Promise((resolve3, reject) => {
2913
3004
  const child = spawn4(pm, args, { cwd: dir, stdio: ["ignore", process.stderr, process.stderr], shell: process.platform === "win32" });
@@ -2920,6 +3011,7 @@ var reactTemplate = {
2920
3011
  id: "react",
2921
3012
  label: "React",
2922
3013
  hint: "static site: built here, served by nginx in the box",
3014
+ size: "xs",
2923
3015
  port: 80,
2924
3016
  /** A Vite or Create React App project. A framework with its own server (Next.js, Remix, Expo) is not a static site. */
2925
3017
  detect(pkg) {
@@ -2930,11 +3022,12 @@ var reactTemplate = {
2930
3022
  },
2931
3023
  async build(ctx) {
2932
3024
  if (typeof ctx.pkg?.scripts?.["build"] !== "string") throw new KvboxError(`${ctx.dir}: package.json has no "build" script`);
2933
- await runBuild(ctx.dir, await packageManager(ctx.dir));
3025
+ const pm = await packageManager(ctx.dir);
3026
+ await runBuild(ctx.dir, pm);
2934
3027
  const candidates = ctx.dist !== void 0 ? [ctx.dist] : OUT_DIRS;
2935
3028
  for (const dist of candidates) {
2936
- const outDir = join2(ctx.dir, dist);
2937
- if (await access(join2(outDir, "index.html")).then(() => true, () => false)) return { outDir, dist };
3029
+ const outDir = join3(ctx.dir, dist);
3030
+ if (await access2(join3(outDir, "index.html")).then(() => true, () => false)) return { outDir, dist, exclude: [], pm, what: `${dist}/` };
2938
3031
  }
2939
3032
  throw new KvboxError(
2940
3033
  `the build left no ${candidates.join(" or ")}/index.html. The React template ships a directory of static files; set "dist" in kvbox.json to the build output directory, or pick the right template with --template`
@@ -2965,7 +3058,7 @@ var reactTemplate = {
2965
3058
  };
2966
3059
 
2967
3060
  // src/commands/deploy/templates/index.ts
2968
- var TEMPLATES = [reactTemplate];
3061
+ var TEMPLATES = [reactTemplate, nextjsTemplate];
2969
3062
  var TEMPLATE_IDS = TEMPLATES.map((t) => t.id);
2970
3063
  function templateById(id) {
2971
3064
  const t = TEMPLATES.find((x) => x.id === id);
@@ -2977,6 +3070,7 @@ function detectTemplate(pkg) {
2977
3070
  }
2978
3071
 
2979
3072
  // src/commands/deploy/config.ts
3073
+ var DEPLOY_AUTO_HIBERNATE_SEC = 3600;
2980
3074
  var CONFIG_FILE = "kvbox.json";
2981
3075
  var BOX_NAME_RE = /^[a-z0-9][a-z0-9-]{0,11}$/;
2982
3076
  var DeployConfigSchema = z5.object({
@@ -2994,7 +3088,7 @@ var DeployConfigSchema = z5.object({
2994
3088
  dist: z5.string().optional()
2995
3089
  });
2996
3090
  async function readConfig(dir) {
2997
- const path = join3(dir, CONFIG_FILE);
3091
+ const path = join4(dir, CONFIG_FILE);
2998
3092
  let raw;
2999
3093
  try {
3000
3094
  raw = await readFile6(path, "utf8");
@@ -3016,7 +3110,7 @@ async function readConfig(dir) {
3016
3110
  return parsed.data;
3017
3111
  }
3018
3112
  async function writeConfig(dir, config) {
3019
- await writeFile4(join3(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
3113
+ await writeFile4(join4(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
3020
3114
  }
3021
3115
  function nameFromDir(dirName) {
3022
3116
  const slug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 12).replace(/-+$/, "");
@@ -3029,21 +3123,27 @@ function subdomainFor(name) {
3029
3123
  // src/commands/deploy/ship.ts
3030
3124
  import { spawn as spawn5 } from "child_process";
3031
3125
  import { readdir, stat as stat2 } from "fs/promises";
3032
- import { join as join4 } from "path";
3126
+ import { join as join5 } from "path";
3033
3127
  var IMAGE_TOO_OLD = 3;
3034
3128
  var SSH_FAILED3 = 255;
3035
- async function measure(dir) {
3036
- let files = 0;
3037
- let bytes = 0;
3038
- for (const entry of await readdir(dir, { recursive: true, withFileTypes: true })) {
3039
- if (!entry.isFile()) continue;
3040
- files += 1;
3041
- bytes += (await stat2(join4(entry.parentPath, entry.name))).size;
3042
- }
3043
- return { files, bytes };
3044
- }
3045
- async function ship(ep, dir, remote) {
3046
- const tar = spawn5("tar", ["-cf", "-", "-C", dir, "."], { stdio: ["ignore", "pipe", "pipe"] });
3129
+ async function measure(dir, exclude = []) {
3130
+ const total = { files: 0, bytes: 0 };
3131
+ const walk = async (d) => {
3132
+ for (const entry of await readdir(d, { withFileTypes: true })) {
3133
+ if (exclude.includes(entry.name)) continue;
3134
+ const path = join5(d, entry.name);
3135
+ if (entry.isDirectory()) await walk(path);
3136
+ else if (entry.isFile()) {
3137
+ total.files += 1;
3138
+ total.bytes += (await stat2(path)).size;
3139
+ }
3140
+ }
3141
+ };
3142
+ await walk(dir);
3143
+ return total;
3144
+ }
3145
+ async function ship(ep, dir, remote, exclude = []) {
3146
+ const tar = spawn5("tar", ["-cf", "-", ...exclude.map((e) => `--exclude=${e}`), "-C", dir, "."], { stdio: ["ignore", "pipe", "pipe"] });
3047
3147
  const ssh = spawn5("ssh", [...sshArgs(ep), remote], { stdio: ["pipe", "pipe", "pipe"] });
3048
3148
  tar.stdout.pipe(ssh.stdin);
3049
3149
  const chunks = [];
@@ -3074,7 +3174,7 @@ async function ship(ep, dir, remote) {
3074
3174
  var shipping = { ship, measure, waitForSsh };
3075
3175
 
3076
3176
  // src/commands/deploy/index.ts
3077
- var deployCommand = new Command49("deploy").description("put the app in this directory on a box with its own HTTPS address (React for now); redeploy by running it again").argument("[dir]", "the app's directory (default: the current one)").addOption(new Option5("-t, --template <template>", "what kind of app it is (detected from package.json when not given)").choices(TEMPLATE_IDS)).option("-n, --name <name>", "the box's name (default: the directory's name)").option("--sub <subdomain>", "its HTTPS name, https://<subdomain>.kvbox.app (default: the box's name)").addOption(new Option5("-s, --size <size>", "size of the box when it has to be made").choices(SIZE_NAMES)).option("-y, --yes", "ask nothing: take the defaults for whatever the flags and kvbox.json leave open").option("--json", "print the result as JSON").option("--org <slug>", "deploy into this org's boxes (saved to kvbox.json)").action(async (dirArg, opts, cmd) => {
3177
+ var deployCommand = new Command49("deploy").description("put the app in this directory (React or Next.js) on a box with its own HTTPS address; redeploy by running it again").argument("[dir]", "the app's directory (default: the current one)").addOption(new Option5("-t, --template <template>", "what kind of app it is (detected from package.json when not given)").choices(TEMPLATE_IDS)).option("-n, --name <name>", "the box's name (default: the directory's name)").option("--sub <subdomain>", "its HTTPS name, https://<subdomain>.kvbox.app (default: the box's name)").addOption(new Option5("-s, --size <size>", "size of the box when it has to be made").choices(SIZE_NAMES)).option("-y, --yes", "ask nothing: take the defaults for whatever the flags and kvbox.json leave open").option("--json", "print the result as JSON").option("--org <slug>", "deploy into this org's boxes (saved to kvbox.json)").action(async (dirArg, opts, cmd) => {
3078
3178
  const dir = resolve2(dirArg ?? ".");
3079
3179
  const pkg = await readPackageJson(dir);
3080
3180
  const saved = await readConfig(dir);
@@ -3092,8 +3192,8 @@ var deployCommand = new Command49("deploy").description("put the app in this dir
3092
3192
  `);
3093
3193
  info(`${color.bold("Building")} ${color.cyan(config.name)} ${color.dim(`(${template.label})`)}`);
3094
3194
  const built = await template.build({ dir, pkg, dist: config.dist });
3095
- if (built.dist !== config.dist) await writeConfig(dir, { ...config, dist: built.dist });
3096
- const size = await shipping.measure(built.outDir);
3195
+ if (built.dist !== void 0 && built.dist !== config.dist) await writeConfig(dir, { ...config, dist: built.dist });
3196
+ const size = await shipping.measure(built.outDir, built.exclude);
3097
3197
  info("");
3098
3198
  const { box: found, ref, created } = await findOrCreate(client, config);
3099
3199
  const cert = await ensureCert(client, void 0, void 0, { owner: found.owner });
@@ -3101,17 +3201,18 @@ var deployCommand = new Command49("deploy").description("put the app in this dir
3101
3201
  const box = created || wasRunning ? found : await wakeUp(client, found, ref);
3102
3202
  const ep = sshEndpoint(box, client.target);
3103
3203
  if (!wasRunning) await spin(`waiting for ${ref} to accept SSH\u2026`, shipping.waitForSsh(ep));
3104
- const shipIt = () => shipping.ship(ep, built.outDir, template.remote());
3105
- let result = await spin(`shipping ${built.dist}/ (${size.files} files, ${gb(size.bytes)}) to ${ref}\u2026`, shipIt);
3204
+ const shipIt = () => shipping.ship(ep, built.outDir, template.remote(built), built.exclude);
3205
+ const doing = built.dist === void 0 ? `shipping ${built.what} (${size.files} files, ${gb(size.bytes)}) to ${ref} and building there, a minute or two\u2026` : `shipping ${built.what} (${size.files} files, ${gb(size.bytes)}) to ${ref}\u2026`;
3206
+ let result = await spin(doing, shipIt);
3106
3207
  if (result.status === SSH_FAILED3 && await renewAfterRefusal(client, cert, box.owner)) {
3107
3208
  info("renewed your SSH certificate, trying again\u2026");
3108
- result = await spin(`shipping ${built.dist}/ to ${ref}\u2026`, shipIt);
3209
+ result = await spin(doing, shipIt);
3109
3210
  }
3110
3211
  if (result.status === SSH_FAILED3) throw new KvboxError(`box ${ref}: could not reach it over SSH (still booting?)${result.output ? `
3111
3212
  ${result.output.trim()}` : ""}`, SSH_FAILED3);
3112
3213
  if (result.status === IMAGE_TOO_OLD) {
3113
3214
  throw new KvboxError(
3114
- `box ${ref} runs an image without nginx; ${template.label} needs base v0.8.0 or newer. Update the box (kvbox vm update ${ref}) once its host has the new image, or deploy under another name (--name) to get a fresh box`
3215
+ `box ${ref} runs an image too old for the ${template.label} template (base v0.8.0 or newer). Update the box (kvbox vm update ${ref}) once its host has the new image, or deploy under another name (--name) to get a fresh box`
3115
3216
  );
3116
3217
  }
3117
3218
  if (result.status !== 0) throw new KvboxError(`deploying to ${ref} failed (exit ${result.status}):
@@ -3120,10 +3221,10 @@ ${result.output.trim()}`);
3120
3221
  if (cloud) {
3121
3222
  const got = await address(client, box, ref, config, template);
3122
3223
  url = got.url;
3123
- if (got.subdomain !== config.subdomain) await writeConfig(dir, { ...config, subdomain: got.subdomain, ...built.dist !== config.dist ? { dist: built.dist } : {} });
3224
+ if (got.subdomain !== config.subdomain) await writeConfig(dir, { ...config, subdomain: got.subdomain, ...built.dist !== void 0 && built.dist !== config.dist ? { dist: built.dist } : {} });
3124
3225
  }
3125
3226
  if (opts.json) {
3126
- json({ box: ref, created, template: template.id, url, dist: built.dist, files: size.files, bytes: size.bytes });
3227
+ json({ box: ref, created, template: template.id, url, ...built.dist !== void 0 ? { dist: built.dist } : {}, files: size.files, bytes: size.bytes });
3127
3228
  return;
3128
3229
  }
3129
3230
  process.stdout.write(`${url}
@@ -3153,8 +3254,9 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
3153
3254
  template: opts.template ?? saved?.template ?? detected?.id,
3154
3255
  name: opts.name ?? saved?.name ?? nameFromDir(basename(dir)),
3155
3256
  subdomain: opts.sub ?? savedSub,
3156
- size: opts.size ?? saved?.size ?? DEFAULT_SIZE
3257
+ size: opts.size ?? saved?.size
3157
3258
  };
3259
+ const sizeFor = (t) => defaults.size ?? t?.size ?? "xs";
3158
3260
  const open = {
3159
3261
  template: opts.template === void 0 && saved?.template === void 0,
3160
3262
  name: opts.name === void 0 && saved?.name === void 0,
@@ -3166,7 +3268,7 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
3166
3268
  if (anyOpen && !opts.yes && !interactive()) {
3167
3269
  const sub = defaults.subdomain ?? subdomainFor(defaults.name);
3168
3270
  throw new KvboxError(
3169
- `kvbox deploy asks a few questions the first time, and this is not a terminal. Pass -y to take the defaults (${defaults.template ?? "template?"}, box ${defaults.name}${cloud ? `, https://${sub}.kvbox.app` : ""}, size ${defaults.size}), or answer with --template, --name${cloud ? ", --sub" : ""} and --size`
3271
+ `kvbox deploy asks a few questions the first time, and this is not a terminal. Pass -y to take the defaults (${defaults.template ?? "template?"}, box ${defaults.name}${cloud ? `, https://${sub}.kvbox.app` : ""}, size ${sizeFor(defaults.template !== void 0 ? templateById(defaults.template) : void 0)}), or answer with --template, --name${cloud ? ", --sub" : ""} and --size`
3170
3272
  );
3171
3273
  }
3172
3274
  const ask = anyOpen && !opts.yes;
@@ -3177,7 +3279,7 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
3177
3279
  const initial = detected ? TEMPLATES.indexOf(detected) : 0;
3178
3280
  templateId = await select("Template", TEMPLATES.map((t) => ({ value: t.id, label: t.label, hint: t === detected ? `${t.hint} \xB7 detected` : t.hint })), initial);
3179
3281
  }
3180
- if (templateId === void 0) throw new KvboxError(`could not tell what kind of app ${dir} is (no package.json of a Vite or Create React App project): say it with --template ${TEMPLATE_IDS.join("|")}`);
3282
+ if (templateId === void 0) throw new KvboxError(`could not tell what kind of app ${dir} is (no package.json of a Vite, Create React App or Next.js project): say it with --template ${TEMPLATE_IDS.join("|")}`);
3181
3283
  const template = templateById(templateId);
3182
3284
  let name = defaults.name;
3183
3285
  if (open.name && ask) name = await text("Box name", { default: defaults.name, validate: (a) => BOX_NAME_RE.test(a) ? void 0 : NAME_RULE });
@@ -3192,9 +3294,9 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
3192
3294
  });
3193
3295
  info(` ${color.dim(`https://${subdomain}.kvbox.app`)}`);
3194
3296
  }
3195
- let size = defaults.size;
3297
+ let size = sizeFor(template);
3196
3298
  if (open.size && ask) {
3197
- size = await select("Box size", SIZE_NAMES.map((s) => ({ value: s, label: s, hint: `${SIZES[s].vcpu} vCPU, ${SIZES[s].memMib / 1024} GB RAM, ${SIZES[s].diskGib} GB disk` })), SIZE_NAMES.indexOf(DEFAULT_SIZE));
3299
+ size = await select("Box size", SIZE_NAMES.map((s) => ({ value: s, label: s, hint: `${SIZES[s].vcpu} vCPU, ${SIZES[s].memMib / 1024} GB RAM, ${SIZES[s].diskGib} GB disk` })), SIZE_NAMES.indexOf(template.size));
3198
3300
  }
3199
3301
  const org = orgFlag ?? saved?.org;
3200
3302
  const config = {
@@ -3215,7 +3317,7 @@ async function findOrCreate(client, config) {
3215
3317
  let box = slug === void 0 ? named[0] : named.find((b) => slugOf(b.owner) === slug);
3216
3318
  let created = false;
3217
3319
  if (!box) {
3218
- box = await spin(`booting a new box ${config.name}\u2026`, client.createMachine({ name: config.name, ...config.size !== void 0 ? { size: config.size } : {} }));
3320
+ box = await spin(`booting a new box ${config.name}\u2026`, client.createMachine({ name: config.name, ...config.size !== void 0 ? { size: config.size } : {}, autoHibernateSec: DEPLOY_AUTO_HIBERNATE_SEC }));
3219
3321
  created = true;
3220
3322
  info(`${color.green("\u2713")} box ${color.bold(config.name)} is up: ${box.vcpu} vCPU, ${gb(box.memMib * 1024 ** 2)} RAM, ${gb(box.diskBytes)} disk`);
3221
3323
  }