@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 +143 -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.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
|
|
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,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
|
|
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
|
-
|
|
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 =
|
|
2937
|
-
if (await
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
}
|