@kvbox/cli 0.5.20 → 0.5.21
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 +142 -41
- package/dist/kvbox.js.map +1 -1
- package/package.json +1 -1
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.
|
|
167
|
+
var VERSION = versionOf(import.meta.url, true ? "0.5.21" : 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
|
|
2894
|
+
import { join as join4 } from "path";
|
|
2895
2895
|
import { z as z5 } from "zod";
|
|
2896
2896
|
|
|
2897
|
-
// src/commands/deploy/templates/
|
|
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,100 @@ 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
|
+
"export CI=1 NEXT_TELEMETRY_DISABLED=1 NODE_ENV=production",
|
|
2977
|
+
...c.setup,
|
|
2978
|
+
c.install,
|
|
2979
|
+
c.build,
|
|
2980
|
+
"cd /",
|
|
2981
|
+
`sudo -n tee /etc/systemd/system/${SERVICE}.service >/dev/null <<'UNIT'`,
|
|
2982
|
+
...unit,
|
|
2983
|
+
"UNIT",
|
|
2984
|
+
`if [ -e ${APP_DIR} ]; then mv ${APP_DIR} /srv/app/.old; fi`,
|
|
2985
|
+
`mv /srv/app/.incoming ${APP_DIR}`,
|
|
2986
|
+
"sudo -n systemctl daemon-reload",
|
|
2987
|
+
`sudo -n systemctl enable --quiet ${SERVICE}`,
|
|
2988
|
+
`sudo -n systemctl restart ${SERVICE}`,
|
|
2989
|
+
"rm -rf /srv/app/.old"
|
|
2990
|
+
].join("\n");
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
|
|
2994
|
+
// src/commands/deploy/templates/react.ts
|
|
2995
|
+
import { spawn as spawn4 } from "child_process";
|
|
2996
|
+
import { access as access2 } from "fs/promises";
|
|
2997
|
+
import { join as join3 } from "path";
|
|
2998
|
+
var OUT_DIRS = ["dist", "build"];
|
|
2909
2999
|
async function runBuild(dir, pm) {
|
|
2910
|
-
const args = pm
|
|
3000
|
+
const args = runScript(pm, "build");
|
|
2911
3001
|
info(`${color.dim("$")} ${pm} ${args.join(" ")}`);
|
|
2912
3002
|
const status2 = await new Promise((resolve3, reject) => {
|
|
2913
3003
|
const child = spawn4(pm, args, { cwd: dir, stdio: ["ignore", process.stderr, process.stderr], shell: process.platform === "win32" });
|
|
@@ -2920,6 +3010,7 @@ var reactTemplate = {
|
|
|
2920
3010
|
id: "react",
|
|
2921
3011
|
label: "React",
|
|
2922
3012
|
hint: "static site: built here, served by nginx in the box",
|
|
3013
|
+
size: "xs",
|
|
2923
3014
|
port: 80,
|
|
2924
3015
|
/** A Vite or Create React App project. A framework with its own server (Next.js, Remix, Expo) is not a static site. */
|
|
2925
3016
|
detect(pkg) {
|
|
@@ -2930,11 +3021,12 @@ var reactTemplate = {
|
|
|
2930
3021
|
},
|
|
2931
3022
|
async build(ctx) {
|
|
2932
3023
|
if (typeof ctx.pkg?.scripts?.["build"] !== "string") throw new KvboxError(`${ctx.dir}: package.json has no "build" script`);
|
|
2933
|
-
|
|
3024
|
+
const pm = await packageManager(ctx.dir);
|
|
3025
|
+
await runBuild(ctx.dir, pm);
|
|
2934
3026
|
const candidates = ctx.dist !== void 0 ? [ctx.dist] : OUT_DIRS;
|
|
2935
3027
|
for (const dist of candidates) {
|
|
2936
|
-
const outDir =
|
|
2937
|
-
if (await
|
|
3028
|
+
const outDir = join3(ctx.dir, dist);
|
|
3029
|
+
if (await access2(join3(outDir, "index.html")).then(() => true, () => false)) return { outDir, dist, exclude: [], pm, what: `${dist}/` };
|
|
2938
3030
|
}
|
|
2939
3031
|
throw new KvboxError(
|
|
2940
3032
|
`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 +3057,7 @@ var reactTemplate = {
|
|
|
2965
3057
|
};
|
|
2966
3058
|
|
|
2967
3059
|
// src/commands/deploy/templates/index.ts
|
|
2968
|
-
var TEMPLATES = [reactTemplate];
|
|
3060
|
+
var TEMPLATES = [reactTemplate, nextjsTemplate];
|
|
2969
3061
|
var TEMPLATE_IDS = TEMPLATES.map((t) => t.id);
|
|
2970
3062
|
function templateById(id) {
|
|
2971
3063
|
const t = TEMPLATES.find((x) => x.id === id);
|
|
@@ -2977,6 +3069,7 @@ function detectTemplate(pkg) {
|
|
|
2977
3069
|
}
|
|
2978
3070
|
|
|
2979
3071
|
// src/commands/deploy/config.ts
|
|
3072
|
+
var DEPLOY_AUTO_HIBERNATE_SEC = 3600;
|
|
2980
3073
|
var CONFIG_FILE = "kvbox.json";
|
|
2981
3074
|
var BOX_NAME_RE = /^[a-z0-9][a-z0-9-]{0,11}$/;
|
|
2982
3075
|
var DeployConfigSchema = z5.object({
|
|
@@ -2994,7 +3087,7 @@ var DeployConfigSchema = z5.object({
|
|
|
2994
3087
|
dist: z5.string().optional()
|
|
2995
3088
|
});
|
|
2996
3089
|
async function readConfig(dir) {
|
|
2997
|
-
const path =
|
|
3090
|
+
const path = join4(dir, CONFIG_FILE);
|
|
2998
3091
|
let raw;
|
|
2999
3092
|
try {
|
|
3000
3093
|
raw = await readFile6(path, "utf8");
|
|
@@ -3016,7 +3109,7 @@ async function readConfig(dir) {
|
|
|
3016
3109
|
return parsed.data;
|
|
3017
3110
|
}
|
|
3018
3111
|
async function writeConfig(dir, config) {
|
|
3019
|
-
await writeFile4(
|
|
3112
|
+
await writeFile4(join4(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
|
|
3020
3113
|
}
|
|
3021
3114
|
function nameFromDir(dirName) {
|
|
3022
3115
|
const slug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 12).replace(/-+$/, "");
|
|
@@ -3029,21 +3122,27 @@ function subdomainFor(name) {
|
|
|
3029
3122
|
// src/commands/deploy/ship.ts
|
|
3030
3123
|
import { spawn as spawn5 } from "child_process";
|
|
3031
3124
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
3032
|
-
import { join as
|
|
3125
|
+
import { join as join5 } from "path";
|
|
3033
3126
|
var IMAGE_TOO_OLD = 3;
|
|
3034
3127
|
var SSH_FAILED3 = 255;
|
|
3035
|
-
async function measure(dir) {
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3128
|
+
async function measure(dir, exclude = []) {
|
|
3129
|
+
const total = { files: 0, bytes: 0 };
|
|
3130
|
+
const walk = async (d) => {
|
|
3131
|
+
for (const entry of await readdir(d, { withFileTypes: true })) {
|
|
3132
|
+
if (exclude.includes(entry.name)) continue;
|
|
3133
|
+
const path = join5(d, entry.name);
|
|
3134
|
+
if (entry.isDirectory()) await walk(path);
|
|
3135
|
+
else if (entry.isFile()) {
|
|
3136
|
+
total.files += 1;
|
|
3137
|
+
total.bytes += (await stat2(path)).size;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
await walk(dir);
|
|
3142
|
+
return total;
|
|
3143
|
+
}
|
|
3144
|
+
async function ship(ep, dir, remote, exclude = []) {
|
|
3145
|
+
const tar = spawn5("tar", ["-cf", "-", ...exclude.map((e) => `--exclude=${e}`), "-C", dir, "."], { stdio: ["ignore", "pipe", "pipe"] });
|
|
3047
3146
|
const ssh = spawn5("ssh", [...sshArgs(ep), remote], { stdio: ["pipe", "pipe", "pipe"] });
|
|
3048
3147
|
tar.stdout.pipe(ssh.stdin);
|
|
3049
3148
|
const chunks = [];
|
|
@@ -3074,7 +3173,7 @@ async function ship(ep, dir, remote) {
|
|
|
3074
3173
|
var shipping = { ship, measure, waitForSsh };
|
|
3075
3174
|
|
|
3076
3175
|
// 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
|
|
3176
|
+
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
3177
|
const dir = resolve2(dirArg ?? ".");
|
|
3079
3178
|
const pkg = await readPackageJson(dir);
|
|
3080
3179
|
const saved = await readConfig(dir);
|
|
@@ -3092,8 +3191,8 @@ var deployCommand = new Command49("deploy").description("put the app in this dir
|
|
|
3092
3191
|
`);
|
|
3093
3192
|
info(`${color.bold("Building")} ${color.cyan(config.name)} ${color.dim(`(${template.label})`)}`);
|
|
3094
3193
|
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);
|
|
3194
|
+
if (built.dist !== void 0 && built.dist !== config.dist) await writeConfig(dir, { ...config, dist: built.dist });
|
|
3195
|
+
const size = await shipping.measure(built.outDir, built.exclude);
|
|
3097
3196
|
info("");
|
|
3098
3197
|
const { box: found, ref, created } = await findOrCreate(client, config);
|
|
3099
3198
|
const cert = await ensureCert(client, void 0, void 0, { owner: found.owner });
|
|
@@ -3101,17 +3200,18 @@ var deployCommand = new Command49("deploy").description("put the app in this dir
|
|
|
3101
3200
|
const box = created || wasRunning ? found : await wakeUp(client, found, ref);
|
|
3102
3201
|
const ep = sshEndpoint(box, client.target);
|
|
3103
3202
|
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
|
-
|
|
3203
|
+
const shipIt = () => shipping.ship(ep, built.outDir, template.remote(built), built.exclude);
|
|
3204
|
+
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`;
|
|
3205
|
+
let result = await spin(doing, shipIt);
|
|
3106
3206
|
if (result.status === SSH_FAILED3 && await renewAfterRefusal(client, cert, box.owner)) {
|
|
3107
3207
|
info("renewed your SSH certificate, trying again\u2026");
|
|
3108
|
-
result = await spin(
|
|
3208
|
+
result = await spin(doing, shipIt);
|
|
3109
3209
|
}
|
|
3110
3210
|
if (result.status === SSH_FAILED3) throw new KvboxError(`box ${ref}: could not reach it over SSH (still booting?)${result.output ? `
|
|
3111
3211
|
${result.output.trim()}` : ""}`, SSH_FAILED3);
|
|
3112
3212
|
if (result.status === IMAGE_TOO_OLD) {
|
|
3113
3213
|
throw new KvboxError(
|
|
3114
|
-
`box ${ref} runs an image
|
|
3214
|
+
`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
3215
|
);
|
|
3116
3216
|
}
|
|
3117
3217
|
if (result.status !== 0) throw new KvboxError(`deploying to ${ref} failed (exit ${result.status}):
|
|
@@ -3120,10 +3220,10 @@ ${result.output.trim()}`);
|
|
|
3120
3220
|
if (cloud) {
|
|
3121
3221
|
const got = await address(client, box, ref, config, template);
|
|
3122
3222
|
url = got.url;
|
|
3123
|
-
if (got.subdomain !== config.subdomain) await writeConfig(dir, { ...config, subdomain: got.subdomain, ...built.dist !== config.dist ? { dist: built.dist } : {} });
|
|
3223
|
+
if (got.subdomain !== config.subdomain) await writeConfig(dir, { ...config, subdomain: got.subdomain, ...built.dist !== void 0 && built.dist !== config.dist ? { dist: built.dist } : {} });
|
|
3124
3224
|
}
|
|
3125
3225
|
if (opts.json) {
|
|
3126
|
-
json({ box: ref, created, template: template.id, url, dist: built.dist, files: size.files, bytes: size.bytes });
|
|
3226
|
+
json({ box: ref, created, template: template.id, url, ...built.dist !== void 0 ? { dist: built.dist } : {}, files: size.files, bytes: size.bytes });
|
|
3127
3227
|
return;
|
|
3128
3228
|
}
|
|
3129
3229
|
process.stdout.write(`${url}
|
|
@@ -3153,8 +3253,9 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
|
|
|
3153
3253
|
template: opts.template ?? saved?.template ?? detected?.id,
|
|
3154
3254
|
name: opts.name ?? saved?.name ?? nameFromDir(basename(dir)),
|
|
3155
3255
|
subdomain: opts.sub ?? savedSub,
|
|
3156
|
-
size: opts.size ?? saved?.size
|
|
3256
|
+
size: opts.size ?? saved?.size
|
|
3157
3257
|
};
|
|
3258
|
+
const sizeFor = (t) => defaults.size ?? t?.size ?? "xs";
|
|
3158
3259
|
const open = {
|
|
3159
3260
|
template: opts.template === void 0 && saved?.template === void 0,
|
|
3160
3261
|
name: opts.name === void 0 && saved?.name === void 0,
|
|
@@ -3166,7 +3267,7 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
|
|
|
3166
3267
|
if (anyOpen && !opts.yes && !interactive()) {
|
|
3167
3268
|
const sub = defaults.subdomain ?? subdomainFor(defaults.name);
|
|
3168
3269
|
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.
|
|
3270
|
+
`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
3271
|
);
|
|
3171
3272
|
}
|
|
3172
3273
|
const ask = anyOpen && !opts.yes;
|
|
@@ -3177,7 +3278,7 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
|
|
|
3177
3278
|
const initial = detected ? TEMPLATES.indexOf(detected) : 0;
|
|
3178
3279
|
templateId = await select("Template", TEMPLATES.map((t) => ({ value: t.id, label: t.label, hint: t === detected ? `${t.hint} \xB7 detected` : t.hint })), initial);
|
|
3179
3280
|
}
|
|
3180
|
-
if (templateId === void 0) throw new KvboxError(`could not tell what kind of app ${dir} is (no package.json of a Vite
|
|
3281
|
+
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
3282
|
const template = templateById(templateId);
|
|
3182
3283
|
let name = defaults.name;
|
|
3183
3284
|
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 +3293,9 @@ async function settle(dir, pkg, saved, opts, orgFlag, cloud) {
|
|
|
3192
3293
|
});
|
|
3193
3294
|
info(` ${color.dim(`https://${subdomain}.kvbox.app`)}`);
|
|
3194
3295
|
}
|
|
3195
|
-
let size =
|
|
3296
|
+
let size = sizeFor(template);
|
|
3196
3297
|
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(
|
|
3298
|
+
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
3299
|
}
|
|
3199
3300
|
const org = orgFlag ?? saved?.org;
|
|
3200
3301
|
const config = {
|
|
@@ -3215,7 +3316,7 @@ async function findOrCreate(client, config) {
|
|
|
3215
3316
|
let box = slug === void 0 ? named[0] : named.find((b) => slugOf(b.owner) === slug);
|
|
3216
3317
|
let created = false;
|
|
3217
3318
|
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 } : {} }));
|
|
3319
|
+
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
3320
|
created = true;
|
|
3220
3321
|
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
3322
|
}
|