vela 0.11.3 → 0.11.4

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/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin.ts
4
- import process38 from "node:process";
4
+ import process39 from "node:process";
5
5
 
6
6
  // src/lib/argv.ts
7
7
  function normalizeArgv(argv) {
@@ -11,7 +11,7 @@ function normalizeArgv(argv) {
11
11
  }
12
12
 
13
13
  // src/program.ts
14
- import process37 from "node:process";
14
+ import process38 from "node:process";
15
15
  import * as p61 from "@clack/prompts";
16
16
  import { Command as Command104 } from "commander";
17
17
  import nodePath from "node:path";
@@ -21,7 +21,7 @@ import pc38 from "picocolors";
21
21
  // package.json
22
22
  var package_default = {
23
23
  name: "vela",
24
- version: "0.11.3",
24
+ version: "0.11.4",
25
25
  type: "module",
26
26
  description: "A CLI for creating and updating SvelteKit projects",
27
27
  license: "MIT",
@@ -45,12 +45,14 @@ var package_default = {
45
45
  build: "node --experimental-strip-types scripts/build.ts",
46
46
  dev: "node --experimental-strip-types scripts/build.ts --watch",
47
47
  clean: "rm -rf dist",
48
- lint: "npm run lint:ts && npm run lint:format",
48
+ lint: "npm run lint:ts && npm run lint:format && npm run lint:sh",
49
49
  "lint:ts": "tsc --noEmit",
50
50
  "lint:format": "prettier --check .",
51
51
  format: "prettier --write .",
52
52
  test: "vitest run",
53
- prepare: "npm run build"
53
+ prepare: "npm run build",
54
+ "lint:sh": "shellcheck -x -P SCRIPTDIR -S warning templates/server/*.sh",
55
+ "test:scripts": "bash scripts/test-server-scripts.sh"
54
56
  },
55
57
  dependencies: {
56
58
  "@clack/prompts": "^1.7.0",
@@ -83,7 +85,8 @@ var package_default = {
83
85
  prettier: "^3.9.6",
84
86
  typescript: "^5.6.0",
85
87
  vite: "^8.2.2",
86
- vitest: "^4.1.11"
88
+ vitest: "^4.1.11",
89
+ shellcheck: "^4.1.0"
87
90
  },
88
91
  peerDependencies: {
89
92
  "@sveltejs/kit": "^2.57.1",
@@ -2996,13 +2999,13 @@ var notifications = new Command18("notifications").description("enable in-app no
2996
2999
  );
2997
3000
 
2998
3001
  // src/commands/enable/s3.ts
2999
- import process12 from "node:process";
3002
+ import process13 from "node:process";
3000
3003
  import { Command as Command19 } from "commander";
3001
3004
  import * as p17 from "@clack/prompts";
3002
3005
  import pc8 from "picocolors";
3003
3006
 
3004
3007
  // src/lib/server-command.ts
3005
- import process11 from "node:process";
3008
+ import process12 from "node:process";
3006
3009
  import * as v6 from "valibot";
3007
3010
  import * as p15 from "@clack/prompts";
3008
3011
  import pc5 from "picocolors";
@@ -3133,6 +3136,7 @@ function writeBinding(workspaceRootDir, envTag, binding) {
3133
3136
  }
3134
3137
 
3135
3138
  // src/lib/instance.ts
3139
+ import crypto2 from "node:crypto";
3136
3140
  var PROD_ENV = "prod";
3137
3141
  var MAX_SEGMENT = 48;
3138
3142
  function normalizeEnvTag(tag) {
@@ -3145,9 +3149,14 @@ function normalizeEnvTag(tag) {
3145
3149
  }
3146
3150
  return normalized === "production" ? PROD_ENV : normalized;
3147
3151
  }
3152
+ var SLUG_HASH = 6;
3148
3153
  function branchToEnvTag(branch) {
3149
- const slug2 = branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SEGMENT);
3150
- return `preview--${slug2}`;
3154
+ const lower = branch.toLowerCase();
3155
+ const slug2 = lower.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3156
+ if (slug2 === branch && slug2.length <= MAX_SEGMENT) return `preview--${slug2}`;
3157
+ const hash = crypto2.createHash("sha256").update(branch).digest("hex").slice(0, SLUG_HASH);
3158
+ const head = slug2.slice(0, MAX_SEGMENT - SLUG_HASH - 1).replace(/-+$/, "");
3159
+ return `preview--${head}-${hash}`;
3151
3160
  }
3152
3161
  function instanceId(appId, envTag = PROD_ENV) {
3153
3162
  const env2 = normalizeEnvTag(envTag);
@@ -3156,8 +3165,12 @@ function instanceId(appId, envTag = PROD_ENV) {
3156
3165
  function isProd(envTag) {
3157
3166
  return normalizeEnvTag(envTag) === PROD_ENV;
3158
3167
  }
3159
- function releaseId(date = /* @__PURE__ */ new Date()) {
3160
- return date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
3168
+ function releaseId(date = /* @__PURE__ */ new Date(), suffix = randomSuffix()) {
3169
+ const stamp = date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
3170
+ return `${stamp}-${suffix}`;
3171
+ }
3172
+ function randomSuffix() {
3173
+ return crypto2.randomBytes(2).toString("hex");
3161
3174
  }
3162
3175
 
3163
3176
  // src/lib/artifact.ts
@@ -3170,7 +3183,7 @@ import { resolveCommand as resolveCommand4 } from "package-manager-detector/comm
3170
3183
  import fs20 from "node:fs";
3171
3184
  import os3 from "node:os";
3172
3185
  import path19 from "node:path";
3173
- import crypto2 from "node:crypto";
3186
+ import crypto3 from "node:crypto";
3174
3187
  import process10 from "node:process";
3175
3188
  import { spawn as spawn2 } from "node:child_process";
3176
3189
  var RemoteCommandError = class extends Error {
@@ -3214,7 +3227,7 @@ var SshSession = class {
3214
3227
  if (this.controlPath) return;
3215
3228
  const dir = path19.join(os3.tmpdir(), "vela-ssh");
3216
3229
  fs20.mkdirSync(dir, { recursive: true, mode: 448 });
3217
- const socket = path19.join(dir, `${crypto2.randomBytes(6).toString("hex")}.sock`);
3230
+ const socket = path19.join(dir, `${crypto3.randomBytes(6).toString("hex")}.sock`);
3218
3231
  this.controlPath = socket;
3219
3232
  const args = [
3220
3233
  ...this.sshArgs(),
@@ -3562,24 +3575,75 @@ function sshOptionsFrom(options) {
3562
3575
  }
3563
3576
 
3564
3577
  // src/lib/remote.ts
3578
+ import crypto4 from "node:crypto";
3579
+ import fs22 from "node:fs";
3565
3580
  import path21 from "node:path";
3581
+ import process11 from "node:process";
3566
3582
  var VELA_ROOT = "/var/lib/vela";
3567
3583
  var VELA_ETC = "/etc/vela";
3568
3584
  var VELA_USER = "vela";
3569
- var SCRIPTS_DIR = `${VELA_ROOT}/scripts`;
3585
+ var SCRIPT_VERSIONS_DIR = `${VELA_ROOT}/script-versions`;
3570
3586
  var PROVISIONED_MARKER = `${VELA_ETC}/provisioned`;
3571
3587
  var ORIGIN_FILE = `${VELA_ETC}/origin.json`;
3572
3588
  function serverTemplatesDir() {
3573
3589
  return path21.join(templatesDir(), "server");
3574
3590
  }
3591
+ var DIGEST_LENGTH = 12;
3592
+ function serverScriptsDigest(dir = serverTemplatesDir()) {
3593
+ const hash = crypto4.createHash("sha256");
3594
+ for (const file of listFiles(dir).sort()) {
3595
+ hash.update(file).update("\0").update(fs22.readFileSync(path21.join(dir, file))).update("\0");
3596
+ }
3597
+ return hash.digest("hex").slice(0, DIGEST_LENGTH);
3598
+ }
3599
+ function listFiles(root, prefix = "") {
3600
+ const files = [];
3601
+ for (const entry of fs22.readdirSync(path21.join(root, prefix), { withFileTypes: true })) {
3602
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
3603
+ if (entry.isDirectory()) files.push(...listFiles(root, rel));
3604
+ else if (entry.isFile()) files.push(rel);
3605
+ }
3606
+ return files;
3607
+ }
3608
+ var scriptDirs = /* @__PURE__ */ new WeakMap();
3575
3609
  async function syncServerScripts(session) {
3576
- await session.script(`mkdir -p "$1" && chmod 0755 "$1"`, { args: [SCRIPTS_DIR] });
3577
- await session.uploadDir(serverTemplatesDir(), SCRIPTS_DIR, ["--chmod=D755,F755"]);
3578
- await session.script(`chown -R root:root "$1"`, { args: [SCRIPTS_DIR] });
3610
+ const digest = serverScriptsDigest();
3611
+ const dir = `${SCRIPT_VERSIONS_DIR}/${digest}`;
3612
+ const present = await session.script(`[ -d "$1" ] && echo yes || echo no`, { args: [dir] });
3613
+ if (present.stdout.trim() !== "yes") {
3614
+ const incoming = `${SCRIPT_VERSIONS_DIR}/.incoming.${digest}.${process11.pid}.${Date.now()}`;
3615
+ await session.script(`mkdir -p "$1" && chmod 0755 "$(dirname "$1")" "$1"`, {
3616
+ args: [incoming]
3617
+ });
3618
+ await session.uploadDir(serverTemplatesDir(), incoming, ["--chmod=D755,F755"]);
3619
+ await session.script(
3620
+ `chown -R root:root "$1"
3621
+ if [ -d "$2" ]; then rm -rf "$1"; else mv -T "$1" "$2" || { rm -rf "$1"; [ -d "$2" ]; }; fi`,
3622
+ { args: [incoming, dir] }
3623
+ );
3624
+ }
3625
+ await session.script(
3626
+ `touch "$1"
3627
+ ln -sfn "$1" "$2/.current.tmp" && mv -Tf "$2/.current.tmp" "$2/current"
3628
+ find "$2" -mindepth 1 -maxdepth 1 -type d -name '[0-9a-f]*' ! -name "$(basename "$1")" -mtime +30 -exec rm -rf {} + 2>/dev/null || true
3629
+ find "$2" -mindepth 1 -maxdepth 1 -type d -name '.incoming.*' -mmin +120 -exec rm -rf {} + 2>/dev/null || true`,
3630
+ { args: [dir, SCRIPT_VERSIONS_DIR] }
3631
+ );
3632
+ scriptDirs.set(session, dir);
3633
+ return dir;
3634
+ }
3635
+ async function serverTime(session) {
3636
+ const result = await session.script(`date -u +%s`);
3637
+ const seconds = Number(result.stdout.trim());
3638
+ if (!Number.isFinite(seconds) || seconds <= 0) {
3639
+ throw new Error(`could not read the clock on ${session.target}`);
3640
+ }
3641
+ return new Date(seconds * 1e3);
3579
3642
  }
3580
3643
  async function runServerScript(session, name, opts = {}) {
3644
+ const dir = scriptDirs.get(session) ?? `${SCRIPT_VERSIONS_DIR}/current`;
3581
3645
  const result = await session.script(`script="$1"; shift; exec "$script" "$@"`, {
3582
- args: [`${SCRIPTS_DIR}/${name}`, ...opts.args ?? []],
3646
+ args: [`${dir}/${name}`, ...opts.args ?? []],
3583
3647
  stream: opts.stream
3584
3648
  });
3585
3649
  return parseResult(result);
@@ -3653,7 +3717,7 @@ var remotePaths = {
3653
3717
  };
3654
3718
 
3655
3719
  // src/lib/remote-env.ts
3656
- import fs22 from "node:fs";
3720
+ import fs23 from "node:fs";
3657
3721
  import dotenv from "dotenv";
3658
3722
  var KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
3659
3723
  function isValidKey(key) {
@@ -3701,7 +3765,7 @@ function quote(value) {
3701
3765
  return `"${escaped}"`;
3702
3766
  }
3703
3767
  function readLocalEnvFile(file) {
3704
- const parsed = dotenv.parse(fs22.readFileSync(file));
3768
+ const parsed = dotenv.parse(fs23.readFileSync(file));
3705
3769
  const result = {};
3706
3770
  for (const [key, value] of Object.entries(parsed)) {
3707
3771
  if (isValidKey(key)) result[key] = value;
@@ -3817,6 +3881,20 @@ var SERVER_OPTIONS_SCHEMA = {
3817
3881
  server: v6.optional(v6.string())
3818
3882
  };
3819
3883
  var OptionsSchema = v6.object(SERVER_OPTIONS_SCHEMA);
3884
+ var DEFAULT_LOCK_WAIT = "300";
3885
+ var LOCK_WAIT_SCHEMA = {
3886
+ lockWait: v6.optional(v6.pipe(v6.string(), v6.regex(/^\d+$/, "must be a whole number of seconds")))
3887
+ };
3888
+ function addLockWaitOption(command) {
3889
+ return command.option(
3890
+ "--lock-wait <seconds>",
3891
+ "how long to wait for another operation on this target to finish before giving up",
3892
+ DEFAULT_LOCK_WAIT
3893
+ );
3894
+ }
3895
+ function lockWaitArgs(lockWait) {
3896
+ return ["--lock-wait", lockWait ?? DEFAULT_LOCK_WAIT];
3897
+ }
3820
3898
  function addTargetOptions(command, fallback) {
3821
3899
  return addSshOptions(command).option("-t, --target <target>", "which copy of the app to act on", fallback).option("--server <ssh>", "server this target runs on \u2014 recorded on first use");
3822
3900
  }
@@ -3883,6 +3961,7 @@ async function ensureBinding(workspaceRootDir, target, request = {}) {
3883
3961
  const key = bindingKey(target);
3884
3962
  const existing = readBinding(workspaceRootDir, key);
3885
3963
  const moving = Boolean(request.server && existing && existing.server !== request.server);
3964
+ if (moving) await confirmMove(target, existing.server, request.server);
3886
3965
  let server = request.server ?? existing?.server;
3887
3966
  if (!server) {
3888
3967
  server = await promptServer(target);
@@ -3905,12 +3984,31 @@ longer reach it. Remove it there first if that was not intended.`
3905
3984
  }
3906
3985
  return binding;
3907
3986
  }
3987
+ async function confirmMove(target, from, to) {
3988
+ const name = bindingName(target);
3989
+ if (!process12.stdout.isTTY || process12.env.CI) {
3990
+ throw new Error(
3991
+ `${pc5.cyan(`--server ${to}`)} does not match the server recorded for ${pc5.cyan(name)}, ${pc5.cyan(from)}.
3992
+
3993
+ Drop ${pc5.cyan("--server")} to use the recorded one, or move the binding on purpose by running
3994
+ this command once from a terminal (or editing .vela/project.json).`
3995
+ );
3996
+ }
3997
+ const ok = await p15.confirm({
3998
+ message: `${name} ${target.kind === "preview" ? "run" : "runs"} on ${from}. Point ${name} at ${to} instead?`,
3999
+ initialValue: false
4000
+ });
4001
+ if (p15.isCancel(ok) || !ok) {
4002
+ p15.cancel("Operation cancelled.");
4003
+ process12.exit(0);
4004
+ }
4005
+ }
3908
4006
  function bindingName(target) {
3909
4007
  return target.kind === "preview" ? "previews" : target.name;
3910
4008
  }
3911
4009
  async function promptServer(target) {
3912
4010
  const name = bindingName(target);
3913
- if (!process11.stdout.isTTY || process11.env.CI) {
4011
+ if (!process12.stdout.isTTY || process12.env.CI) {
3914
4012
  throw new Error(
3915
4013
  `${name} ${target.kind === "preview" ? "are" : "is"} not bound to a server yet, and there is no terminal to ask on.
3916
4014
 
@@ -3928,19 +4026,19 @@ and commit ${pc5.cyan(".vela/project.json")}.`
3928
4026
  });
3929
4027
  if (p15.isCancel(value)) {
3930
4028
  p15.cancel("Operation cancelled.");
3931
- process11.exit(0);
4029
+ process12.exit(0);
3932
4030
  }
3933
4031
  return value.trim();
3934
4032
  }
3935
4033
  async function promptDomain(target) {
3936
- if (!process11.stdout.isTTY || process11.env.CI) return void 0;
4034
+ if (!process12.stdout.isTTY || process12.env.CI) return void 0;
3937
4035
  const value = await p15.text({
3938
4036
  message: `Which hostname should ${target.name} be served on?`,
3939
4037
  placeholder: "example.com \u2014 leave blank to decide later"
3940
4038
  });
3941
4039
  if (p15.isCancel(value)) {
3942
4040
  p15.cancel("Operation cancelled.");
3943
- process11.exit(0);
4041
+ process12.exit(0);
3944
4042
  }
3945
4043
  return value.trim() || void 0;
3946
4044
  }
@@ -3996,17 +4094,17 @@ function envFilePath(workspaceRootDir) {
3996
4094
  import pc7 from "picocolors";
3997
4095
 
3998
4096
  // src/lib/local-env.ts
3999
- import fs23 from "node:fs";
4097
+ import fs24 from "node:fs";
4000
4098
  import * as p16 from "@clack/prompts";
4001
4099
  import pc6 from "picocolors";
4002
4100
  function readLocalEnv(envFile) {
4003
- if (!fs23.existsSync(envFile)) return {};
4101
+ if (!fs24.existsSync(envFile)) return {};
4004
4102
  return readLocalEnvFile(envFile);
4005
4103
  }
4006
4104
  function editLocalEnv(envFile, edit) {
4007
- const before = fs23.existsSync(envFile) ? fs23.readFileSync(envFile, "utf8") : "";
4105
+ const before = fs24.existsSync(envFile) ? fs24.readFileSync(envFile, "utf8") : "";
4008
4106
  const after = edit(before);
4009
- if (after !== before) fs23.writeFileSync(envFile, after);
4107
+ if (after !== before) fs24.writeFileSync(envFile, after);
4010
4108
  }
4011
4109
  function setLocalEnv(envFile, key, value) {
4012
4110
  editLocalEnv(envFile, (content) => upsertEnvVar(content, key, value));
@@ -4138,7 +4236,7 @@ Set ${pc7.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc7.cyan("POCKETBASE_SUPERUS
4138
4236
  }
4139
4237
 
4140
4238
  // src/lib/s3-settings.ts
4141
- import fs24 from "node:fs";
4239
+ import fs25 from "node:fs";
4142
4240
  import path22 from "node:path";
4143
4241
  var VIRTUAL_HOSTED = [/\.amazonaws\.com$/i, /\.r2\.cloudflarestorage\.com$/i];
4144
4242
  function defaultForcePathStyle(endpoint) {
@@ -4182,7 +4280,7 @@ async function hasLocalUploads(session, instance, workspaceRootDir) {
4182
4280
  function hasFile(dir) {
4183
4281
  let entries;
4184
4282
  try {
4185
- entries = fs24.readdirSync(dir, { withFileTypes: true });
4283
+ entries = fs25.readdirSync(dir, { withFileTypes: true });
4186
4284
  } catch {
4187
4285
  return false;
4188
4286
  }
@@ -4261,8 +4359,8 @@ async function promptConfig(options, existing) {
4261
4359
  const bucket = options.bucket ?? await text7("S3 bucket name", existing.bucket);
4262
4360
  const region = options.region ?? await text7("S3 region", existing.region || "us-east-1");
4263
4361
  const accessKey = options.accessKey ?? await text7("S3 access key", existing.accessKey);
4264
- const secret = options.secret ?? process12.env.VELA_S3_SECRET ?? await password5("S3 secret key");
4265
- const forcePathStyle = options.forcePathStyle ?? await confirm3(
4362
+ const secret = options.secret ?? process13.env.VELA_S3_SECRET ?? await password5("S3 secret key");
4363
+ const forcePathStyle = options.forcePathStyle ?? await confirm4(
4266
4364
  "Address the bucket in the path? (MinIO, Ceph and most self-hosted gateways need this)",
4267
4365
  defaultForcePathStyle(endpoint)
4268
4366
  );
@@ -4276,7 +4374,7 @@ async function text7(message, initialValue = "") {
4276
4374
  });
4277
4375
  if (p17.isCancel(value)) {
4278
4376
  p17.cancel("Operation cancelled.");
4279
- process12.exit(0);
4377
+ process13.exit(0);
4280
4378
  }
4281
4379
  return value.trim();
4282
4380
  }
@@ -4287,21 +4385,21 @@ async function password5(message) {
4287
4385
  });
4288
4386
  if (p17.isCancel(value)) {
4289
4387
  p17.cancel("Operation cancelled.");
4290
- process12.exit(0);
4388
+ process13.exit(0);
4291
4389
  }
4292
4390
  return value;
4293
4391
  }
4294
- async function confirm3(message, initialValue) {
4392
+ async function confirm4(message, initialValue) {
4295
4393
  const value = await p17.confirm({ message, initialValue });
4296
4394
  if (p17.isCancel(value)) {
4297
4395
  p17.cancel("Operation cancelled.");
4298
- process12.exit(0);
4396
+ process13.exit(0);
4299
4397
  }
4300
4398
  return value;
4301
4399
  }
4302
4400
 
4303
4401
  // src/commands/enable/smtp.ts
4304
- import process13 from "node:process";
4402
+ import process14 from "node:process";
4305
4403
  import { Command as Command20 } from "commander";
4306
4404
  import * as p18 from "@clack/prompts";
4307
4405
  var smtp = new Command20("smtp").description("configure SMTP for transactional email").configureHelp(helpConfig).action(
@@ -4330,7 +4428,7 @@ var smtp = new Command20("smtp").description("configure SMTP for transactional e
4330
4428
  {
4331
4429
  onCancel: () => {
4332
4430
  p18.cancel("Operation cancelled.");
4333
- process13.exit(0);
4431
+ process14.exit(0);
4334
4432
  }
4335
4433
  }
4336
4434
  );
@@ -4357,7 +4455,7 @@ var smtp = new Command20("smtp").description("configure SMTP for transactional e
4357
4455
  );
4358
4456
 
4359
4457
  // src/commands/enable/cms.ts
4360
- import process14 from "node:process";
4458
+ import process15 from "node:process";
4361
4459
  import { Command as Command21 } from "commander";
4362
4460
  import * as p19 from "@clack/prompts";
4363
4461
  import pc9 from "picocolors";
@@ -4374,7 +4472,7 @@ Run ${pc9.cyan("vela bless")} to add a backend, or point at a hosted CMS with ${
4374
4472
  );
4375
4473
  p19.log.message();
4376
4474
  p19.cancel("Operation failed.");
4377
- process14.exitCode = 1;
4475
+ process15.exitCode = 1;
4378
4476
  return;
4379
4477
  }
4380
4478
  await runPattern("enable-cms", cmd.args, opts.endpoint ? { endpoint: opts.endpoint } : {}, {
@@ -4454,7 +4552,7 @@ import { Command as Command35 } from "commander";
4454
4552
  import { Command as Command25 } from "commander";
4455
4553
 
4456
4554
  // src/commands/disable/_shared.ts
4457
- import process15 from "node:process";
4555
+ import process16 from "node:process";
4458
4556
  import * as p20 from "@clack/prompts";
4459
4557
  async function runDisable(opts, flags, cmdArgs) {
4460
4558
  if (!flags.yes) {
@@ -4464,7 +4562,7 @@ async function runDisable(opts, flags, cmdArgs) {
4464
4562
  });
4465
4563
  if (p20.isCancel(ok) || !ok) {
4466
4564
  p20.cancel("Operation cancelled.");
4467
- process15.exit(0);
4565
+ process16.exit(0);
4468
4566
  }
4469
4567
  }
4470
4568
  await runPattern(opts.slug, cmdArgs, { destructive: true }, opts.report);
@@ -4735,14 +4833,14 @@ import { Command as Command41 } from "commander";
4735
4833
  import { Command as Command36 } from "commander";
4736
4834
 
4737
4835
  // src/commands/destroy/_shared.ts
4738
- import process16 from "node:process";
4836
+ import process17 from "node:process";
4739
4837
  import * as p22 from "@clack/prompts";
4740
4838
  async function runDestroy(slug2, model, confirmMessage, report4, flags) {
4741
4839
  if (!flags.yes) {
4742
4840
  const ok = await p22.confirm({ message: confirmMessage, initialValue: false });
4743
4841
  if (p22.isCancel(ok) || !ok) {
4744
4842
  p22.cancel("Operation cancelled.");
4745
- process16.exit(0);
4843
+ process17.exit(0);
4746
4844
  }
4747
4845
  }
4748
4846
  await runPattern(slug2, [model], { destructive: true, route: flags.route }, report4);
@@ -4845,11 +4943,55 @@ var scaffold2 = new Command39("scaffold").description("destroy a scaffold genera
4845
4943
  );
4846
4944
 
4847
4945
  // src/commands/destroy/deployment.ts
4848
- import process17 from "node:process";
4946
+ import process18 from "node:process";
4849
4947
  import { Command as Command40 } from "commander";
4850
4948
  import * as p25 from "@clack/prompts";
4851
4949
  import pc13 from "picocolors";
4852
4950
 
4951
+ // src/lib/destroy-guard.ts
4952
+ function isPreview(envTag) {
4953
+ return envTag.startsWith("preview--");
4954
+ }
4955
+ function destroyDecision(request) {
4956
+ const { appName, purge, yes, confirm: confirm14, interactive } = request;
4957
+ const dangerous = isProd(request.envTag) || purge && !isPreview(request.envTag);
4958
+ const what = describe(request);
4959
+ if (confirm14 !== void 0) {
4960
+ if (confirm14 === appName) return { kind: "proceed" };
4961
+ return {
4962
+ kind: "refuse",
4963
+ message: `\`--confirm ${confirm14}\` does not name this app. Removing ${what} takes \`--confirm ${appName}\`.`
4964
+ };
4965
+ }
4966
+ if (dangerous) {
4967
+ if (yes) {
4968
+ return {
4969
+ kind: "refuse",
4970
+ message: `\`--yes\` is not enough to remove ${what}.
4971
+
4972
+ Pass \`--confirm ${appName}\` as well \u2014 the app name, the same thing a terminal would ask you to type.`
4973
+ };
4974
+ }
4975
+ if (interactive) return { kind: "prompt", byName: true };
4976
+ return {
4977
+ kind: "refuse",
4978
+ message: `Removing ${what} needs a confirmation, and there is no terminal to ask on.
4979
+
4980
+ Pass \`--confirm ${appName}\` \u2014 the app name, the same thing a terminal would ask you to type.`
4981
+ };
4982
+ }
4983
+ if (yes) return { kind: "proceed" };
4984
+ if (interactive) return { kind: "prompt", byName: false };
4985
+ return {
4986
+ kind: "refuse",
4987
+ message: `Removing ${what} needs a confirmation, and there is no terminal to ask on. Pass \`--yes\`.`
4988
+ };
4989
+ }
4990
+ function describe({ appName, envTag, purge }) {
4991
+ const target = isProd(envTag) ? "production" : `the ${envTag} environment`;
4992
+ return purge ? `${target} of ${appName} and its database` : `${target} of ${appName}`;
4993
+ }
4994
+
4853
4995
  // src/lib/deploy-report.ts
4854
4996
  import * as p24 from "@clack/prompts";
4855
4997
  import pc12 from "picocolors";
@@ -5042,26 +5184,52 @@ ${pc12.dim(err instanceof Error ? err.message : String(err))}`
5042
5184
  }
5043
5185
 
5044
5186
  // src/commands/destroy/deployment.ts
5045
- var deployment = addTargetOptions(
5046
- new Command40("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
5047
- "production"
5048
- ).option("--purge", "also delete the database and uploaded files").option("-y, --yes", "skip the confirmation prompt").action(
5187
+ var deployment = addLockWaitOption(
5188
+ addTargetOptions(
5189
+ new Command40("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
5190
+ "production"
5191
+ )
5192
+ ).option("--purge", "also delete the database and uploaded files").option("-y, --yes", "skip the confirmation prompt").option(
5193
+ "--confirm <app-name>",
5194
+ "confirm removing production, or a purge, by typing the app name \u2014 what a terminal would ask for"
5195
+ ).action(
5049
5196
  (raw) => runCommand(async () => {
5050
5197
  const options = raw;
5051
5198
  await withTarget(
5052
5199
  raw,
5053
5200
  {
5054
5201
  remote: async (ctx) => {
5055
- if (!options.yes) {
5056
- await confirm7(ctx.appName, ctx.targetName, ctx.envTag, options.purge === true);
5202
+ const decision = destroyDecision({
5203
+ appName: ctx.appName,
5204
+ envTag: ctx.envTag,
5205
+ purge: options.purge === true,
5206
+ yes: options.yes === true,
5207
+ confirm: options.confirm,
5208
+ interactive: Boolean(process18.stdin.isTTY && process18.stdout.isTTY)
5209
+ });
5210
+ if (decision.kind === "refuse") throw new Error(decision.message);
5211
+ if (decision.kind === "prompt") {
5212
+ await confirm8(ctx.appName, ctx.targetName, decision.byName);
5057
5213
  }
5058
5214
  const result = await runServerScript(ctx.session, "destroy.sh", {
5059
- args: [ctx.instance, ...options.purge ? ["--purge"] : []],
5215
+ args: [
5216
+ ctx.instance,
5217
+ ...options.purge ? ["--purge"] : [],
5218
+ ...lockWaitArgs(options.lockWait)
5219
+ ],
5060
5220
  stream: true
5061
5221
  });
5222
+ if (result?.existed === false) {
5223
+ p25.log.info(
5224
+ `Nothing named ${pc13.cyan(`${ctx.appName} (${ctx.targetName})`)} on ${ctx.server}; nothing to remove.`
5225
+ );
5226
+ return;
5227
+ }
5062
5228
  await reportEnvironmentDestroyed(ctx.workspaceRootDir, ctx.envTag);
5063
5229
  p25.log.success(
5064
- `Removed ${pc13.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${ctx.server}.` + (result?.purged ? "" : `
5230
+ `Removed ${pc13.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${ctx.server}.` + (result?.purged ? result.trash ? `
5231
+
5232
+ A snapshot of its data is at ${pc13.cyan(result.trash)} on the server for two weeks.` : "" : `
5065
5233
 
5066
5234
  The database and uploads are still in the instance's shared directory. Pass ${pc13.cyan("--purge")} to delete them.`)
5067
5235
  );
@@ -5074,15 +5242,15 @@ The database and uploads are still in the instance's shared directory. Pass ${pc
5074
5242
  );
5075
5243
  }, "Failed to remove the deployment.")
5076
5244
  );
5077
- async function confirm7(appName, targetName, envTag, purge) {
5078
- if (isProd(envTag) || purge) {
5245
+ async function confirm8(appName, targetName, byName) {
5246
+ if (byName) {
5079
5247
  const answer = await p25.text({
5080
- message: `This removes ${pc13.cyan(`${appName} (${targetName})`)}${purge ? " and its database" : ""}. Type the app name to confirm`,
5248
+ message: `This removes ${pc13.cyan(`${appName} (${targetName})`)}. Type the app name to confirm`,
5081
5249
  validate: (value) => value === appName ? void 0 : `Type ${appName} to confirm`
5082
5250
  });
5083
5251
  if (p25.isCancel(answer)) {
5084
5252
  p25.cancel("Operation cancelled.");
5085
- process17.exit(0);
5253
+ process18.exit(0);
5086
5254
  }
5087
5255
  return;
5088
5256
  }
@@ -5092,7 +5260,7 @@ async function confirm7(appName, targetName, envTag, purge) {
5092
5260
  });
5093
5261
  if (p25.isCancel(ok) || !ok) {
5094
5262
  p25.cancel("Operation cancelled.");
5095
- process17.exit(0);
5263
+ process18.exit(0);
5096
5264
  }
5097
5265
  }
5098
5266
 
@@ -5376,13 +5544,13 @@ var ui = new Command47("ui").description("generate ui components").configureHelp
5376
5544
  import { Command as Command50 } from "commander";
5377
5545
 
5378
5546
  // src/commands/legal/terms.ts
5379
- import fs25 from "node:fs";
5547
+ import fs26 from "node:fs";
5380
5548
  import path23 from "node:path";
5381
5549
  import { Command as Command48 } from "commander";
5382
5550
  import * as p32 from "@clack/prompts";
5383
5551
 
5384
5552
  // src/commands/legal/shared.ts
5385
- import process18 from "node:process";
5553
+ import process19 from "node:process";
5386
5554
  import * as p31 from "@clack/prompts";
5387
5555
  var sharedFields = {
5388
5556
  websiteUrl: () => p31.text({
@@ -5451,7 +5619,7 @@ var sharedFields = {
5451
5619
  };
5452
5620
  var onCancel = () => {
5453
5621
  p31.cancel("Operation cancelled.");
5454
- process18.exit(0);
5622
+ process19.exit(0);
5455
5623
  };
5456
5624
  async function contactMethods(type) {
5457
5625
  const selection = await p31.multiselect({
@@ -6044,9 +6212,9 @@ async function termsAction() {
6044
6212
  "+page.svelte"
6045
6213
  );
6046
6214
  const termsPageTs = path23.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
6047
- fs25.mkdirSync(path23.dirname(termsPage), { recursive: true });
6048
- fs25.writeFileSync(termsPage, html);
6049
- fs25.writeFileSync(
6215
+ fs26.mkdirSync(path23.dirname(termsPage), { recursive: true });
6216
+ fs26.writeFileSync(termsPage, html);
6217
+ fs26.writeFileSync(
6050
6218
  termsPageTs,
6051
6219
  pageMetaTagsLoader("Terms of Service", `Terms of Service for ${core.websiteName}`)
6052
6220
  );
@@ -6065,7 +6233,7 @@ async function termsAction() {
6065
6233
  var terms = new Command48("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
6066
6234
 
6067
6235
  // src/commands/legal/privacy.ts
6068
- import fs26 from "node:fs";
6236
+ import fs27 from "node:fs";
6069
6237
  import path24 from "node:path";
6070
6238
  import { Command as Command49 } from "commander";
6071
6239
  import * as p33 from "@clack/prompts";
@@ -6793,9 +6961,9 @@ async function privacyAction() {
6793
6961
  "privacy",
6794
6962
  "+page.ts"
6795
6963
  );
6796
- fs26.mkdirSync(path24.dirname(privacyPage), { recursive: true });
6797
- fs26.writeFileSync(privacyPage, html);
6798
- fs26.writeFileSync(
6964
+ fs27.mkdirSync(path24.dirname(privacyPage), { recursive: true });
6965
+ fs27.writeFileSync(privacyPage, html);
6966
+ fs27.writeFileSync(
6799
6967
  privacyPageTs,
6800
6968
  pageMetaTagsLoader("Privacy Policy", `Privacy Policy for ${core.websiteName}`)
6801
6969
  );
@@ -6823,7 +6991,7 @@ import { Command as Command56 } from "commander";
6823
6991
  import { Command as Command51 } from "commander";
6824
6992
 
6825
6993
  // src/lib/data.ts
6826
- import fs27 from "node:fs";
6994
+ import fs28 from "node:fs";
6827
6995
  import path25 from "node:path";
6828
6996
  import { ClientResponseError } from "pocketbase";
6829
6997
 
@@ -6865,8 +7033,8 @@ function dataDir(cwd, kind) {
6865
7033
  }
6866
7034
  function getDataFiles(cwd, kind) {
6867
7035
  const dir = dataDir(cwd, kind);
6868
- if (!fs27.existsSync(dir)) return [];
6869
- return fs27.readdirSync(dir).filter((file) => file.endsWith(".json")).sort((a, b) => a.localeCompare(b)).map((file) => ({
7036
+ if (!fs28.existsSync(dir)) return [];
7037
+ return fs28.readdirSync(dir).filter((file) => file.endsWith(".json")).sort((a, b) => a.localeCompare(b)).map((file) => ({
6870
7038
  collectionName: file.replace(/\.json$/i, "").replace(/^\d+[-_]?/, ""),
6871
7039
  filePath: path25.join(dir, file)
6872
7040
  }));
@@ -6878,7 +7046,7 @@ function getFixtureFiles(cwd) {
6878
7046
  return getDataFiles(cwd, "fixtures");
6879
7047
  }
6880
7048
  function readRecords(filePath) {
6881
- return JSON.parse(fs27.readFileSync(filePath, "utf8"));
7049
+ return JSON.parse(fs28.readFileSync(filePath, "utf8"));
6882
7050
  }
6883
7051
  function readSeedIds(cwd) {
6884
7052
  const ids = /* @__PURE__ */ new Map();
@@ -7097,7 +7265,7 @@ var reset = new Command53("reset").description("clear and reload fixtures").conf
7097
7265
  );
7098
7266
 
7099
7267
  // src/commands/fixtures/generate.ts
7100
- import fs28 from "node:fs";
7268
+ import fs29 from "node:fs";
7101
7269
  import path26 from "node:path";
7102
7270
  import { Command as Command54, InvalidArgumentError } from "commander";
7103
7271
  import * as p34 from "@clack/prompts";
@@ -7135,9 +7303,9 @@ async function loadCollections(pb) {
7135
7303
  }
7136
7304
  async function generateFixtureFiles(pb, workspaceRootDir, opts) {
7137
7305
  const fixturesDir = dataDir(workspaceRootDir, "fixtures");
7138
- fs28.mkdirSync(fixturesDir, { recursive: true });
7139
- for (const file of fs28.readdirSync(fixturesDir)) {
7140
- if (file.endsWith(".json")) fs28.unlinkSync(path26.join(fixturesDir, file));
7306
+ fs29.mkdirSync(fixturesDir, { recursive: true });
7307
+ for (const file of fs29.readdirSync(fixturesDir)) {
7308
+ if (file.endsWith(".json")) fs29.unlinkSync(path26.join(fixturesDir, file));
7141
7309
  }
7142
7310
  if (opts.seed !== void 0) faker.seed(opts.seed);
7143
7311
  const generator = createGenerator({
@@ -7204,7 +7372,7 @@ async function generateFixtureFiles(pb, workspaceRootDir, opts) {
7204
7372
  items.push(record);
7205
7373
  }
7206
7374
  const filename = `${padZeros(fileIndex, 2)}-${collection.name}.json`;
7207
- fs28.writeFileSync(path26.join(fixturesDir, filename), JSON.stringify(items, null, 2));
7375
+ fs29.writeFileSync(path26.join(fixturesDir, filename), JSON.stringify(items, null, 2));
7208
7376
  writtenFiles.push(`${path26.join(DATA_DIR, "fixtures", filename)} (${items.length} records)`);
7209
7377
  fileIndex++;
7210
7378
  }
@@ -7352,7 +7520,7 @@ var load2 = new Command57("load").description("load seeds into the database").op
7352
7520
  );
7353
7521
 
7354
7522
  // src/commands/seeds/save.ts
7355
- import fs29 from "node:fs";
7523
+ import fs30 from "node:fs";
7356
7524
  import path27 from "node:path";
7357
7525
  import { Command as Command58 } from "commander";
7358
7526
  var padZeros2 = (num, length) => num.toString().padStart(length, "0");
@@ -7373,10 +7541,10 @@ var save = new Command58("save").description("save the current data as seeds").o
7373
7541
  if (existing.length > 0 && !opts.force) {
7374
7542
  throw new Error("Existing seed files found in data/seeds. Pass --force to overwrite.");
7375
7543
  }
7376
- fs29.mkdirSync(seedsPath, { recursive: true });
7544
+ fs30.mkdirSync(seedsPath, { recursive: true });
7377
7545
  if (opts.force) {
7378
- for (const file of fs29.readdirSync(seedsPath)) {
7379
- if (file.endsWith(".json")) fs29.unlinkSync(path27.join(seedsPath, file));
7546
+ for (const file of fs30.readdirSync(seedsPath)) {
7547
+ if (file.endsWith(".json")) fs30.unlinkSync(path27.join(seedsPath, file));
7380
7548
  }
7381
7549
  }
7382
7550
  const saved = [];
@@ -7411,7 +7579,7 @@ var save = new Command58("save").description("save the current data as seeds").o
7411
7579
  `${padZeros2(count, 2)}-${collectionName}.json`
7412
7580
  );
7413
7581
  const seedPath = path27.join(workspaceRootDir, relativeSeedPath);
7414
- fs29.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
7582
+ fs30.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
7415
7583
  saved.push(`${relativeSeedPath} (${filtered.length} records)`);
7416
7584
  count++;
7417
7585
  }
@@ -7623,10 +7791,10 @@ import { Command as Command65 } from "commander";
7623
7791
 
7624
7792
  // src/lib/migrate.ts
7625
7793
  import path28 from "node:path";
7626
- import process19 from "node:process";
7794
+ import process20 from "node:process";
7627
7795
  import { x as x2 } from "tinyexec";
7628
7796
  async function runPocketbaseMigrate(args) {
7629
- const cwd = process19.cwd();
7797
+ const cwd = process20.cwd();
7630
7798
  const { getBinaryPath } = await import("pocketbase-server");
7631
7799
  const binaryPath = getBinaryPath();
7632
7800
  await x2(
@@ -7683,13 +7851,13 @@ var down = new Command66("down").alias("rollback").description("revert the last
7683
7851
  );
7684
7852
 
7685
7853
  // src/commands/migrate/create.ts
7686
- import fs30 from "node:fs";
7854
+ import fs31 from "node:fs";
7687
7855
  import path29 from "node:path";
7688
- import process20 from "node:process";
7856
+ import process21 from "node:process";
7689
7857
  import { Command as Command67 } from "commander";
7690
7858
  var create2 = new Command67("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
7691
7859
  (name) => runCommand(async () => {
7692
- const cwd = process20.cwd();
7860
+ const cwd = process21.cwd();
7693
7861
  const before = listMigrationFiles(cwd);
7694
7862
  await runPocketbaseMigrate(["create", name]);
7695
7863
  const after = listMigrationFiles(cwd);
@@ -7706,18 +7874,18 @@ var create2 = new Command67("create").alias("new").description("create a new bla
7706
7874
  );
7707
7875
  function listMigrationFiles(cwd) {
7708
7876
  const dir = path29.join(cwd, MIGRATIONS_DIR);
7709
- if (!fs30.existsSync(dir)) return /* @__PURE__ */ new Set();
7710
- return new Set(fs30.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7877
+ if (!fs31.existsSync(dir)) return /* @__PURE__ */ new Set();
7878
+ return new Set(fs31.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7711
7879
  }
7712
7880
 
7713
7881
  // src/commands/migrate/collections.ts
7714
- import fs31 from "node:fs";
7882
+ import fs32 from "node:fs";
7715
7883
  import path30 from "node:path";
7716
- import process21 from "node:process";
7884
+ import process22 from "node:process";
7717
7885
  import { Command as Command68 } from "commander";
7718
7886
  var collections = new Command68("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
7719
7887
  () => runCommand(async () => {
7720
- const cwd = process21.cwd();
7888
+ const cwd = process22.cwd();
7721
7889
  const before = listMigrationFiles2(cwd);
7722
7890
  await runPocketbaseMigrate(["collections"]);
7723
7891
  const after = listMigrationFiles2(cwd);
@@ -7741,8 +7909,8 @@ var collections = new Command68("collections").alias("snapshot").description("sn
7741
7909
  );
7742
7910
  function listMigrationFiles2(cwd) {
7743
7911
  const dir = path30.join(cwd, MIGRATIONS_DIR);
7744
- if (!fs31.existsSync(dir)) return /* @__PURE__ */ new Set();
7745
- return new Set(fs31.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7912
+ if (!fs32.existsSync(dir)) return /* @__PURE__ */ new Set();
7913
+ return new Set(fs32.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7746
7914
  }
7747
7915
 
7748
7916
  // src/commands/migrate/history-sync.ts
@@ -7761,9 +7929,9 @@ var historySync = new Command69("history-sync").description("drop _migrations ro
7761
7929
  var migrate = new Command70("migrate").description("manage database migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations.")).addCommand(up).addCommand(down).addCommand(create2).addCommand(collections).addCommand(historySync);
7762
7930
 
7763
7931
  // src/commands/dev.ts
7764
- import fs32 from "node:fs";
7932
+ import fs33 from "node:fs";
7765
7933
  import path32 from "node:path";
7766
- import process23 from "node:process";
7934
+ import process24 from "node:process";
7767
7935
  import { performance } from "node:perf_hooks";
7768
7936
  import { Command as Command71, InvalidArgumentError as InvalidArgumentError4 } from "commander";
7769
7937
  import pc15 from "picocolors";
@@ -7771,7 +7939,7 @@ import PocketBase4 from "pocketbase";
7771
7939
 
7772
7940
  // src/lib/vite.ts
7773
7941
  import path31 from "node:path";
7774
- import process22 from "node:process";
7942
+ import process23 from "node:process";
7775
7943
  import { createRequire as createRequire2 } from "node:module";
7776
7944
  import { pathToFileURL as pathToFileURL2 } from "node:url";
7777
7945
  import pc14 from "picocolors";
@@ -7795,13 +7963,13 @@ function resolveProjectVite(cwd) {
7795
7963
  return null;
7796
7964
  }
7797
7965
  }
7798
- async function loadVite(cwd = process22.cwd()) {
7966
+ async function loadVite(cwd = process23.cwd()) {
7799
7967
  const entry = resolveProjectVite(cwd);
7800
7968
  const vite = entry ? await import(pathToFileURL2(entry).href) : await import("vite");
7801
7969
  const error = viteVersionError(vite.version);
7802
7970
  if (error) {
7803
7971
  console.error(`${pc14.redBright("\u2717")} ${error}`);
7804
- process22.exit(1);
7972
+ process23.exit(1);
7805
7973
  }
7806
7974
  return vite;
7807
7975
  }
@@ -7815,18 +7983,18 @@ function parsePort(value) {
7815
7983
  return port;
7816
7984
  }
7817
7985
  var dev = new Command71("dev").description("start the development server").option("--open [path]", "open the app in a browser once the server is ready").option("--host [host]", "expose the server on the network").option("--port <port>", "port to listen on", parsePort).option("--strictPort", "exit if the port is already in use instead of taking the next one").option("--cors", "enable CORS").option("--force", "re-bundle dependencies, ignoring the optimizer cache").configureHelp(helpConfig).action(async (options) => {
7818
- const cwd = process23.cwd();
7819
- process23.env.VELA_DATA_DIR ??= localDataDir(cwd);
7986
+ const cwd = process24.cwd();
7987
+ process24.env.VELA_DATA_DIR ??= localDataDir(cwd);
7820
7988
  const startTime = performance.now();
7821
7989
  const { createServer, version } = await loadVite(cwd);
7822
7990
  const viteMetadataDir = path32.join(cwd, "node_modules", ".vite");
7823
7991
  const viteMetadataFile = path32.join(viteMetadataDir, "_pocketbase_metadata.json");
7824
7992
  let pbProc;
7825
7993
  const backend3 = hasBackend(cwd);
7826
- const needsStart = backend3 && !process23.env.POCKETBASE_URL;
7994
+ const needsStart = backend3 && !process24.env.POCKETBASE_URL;
7827
7995
  const cleanup = () => {
7828
7996
  if (pbProc?.pid) pbProc.kill();
7829
- if (fs32.existsSync(viteMetadataFile)) fs32.rmSync(viteMetadataFile);
7997
+ if (fs33.existsSync(viteMetadataFile)) fs33.rmSync(viteMetadataFile);
7830
7998
  };
7831
7999
  if (needsStart) {
7832
8000
  const dataDir2 = path32.join(cwd, DATA_DIR);
@@ -7838,15 +8006,15 @@ var dev = new Command71("dev").description("start the development server").optio
7838
8006
  stdio: "pipe"
7839
8007
  });
7840
8008
  pbProc = started.proc;
7841
- process23.env.POCKETBASE_URL = started.url;
7842
- pbProc.stdout?.pipe(process23.stdout);
7843
- pbProc.stderr?.pipe(process23.stderr);
8009
+ process24.env.POCKETBASE_URL = started.url;
8010
+ pbProc.stdout?.pipe(process24.stdout);
8011
+ pbProc.stderr?.pipe(process24.stderr);
7844
8012
  pbProc.on("error", (err) => console.error("PocketBase error:", err));
7845
8013
  pbProc.on("exit", (code) => console.log(`PocketBase exited with code ${code}`));
7846
- process23.on("exit", cleanup);
7847
- process23.on("SIGINT", () => {
8014
+ process24.on("exit", cleanup);
8015
+ process24.on("SIGINT", () => {
7848
8016
  cleanup();
7849
- process23.exit(0);
8017
+ process24.exit(0);
7850
8018
  });
7851
8019
  }
7852
8020
  const serverOptions = {};
@@ -7864,25 +8032,25 @@ var dev = new Command71("dev").description("start the development server").optio
7864
8032
  if (!backend3) return;
7865
8033
  const { address, port: vitePort } = server.httpServer.address();
7866
8034
  const viteHost = address === "::1" ? "localhost" : address;
7867
- await fs32.promises.mkdir(viteMetadataDir, { recursive: true });
7868
- await fs32.promises.writeFile(
8035
+ await fs33.promises.mkdir(viteMetadataDir, { recursive: true });
8036
+ await fs33.promises.writeFile(
7869
8037
  viteMetadataFile,
7870
8038
  JSON.stringify({
7871
- pocketbaseUrl: process23.env.POCKETBASE_URL,
8039
+ pocketbaseUrl: process24.env.POCKETBASE_URL,
7872
8040
  vitePort,
7873
8041
  viteHost
7874
8042
  })
7875
8043
  );
7876
- const pb = new PocketBase4(process23.env.POCKETBASE_URL);
8044
+ const pb = new PocketBase4(process24.env.POCKETBASE_URL);
7877
8045
  await pb.collection("_superusers").authWithPassword(
7878
- process23.env.POCKETBASE_SUPERUSER_EMAIL,
7879
- process23.env.POCKETBASE_SUPERUSER_PASSWORD
8046
+ process24.env.POCKETBASE_SUPERUSER_EMAIL,
8047
+ process24.env.POCKETBASE_SUPERUSER_PASSWORD
7880
8048
  );
7881
8049
  await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
7882
8050
  await startWatchingTypes(cwd, pb);
7883
8051
  });
7884
8052
  await server.listen();
7885
- const hasExistingLogs = process23.stdout.bytesWritten > 0 || process23.stderr.bytesWritten > 0;
8053
+ const hasExistingLogs = process24.stdout.bytesWritten > 0 || process24.stderr.bytesWritten > 0;
7886
8054
  const startupDurationString = pc15.dim(
7887
8055
  `ready in ${pc15.reset(pc15.bold(Math.ceil(performance.now() - startTime)))} ms`
7888
8056
  );
@@ -7906,9 +8074,9 @@ async function startWatchingTypes(cwd, pb) {
7906
8074
  void (async () => {
7907
8075
  for (; ; ) {
7908
8076
  try {
7909
- await fs32.promises.mkdir(pocketbaseDir, { recursive: true });
7910
- for await (const event of fs32.promises.watch(pocketbaseDir)) {
7911
- if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs32.existsSync(pocketbaseTypes)) {
8077
+ await fs33.promises.mkdir(pocketbaseDir, { recursive: true });
8078
+ for await (const event of fs33.promises.watch(pocketbaseDir)) {
8079
+ if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs33.existsSync(pocketbaseTypes)) {
7912
8080
  setTimeout(regenerate, 100);
7913
8081
  }
7914
8082
  }
@@ -7920,9 +8088,9 @@ async function startWatchingTypes(cwd, pb) {
7920
8088
  }
7921
8089
 
7922
8090
  // src/commands/build.ts
7923
- import fs33 from "node:fs";
8091
+ import fs34 from "node:fs";
7924
8092
  import path33 from "node:path";
7925
- import process25 from "node:process";
8093
+ import process26 from "node:process";
7926
8094
  import { Command as Command72 } from "commander";
7927
8095
  import * as p40 from "@clack/prompts";
7928
8096
  import pc16 from "picocolors";
@@ -7937,10 +8105,10 @@ function applyBuildEnv(cwd, env2 = process.env) {
7937
8105
  }
7938
8106
 
7939
8107
  // src/lib/origin.ts
7940
- import process24 from "node:process";
8108
+ import process25 from "node:process";
7941
8109
  function resolveOrigin(workspaceRootDir, envTag, config = {}) {
7942
8110
  return normalizeOrigin(
7943
- process24.env.VELA_ORIGIN ?? readBinding(workspaceRootDir, envTag)?.domain ?? config.deploy?.domain
8111
+ process25.env.VELA_ORIGIN ?? readBinding(workspaceRootDir, envTag)?.domain ?? config.deploy?.domain
7944
8112
  );
7945
8113
  }
7946
8114
  function normalizeOrigin(value) {
@@ -7961,12 +8129,12 @@ function splitHosts(value) {
7961
8129
  // src/commands/build.ts
7962
8130
  var PRERENDERED_DIR = path33.join(".svelte-kit", "output", "prerendered");
7963
8131
  var build = new Command72("build").description("build the app").configureHelp(helpConfig).option("-t, --target <target>", "which copy of the app to build for", PRODUCTION_TARGET).action(async (options) => {
7964
- const cwd = process25.cwd();
8132
+ const cwd = process26.cwd();
7965
8133
  applyBuildEnv(cwd);
7966
8134
  const origin = await originForBuild(cwd, options.target);
7967
- if (origin) process25.env.VELA_ORIGIN = origin;
8135
+ if (origin) process26.env.VELA_ORIGIN = origin;
7968
8136
  let pbProc;
7969
- const needsStart = hasBackend(cwd) && !process25.env.POCKETBASE_URL;
8137
+ const needsStart = hasBackend(cwd) && !process26.env.POCKETBASE_URL;
7970
8138
  const cleanup = () => {
7971
8139
  if (pbProc?.pid) pbProc.kill();
7972
8140
  };
@@ -7980,11 +8148,11 @@ var build = new Command72("build").description("build the app").configureHelp(he
7980
8148
  dev: true
7981
8149
  });
7982
8150
  pbProc = started.proc;
7983
- process25.env.POCKETBASE_URL = started.url;
7984
- process25.on("exit", cleanup);
7985
- process25.on("SIGINT", () => {
8151
+ process26.env.POCKETBASE_URL = started.url;
8152
+ process26.on("exit", cleanup);
8153
+ process26.on("SIGINT", () => {
7986
8154
  cleanup();
7987
- process25.exit(0);
8155
+ process26.exit(0);
7988
8156
  });
7989
8157
  }
7990
8158
  try {
@@ -8016,7 +8184,7 @@ async function originForBuild(cwd, target) {
8016
8184
  }
8017
8185
  function warnIfPrerendered(cwd) {
8018
8186
  const dir = path33.join(cwd, PRERENDERED_DIR);
8019
- if (!fs33.existsSync(dir) || fs33.readdirSync(dir).length === 0) return;
8187
+ if (!fs34.existsSync(dir) || fs34.readdirSync(dir).length === 0) return;
8020
8188
  p40.log.warn(
8021
8189
  `Prerendered pages were built with no domain configured, so their canonical
8022
8190
  links point at SvelteKit's placeholder host rather than at this site.
@@ -8027,16 +8195,16 @@ Set one with ${pc16.cyan("vela deploy --domain example.com")}, or pass ${pc16.cy
8027
8195
 
8028
8196
  // src/commands/preview.ts
8029
8197
  import path34 from "node:path";
8030
- import process26 from "node:process";
8198
+ import process27 from "node:process";
8031
8199
  import { Command as Command73 } from "commander";
8032
8200
  import { x as x4 } from "tinyexec";
8033
8201
  import { detect as detect6 } from "package-manager-detector";
8034
8202
  import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
8035
8203
  var preview = new Command73("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
8036
- const cwd = process26.cwd();
8037
- process26.env.VELA_DATA_DIR ??= localDataDir(cwd);
8204
+ const cwd = process27.cwd();
8205
+ process27.env.VELA_DATA_DIR ??= localDataDir(cwd);
8038
8206
  let pbProc;
8039
- const needsStart = hasBackend(cwd) && !process26.env.POCKETBASE_URL;
8207
+ const needsStart = hasBackend(cwd) && !process27.env.POCKETBASE_URL;
8040
8208
  const cleanup = () => {
8041
8209
  if (pbProc?.pid) pbProc.kill();
8042
8210
  };
@@ -8049,11 +8217,11 @@ var preview = new Command73("preview").description("preview the built app").conf
8049
8217
  dev: true
8050
8218
  });
8051
8219
  pbProc = started.proc;
8052
- process26.env.POCKETBASE_URL = started.url;
8053
- process26.on("exit", cleanup);
8054
- process26.on("SIGINT", () => {
8220
+ process27.env.POCKETBASE_URL = started.url;
8221
+ process27.on("exit", cleanup);
8222
+ process27.on("SIGINT", () => {
8055
8223
  cleanup();
8056
- process26.exit(0);
8224
+ process27.exit(0);
8057
8225
  });
8058
8226
  }
8059
8227
  try {
@@ -8139,23 +8307,23 @@ var provision = addSshOptions(
8139
8307
 
8140
8308
  // src/commands/deploy.ts
8141
8309
  import path37 from "node:path";
8142
- import fs35 from "node:fs";
8310
+ import fs36 from "node:fs";
8143
8311
  import { Command as Command76, Option as Option2 } from "commander";
8144
8312
  import * as p42 from "@clack/prompts";
8145
8313
  import pc18 from "picocolors";
8146
8314
  import * as v8 from "valibot";
8147
8315
 
8148
8316
  // src/lib/pocketbase-settings.ts
8149
- import fs34 from "node:fs";
8317
+ import fs35 from "node:fs";
8150
8318
  import path36 from "node:path";
8151
- import process27 from "node:process";
8319
+ import process28 from "node:process";
8152
8320
  import PocketBase5 from "pocketbase";
8153
8321
  var COPIED_KEYS = ["appName", "senderName", "senderAddress"];
8154
8322
  async function readLocalMeta(cwd) {
8155
8323
  const dataDir2 = path36.join(cwd, DATA_DIR);
8156
- if (!fs34.existsSync(dataDir2)) return null;
8157
- const email3 = process27.env.POCKETBASE_SUPERUSER_EMAIL;
8158
- const password11 = process27.env.POCKETBASE_SUPERUSER_PASSWORD;
8324
+ if (!fs35.existsSync(dataDir2)) return null;
8325
+ const email3 = process28.env.POCKETBASE_SUPERUSER_EMAIL;
8326
+ const password11 = process28.env.POCKETBASE_SUPERUSER_PASSWORD;
8159
8327
  if (!email3 || !password11) return null;
8160
8328
  let proc;
8161
8329
  try {
@@ -8208,6 +8376,7 @@ var OptionsSchema3 = v8.object({
8208
8376
  ...SSH_OPTION_SCHEMA,
8209
8377
  env: v8.optional(v8.string()),
8210
8378
  project: v8.optional(v8.string()),
8379
+ ...LOCK_WAIT_SCHEMA,
8211
8380
  remoteDb: v8.optional(v8.boolean()),
8212
8381
  domain: v8.optional(v8.string()),
8213
8382
  healthPath: v8.optional(v8.string()),
@@ -8215,9 +8384,11 @@ var OptionsSchema3 = v8.object({
8215
8384
  pbVersion: v8.optional(v8.string()),
8216
8385
  build: v8.optional(v8.boolean())
8217
8386
  });
8218
- var deploy = addTargetOptions(
8219
- new Command76("deploy").description("deploy the app").configureHelp(helpConfig),
8220
- "production"
8387
+ var deploy = addLockWaitOption(
8388
+ addTargetOptions(
8389
+ new Command76("deploy").description("deploy the app").configureHelp(helpConfig),
8390
+ "production"
8391
+ )
8221
8392
  ).option("--project <name>", "override the project name").option("--domain <hosts>", "hostname(s) to serve on, comma separated").option("--health-path <path>", "path the health check requests").option("--keep <count>", "how many old releases to keep on the server").option("--pb-version <version>", "PocketBase version to run").option("--no-build", "deploy the existing build output without rebuilding").addOption(
8222
8393
  new Option2(
8223
8394
  "--remote-db",
@@ -8231,7 +8402,6 @@ var deploy = addTargetOptions(
8231
8402
  (raw) => runCommand(async () => {
8232
8403
  const options = parseOptions(OptionsSchema3, raw);
8233
8404
  const backend3 = hasBackend();
8234
- const release = releaseId();
8235
8405
  p42.intro(pc18.bgCyan(pc18.black(" vela deploy ")));
8236
8406
  await withTarget(
8237
8407
  raw,
@@ -8242,8 +8412,9 @@ var deploy = addTargetOptions(
8242
8412
  `${pc18.cyan(ctx.appName)} ${pc18.dim("\u2192")} ${pc18.cyan(ctx.targetName)} ${pc18.dim(`(${ctx.server})`)}`
8243
8413
  );
8244
8414
  const [existing] = await readInstanceStates(session, instance);
8245
- const isPreview = ctx.target.kind === "preview";
8246
- const configured = options.domain ?? (isPreview ? existing?.domain : ctx.binding.domain ?? config.deploy?.domain ?? existing?.domain) ?? "";
8415
+ const release = releaseId(await serverTimeOrLocal(session));
8416
+ const isPreview2 = ctx.target.kind === "preview";
8417
+ const configured = options.domain ?? (isPreview2 ? existing?.domain : ctx.binding.domain ?? config.deploy?.domain ?? existing?.domain) ?? "";
8247
8418
  const askedForRemoteDb = options.remoteDb ?? config.deploy?.buildAgainstRemote;
8248
8419
  const remoteDb = askedForRemoteDb ?? Boolean(existing?.pbPort);
8249
8420
  const sha = await gitSha(workspaceRootDir);
@@ -8265,17 +8436,17 @@ var deploy = addTargetOptions(
8265
8436
  const managed = managedHosts.join(",");
8266
8437
  const primaryHost = directHosts[0] ?? managedHosts[0] ?? "";
8267
8438
  const primaryUrl = started?.environment.primaryUrl || normalizeOrigin(primaryHost) || "";
8268
- if (isPreview && !primaryHost) {
8269
- throw new Error(
8270
- `Nothing routes to this preview.
8439
+ let result = null;
8440
+ try {
8441
+ if (isPreview2 && !primaryHost) {
8442
+ throw new Error(
8443
+ `Nothing routes to this preview.
8271
8444
 
8272
8445
  Previews get a free velastack.app hostname from velastack.dev: ${pc18.cyan("vela link")} the
8273
8446
  project and ${pc18.cyan("vela login")} (or set ${pc18.cyan("VELA_API_KEY")}). Or pass ${pc18.cyan("--domain <host>")}
8274
8447
  with DNS of your own pointing at ${ctx.server}.`
8275
- );
8276
- }
8277
- let result = null;
8278
- try {
8448
+ );
8449
+ }
8279
8450
  if (options.build !== false) {
8280
8451
  const origin = normalizeOrigin(process.env.VELA_ORIGIN ?? primaryUrl);
8281
8452
  let buildEnv = origin ? { VELA_ORIGIN: origin } : {};
@@ -8333,7 +8504,8 @@ ${pc18.dim(String(err))}`
8333
8504
  "--pb-version",
8334
8505
  options.pbVersion ?? config.deploy?.pocketbaseVersion ?? pocketbaseVersion(),
8335
8506
  "--git-sha",
8336
- sha
8507
+ sha,
8508
+ ...lockWaitArgs(options.lockWait)
8337
8509
  ],
8338
8510
  stream: true
8339
8511
  });
@@ -8347,7 +8519,7 @@ ${pc18.dim(String(err))}`
8347
8519
  writeBinding(
8348
8520
  workspaceRootDir,
8349
8521
  bindingKey(ctx.target),
8350
- isPreview ? { server: ctx.server } : { server: ctx.server, domain: domain || void 0 }
8522
+ isPreview2 ? { server: ctx.server } : { server: ctx.server, domain: domain || void 0 }
8351
8523
  );
8352
8524
  if (!existing) await reportEmptyEnvironment(session, instance, workspaceRootDir);
8353
8525
  const url = result?.url ?? "";
@@ -8498,7 +8670,7 @@ async function superuserAuthenticates(port, identity, password11) {
8498
8670
  }
8499
8671
  async function uploadRelease(session, instance, release, entries) {
8500
8672
  const dir = remotePaths.release(instance, release);
8501
- await session.script(`mkdir -p "$1"`, { args: [dir] });
8673
+ await session.script(`mkdir -p "$(dirname "$1")" && mkdir "$1"`, { args: [dir] });
8502
8674
  const byTarget = /* @__PURE__ */ new Map();
8503
8675
  for (const entry of entries) {
8504
8676
  const target = entry.remoteDir ? `${dir}/${entry.remoteDir}` : dir;
@@ -8512,7 +8684,7 @@ async function uploadRelease(session, instance, release, entries) {
8512
8684
  }
8513
8685
  function isDirectory(target) {
8514
8686
  try {
8515
- return fs35.statSync(target).isDirectory();
8687
+ return fs36.statSync(target).isDirectory();
8516
8688
  } catch {
8517
8689
  return false;
8518
8690
  }
@@ -8520,7 +8692,7 @@ function isDirectory(target) {
8520
8692
  async function reportEmptyEnvironment(session, instance, workspaceRootDir) {
8521
8693
  const remote = await readRemoteEnv(session, instance);
8522
8694
  if (Object.keys(remote).length > 0) return;
8523
- if (!fs35.existsSync(path37.join(workspaceRootDir, ".env"))) return;
8695
+ if (!fs36.existsSync(path37.join(workspaceRootDir, ".env"))) return;
8524
8696
  p42.log.warn(
8525
8697
  `This app has no production environment variables yet.
8526
8698
 
@@ -8528,10 +8700,18 @@ Local ${pc18.cyan(".env")} values are not uploaded by a deploy. Set them with
8528
8700
  ${pc18.cyan("vela env set KEY")}, or copy a file across with ${pc18.cyan("vela env import .env.production")}.`
8529
8701
  );
8530
8702
  }
8703
+ async function serverTimeOrLocal(session) {
8704
+ try {
8705
+ return await serverTime(session);
8706
+ } catch {
8707
+ p42.log.warn("Could not read the clock on the server; stamping this release from this machine.");
8708
+ return /* @__PURE__ */ new Date();
8709
+ }
8710
+ }
8531
8711
 
8532
8712
  // src/commands/link.ts
8533
8713
  import path38 from "node:path";
8534
- import process28 from "node:process";
8714
+ import process29 from "node:process";
8535
8715
  import { Command as Command77 } from "commander";
8536
8716
  import * as p43 from "@clack/prompts";
8537
8717
  var CREATE_NEW = "__new__";
@@ -8582,7 +8762,7 @@ async function pickExistingProject(projects) {
8582
8762
  });
8583
8763
  if (p43.isCancel(choice)) {
8584
8764
  p43.cancel("Operation cancelled.");
8585
- process28.exit(0);
8765
+ process29.exit(0);
8586
8766
  }
8587
8767
  return choice;
8588
8768
  }
@@ -8597,7 +8777,7 @@ async function pickTeam(teams3) {
8597
8777
  });
8598
8778
  if (p43.isCancel(choice)) {
8599
8779
  p43.cancel("Operation cancelled.");
8600
- process28.exit(0);
8780
+ process29.exit(0);
8601
8781
  }
8602
8782
  return teams3.find((team) => team.id === choice);
8603
8783
  }
@@ -8612,7 +8792,7 @@ async function promptProjectName(workspaceRootDir) {
8612
8792
  });
8613
8793
  if (p43.isCancel(value)) {
8614
8794
  p43.cancel("Operation cancelled.");
8615
- process28.exit(0);
8795
+ process29.exit(0);
8616
8796
  }
8617
8797
  return value.trim();
8618
8798
  }
@@ -8667,7 +8847,7 @@ function report(keys, where) {
8667
8847
  }
8668
8848
 
8669
8849
  // src/commands/env/set.ts
8670
- import process29 from "node:process";
8850
+ import process30 from "node:process";
8671
8851
  import { Command as Command79 } from "commander";
8672
8852
  import * as p45 from "@clack/prompts";
8673
8853
  import pc20 from "picocolors";
@@ -8709,7 +8889,7 @@ async function promptValue(key) {
8709
8889
  });
8710
8890
  if (p45.isCancel(value)) {
8711
8891
  p45.cancel("Operation cancelled.");
8712
- process29.exit(0);
8892
+ process30.exit(0);
8713
8893
  }
8714
8894
  return value;
8715
8895
  }
@@ -8754,9 +8934,9 @@ var envUnset = addTargetOptions(
8754
8934
  );
8755
8935
 
8756
8936
  // src/commands/env/import.ts
8757
- import fs36 from "node:fs";
8937
+ import fs37 from "node:fs";
8758
8938
  import path39 from "node:path";
8759
- import process30 from "node:process";
8939
+ import process31 from "node:process";
8760
8940
  import { Command as Command81 } from "commander";
8761
8941
  import * as p47 from "@clack/prompts";
8762
8942
  import pc22 from "picocolors";
@@ -8801,10 +8981,10 @@ var envImport = addTargetOptions(
8801
8981
  )
8802
8982
  );
8803
8983
  function resolve(file) {
8804
- return path39.resolve(process30.cwd(), file);
8984
+ return path39.resolve(process31.cwd(), file);
8805
8985
  }
8806
8986
  function read(resolved, shown) {
8807
- if (!fs36.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
8987
+ if (!fs37.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
8808
8988
  const incoming = readLocalEnvFile(resolved);
8809
8989
  if (Object.keys(incoming).length === 0) p47.log.info(`${shown} has no variables to import.`);
8810
8990
  return incoming;
@@ -8853,10 +9033,10 @@ function report2(states, json) {
8853
9033
  return;
8854
9034
  }
8855
9035
  for (const state of states) {
8856
- p48.log.info(describe(state));
9036
+ p48.log.info(describe2(state));
8857
9037
  }
8858
9038
  }
8859
- function describe(state) {
9039
+ function describe2(state) {
8860
9040
  const health = (value) => value === "active" ? pc23.green(value) : pc23.red(value || "inactive");
8861
9041
  const rows = [
8862
9042
  ["Instance", state.instance],
@@ -8880,9 +9060,11 @@ function describe(state) {
8880
9060
  import { Command as Command84 } from "commander";
8881
9061
  import * as p49 from "@clack/prompts";
8882
9062
  import pc24 from "picocolors";
8883
- var rollback = addTargetOptions(
8884
- new Command84("rollback").description("put the previous release back").configureHelp(helpConfig),
8885
- "production"
9063
+ var rollback = addLockWaitOption(
9064
+ addTargetOptions(
9065
+ new Command84("rollback").description("put the previous release back").configureHelp(helpConfig),
9066
+ "production"
9067
+ )
8886
9068
  ).option("--to <release>", "roll back to a specific release instead of the previous one").action(
8887
9069
  (raw) => runCommand(async () => {
8888
9070
  const options = raw;
@@ -8897,7 +9079,11 @@ var rollback = addTargetOptions(
8897
9079
  ctx.session,
8898
9080
  "rollback.sh",
8899
9081
  {
8900
- args: [ctx.instance, ...options.to ? ["--to", options.to] : []],
9082
+ args: [
9083
+ ctx.instance,
9084
+ ...options.to ? ["--to", options.to] : [],
9085
+ ...lockWaitArgs(options.lockWait)
9086
+ ],
8901
9087
  stream: true
8902
9088
  }
8903
9089
  );
@@ -8954,7 +9140,7 @@ var logs = addTargetOptions(
8954
9140
  import { Command as Command87 } from "commander";
8955
9141
 
8956
9142
  // src/commands/admin/create.ts
8957
- import process31 from "node:process";
9143
+ import process32 from "node:process";
8958
9144
  import { Command as Command86 } from "commander";
8959
9145
  import * as p50 from "@clack/prompts";
8960
9146
  import pc25 from "picocolors";
@@ -9021,7 +9207,7 @@ async function upsertSuperuser(pb, email3, password11) {
9021
9207
  });
9022
9208
  if (p50.isCancel(confirmed) || !confirmed) {
9023
9209
  p50.cancel("Operation cancelled.");
9024
- process31.exit(0);
9210
+ process32.exit(0);
9025
9211
  }
9026
9212
  await pb.collection("_superusers").update(existing, { password: password11, passwordConfirm: password11 });
9027
9213
  p50.log.success(`Password updated for ${pc25.cyan(email3)}, you can sign in now`);
@@ -9045,7 +9231,7 @@ async function promptEmail() {
9045
9231
  });
9046
9232
  if (p50.isCancel(value)) {
9047
9233
  p50.cancel("Operation cancelled.");
9048
- process31.exit(0);
9234
+ process32.exit(0);
9049
9235
  }
9050
9236
  return value.trim();
9051
9237
  }
@@ -9056,7 +9242,7 @@ async function promptPassword2() {
9056
9242
  });
9057
9243
  if (p50.isCancel(value)) {
9058
9244
  p50.cancel("Operation cancelled.");
9059
- process31.exit(0);
9245
+ process32.exit(0);
9060
9246
  }
9061
9247
  const again = await p50.password({
9062
9248
  message: "Password again",
@@ -9064,7 +9250,7 @@ async function promptPassword2() {
9064
9250
  });
9065
9251
  if (p50.isCancel(again)) {
9066
9252
  p50.cancel("Operation cancelled.");
9067
- process31.exit(0);
9253
+ process32.exit(0);
9068
9254
  }
9069
9255
  return value;
9070
9256
  }
@@ -9076,7 +9262,7 @@ var admin = new Command87("admin").description("manage admin panel logins").conf
9076
9262
  import { Command as Command93 } from "commander";
9077
9263
 
9078
9264
  // src/commands/backup/create.ts
9079
- import fs37 from "node:fs";
9265
+ import fs38 from "node:fs";
9080
9266
  import path40 from "node:path";
9081
9267
  import { Command as Command88 } from "commander";
9082
9268
  import * as p51 from "@clack/prompts";
@@ -9232,10 +9418,10 @@ Your bucket's own versioning is what protects the uploaded files.`
9232
9418
  );
9233
9419
  async function download(ctx, key, outputDir) {
9234
9420
  const dir = path40.resolve(ctx.workspaceRootDir, outputDir);
9235
- fs37.mkdirSync(dir, { recursive: true });
9421
+ fs38.mkdirSync(dir, { recursive: true });
9236
9422
  const destination = path40.join(dir, key);
9237
9423
  if (!ctx.session) {
9238
- fs37.copyFileSync(path40.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9424
+ fs38.copyFileSync(path40.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9239
9425
  return path40.relative(ctx.workspaceRootDir, destination);
9240
9426
  }
9241
9427
  const spinner7 = p51.spinner();
@@ -9281,7 +9467,7 @@ Take one with ${pc27.cyan("vela backup create")}.`
9281
9467
  );
9282
9468
 
9283
9469
  // src/commands/backup/download.ts
9284
- import fs38 from "node:fs";
9470
+ import fs39 from "node:fs";
9285
9471
  import path41 from "node:path";
9286
9472
  import { Command as Command90 } from "commander";
9287
9473
  import * as p53 from "@clack/prompts";
@@ -9310,10 +9496,10 @@ Fetch it from the bucket directly \u2014 vela does not hold its credentials.`
9310
9496
  );
9311
9497
  }
9312
9498
  const dir = path41.resolve(ctx.workspaceRootDir, options.output);
9313
- fs38.mkdirSync(dir, { recursive: true });
9499
+ fs39.mkdirSync(dir, { recursive: true });
9314
9500
  const destination = path41.join(dir, key);
9315
9501
  if (!ctx.session) {
9316
- fs38.copyFileSync(path41.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9502
+ fs39.copyFileSync(path41.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9317
9503
  } else {
9318
9504
  const spinner7 = p53.spinner();
9319
9505
  spinner7.start(`Downloading ${key} (${formatBytes(found.size)})`);
@@ -9334,7 +9520,7 @@ Fetch it from the bucket directly \u2014 vela does not hold its credentials.`
9334
9520
  );
9335
9521
 
9336
9522
  // src/commands/backup/delete.ts
9337
- import process32 from "node:process";
9523
+ import process33 from "node:process";
9338
9524
  import { Command as Command91 } from "commander";
9339
9525
  import * as p54 from "@clack/prompts";
9340
9526
  import pc29 from "picocolors";
@@ -9360,7 +9546,7 @@ Run ${pc29.cyan("vela backup list")} to see what it does have.`
9360
9546
  });
9361
9547
  if (p54.isCancel(confirmed) || !confirmed) {
9362
9548
  p54.cancel("Operation cancelled.");
9363
- process32.exit(0);
9549
+ process33.exit(0);
9364
9550
  }
9365
9551
  }
9366
9552
  await ctx.pb.backups.delete(key);
@@ -9422,15 +9608,17 @@ Set one with ${pc30.cyan('vela backup schedule "0 3 * * *"')}.`
9422
9608
  var backup = new Command93("backup").description("back up the database and uploads, locally or on a target").configureHelp(helpConfig).addCommand(backupCreate).addCommand(backupList).addCommand(backupDownload).addCommand(backupDelete).addCommand(backupSchedule);
9423
9609
 
9424
9610
  // src/commands/restore.ts
9425
- import fs39 from "node:fs";
9611
+ import fs40 from "node:fs";
9426
9612
  import path42 from "node:path";
9427
- import process33 from "node:process";
9613
+ import process34 from "node:process";
9428
9614
  import { Command as Command94 } from "commander";
9429
9615
  import * as p56 from "@clack/prompts";
9430
9616
  import pc31 from "picocolors";
9431
- var restore = addTargetOptions(
9432
- new Command94("restore").description("replace a deployment\u2019s database and uploads from a backup").argument("[source]", "a backup key on the target, or a path to a local archive").configureHelp(helpConfig),
9433
- "production"
9617
+ var restore = addLockWaitOption(
9618
+ addTargetOptions(
9619
+ new Command94("restore").description("replace a deployment\u2019s database and uploads from a backup").argument("[source]", "a backup key on the target, or a path to a local archive").configureHelp(helpConfig),
9620
+ "production"
9621
+ )
9434
9622
  ).option("-y, --yes", "skip the confirmation prompt").option("--no-migrate", "do not run migrations against the restored database").option("--keep-previous <n>", "how many replaced databases to keep", "1").action(
9435
9623
  (source, raw) => runCommand(async () => {
9436
9624
  const options = raw;
@@ -9444,10 +9632,10 @@ var restore = addTargetOptions(
9444
9632
  `${ctx.targetName} was deployed without a database, so there is nothing to restore.`
9445
9633
  );
9446
9634
  }
9447
- const local = source && fs39.existsSync(source) ? source : void 0;
9635
+ const local = source && fs40.existsSync(source) ? source : void 0;
9448
9636
  const key = local ? void 0 : await resolveKey(ctx, source);
9449
9637
  if (!options.yes) {
9450
- await confirm12(ctx.appName, ctx.targetName, ctx.envTag, local ?? key);
9638
+ await confirm13(ctx.appName, ctx.targetName, ctx.envTag, local ?? key);
9451
9639
  }
9452
9640
  const archive = local ? await stage(ctx, local) : remotePaths.backup(ctx.instance, key);
9453
9641
  const result = await runServerScript(ctx.session, "restore.sh", {
@@ -9460,7 +9648,8 @@ var restore = addTargetOptions(
9460
9648
  ...options.migrate ? [] : ["--no-migrate"],
9461
9649
  // A copy this machine pushed up is scratch; one already in the
9462
9650
  // instance's own backups directory is not ours to delete.
9463
- ...local ? ["--cleanup-archive"] : []
9651
+ ...local ? ["--cleanup-archive"] : [],
9652
+ ...lockWaitArgs(options.lockWait)
9464
9653
  ],
9465
9654
  stream: true
9466
9655
  });
@@ -9526,7 +9715,7 @@ Take one with ${pc31.cyan("vela backup create")}, or pass the path to an archive
9526
9715
  });
9527
9716
  if (p56.isCancel(chosen)) {
9528
9717
  p56.cancel("Operation cancelled.");
9529
- process33.exit(0);
9718
+ process34.exit(0);
9530
9719
  }
9531
9720
  return chosen;
9532
9721
  }
@@ -9545,7 +9734,7 @@ async function stage(ctx, file) {
9545
9734
  spinner7.stop(`Uploaded ${path42.basename(file)}`);
9546
9735
  return remote;
9547
9736
  }
9548
- async function confirm12(appName, targetName, envTag, from) {
9737
+ async function confirm13(appName, targetName, envTag, from) {
9549
9738
  const what = `${pc31.cyan(`${appName} (${targetName})`)} from ${pc31.cyan(path42.basename(from))}`;
9550
9739
  if (isProd(envTag)) {
9551
9740
  const answer = await p56.text({
@@ -9554,7 +9743,7 @@ async function confirm12(appName, targetName, envTag, from) {
9554
9743
  });
9555
9744
  if (p56.isCancel(answer)) {
9556
9745
  p56.cancel("Operation cancelled.");
9557
- process33.exit(0);
9746
+ process34.exit(0);
9558
9747
  }
9559
9748
  return;
9560
9749
  }
@@ -9564,7 +9753,7 @@ async function confirm12(appName, targetName, envTag, from) {
9564
9753
  });
9565
9754
  if (p56.isCancel(ok) || !ok) {
9566
9755
  p56.cancel("Operation cancelled.");
9567
- process33.exit(0);
9756
+ process34.exit(0);
9568
9757
  }
9569
9758
  }
9570
9759
 
@@ -9658,20 +9847,20 @@ Release and domain are shown from what this project recorded.`
9658
9847
 
9659
9848
  // src/commands/test.ts
9660
9849
  import path43 from "node:path";
9661
- import process34 from "node:process";
9850
+ import process35 from "node:process";
9662
9851
  import { Command as Command96 } from "commander";
9663
9852
  import PocketBase6 from "pocketbase";
9664
9853
  import pc33 from "picocolors";
9665
9854
  import { x as x5 } from "tinyexec";
9666
9855
  import { detect as detect7 } from "package-manager-detector";
9667
9856
  import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
9668
- import fs40 from "node:fs";
9857
+ import fs41 from "node:fs";
9669
9858
  var testServer = new Command96("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
9670
- const cwd = process34.cwd();
9859
+ const cwd = process35.cwd();
9671
9860
  const email3 = `test-${Math.random().toString(36).slice(2)}@example.com`;
9672
9861
  const password11 = "password";
9673
9862
  const testDataDir = path43.join(cwd, "test-data");
9674
- fs40.rmSync(testDataDir, { recursive: true, force: true });
9863
+ fs41.rmSync(testDataDir, { recursive: true, force: true });
9675
9864
  const { stop, url } = await launchPocketbase(cwd, {
9676
9865
  dir: testDataDir,
9677
9866
  migrationsDir: path43.join(cwd, MIGRATIONS_DIR),
@@ -9682,11 +9871,11 @@ var testServer = new Command96("test:server").description("run server tests").al
9682
9871
  email: email3,
9683
9872
  password: password11
9684
9873
  });
9685
- process34.env.POCKETBASE_URL = url;
9686
- process34.env.POCKETBASE_SUPERUSER_EMAIL = email3;
9687
- process34.env.POCKETBASE_SUPERUSER_PASSWORD = password11;
9688
- process34.env.VELA_DATA_DIR = testDataDir;
9689
- process34.env.TEST = "true";
9874
+ process35.env.POCKETBASE_URL = url;
9875
+ process35.env.POCKETBASE_SUPERUSER_EMAIL = email3;
9876
+ process35.env.POCKETBASE_SUPERUSER_PASSWORD = password11;
9877
+ process35.env.VELA_DATA_DIR = testDataDir;
9878
+ process35.env.TEST = "true";
9690
9879
  console.log(`${pc33.greenBright("\u2713")} Created test database`);
9691
9880
  let vite;
9692
9881
  let cleanedUp = false;
@@ -9694,7 +9883,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9694
9883
  if (cleanedUp) return;
9695
9884
  cleanedUp = true;
9696
9885
  stop();
9697
- fs40.rmSync(testDataDir, { recursive: true, force: true });
9886
+ fs41.rmSync(testDataDir, { recursive: true, force: true });
9698
9887
  };
9699
9888
  const cleanup = async () => {
9700
9889
  if (cleanedUp) return;
@@ -9705,12 +9894,12 @@ var testServer = new Command96("test:server").description("run server tests").al
9705
9894
  const fail = async (message) => {
9706
9895
  console.error(`${pc33.redBright("\u2717")} ${message}`);
9707
9896
  await cleanup();
9708
- process34.exit(1);
9897
+ process35.exit(1);
9709
9898
  };
9710
- process34.on("exit", cleanupSync);
9711
- process34.on("SIGINT", () => {
9899
+ process35.on("exit", cleanupSync);
9900
+ process35.on("SIGINT", () => {
9712
9901
  cleanupSync();
9713
- process34.exit(130);
9902
+ process35.exit(130);
9714
9903
  });
9715
9904
  const pb = new PocketBase6(url);
9716
9905
  try {
@@ -9738,7 +9927,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9738
9927
  });
9739
9928
  const vitePort = await findFreePort();
9740
9929
  await vite.listen(vitePort);
9741
- process34.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
9930
+ process35.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
9742
9931
  console.log(`${pc33.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
9743
9932
  console.log(`${pc33.greenBright("\u2713")} Started PocketBase: ${url}`);
9744
9933
  const extraArgs = (cmd.parent?.args ?? []).slice(1);
@@ -9757,12 +9946,12 @@ var testServer = new Command96("test:server").description("run server tests").al
9757
9946
  const resolvedArgs = resolved.args.slice();
9758
9947
  if (pm === "npm") resolvedArgs.unshift("--yes");
9759
9948
  const result = await x5(resolved.command, resolvedArgs, {
9760
- nodeOptions: { cwd, stdio: "inherit", env: { ...process34.env, CI: "1" } }
9949
+ nodeOptions: { cwd, stdio: "inherit", env: { ...process35.env, CI: "1" } }
9761
9950
  });
9762
- process34.exitCode = result.exitCode ?? 1;
9951
+ process35.exitCode = result.exitCode ?? 1;
9763
9952
  } catch (e) {
9764
9953
  console.error(`${pc33.redBright("\u2717")} ${e.message}`);
9765
- process34.exitCode = 1;
9954
+ process35.exitCode = 1;
9766
9955
  } finally {
9767
9956
  await cleanup();
9768
9957
  }
@@ -9793,7 +9982,7 @@ function stubPagesPlugin() {
9793
9982
  }
9794
9983
 
9795
9984
  // src/commands/routes.ts
9796
- import fs41 from "node:fs";
9985
+ import fs42 from "node:fs";
9797
9986
  import path44 from "node:path";
9798
9987
  import { Command as Command97 } from "commander";
9799
9988
  var HTTP_METHODS = /* @__PURE__ */ new Set([
@@ -9814,7 +10003,7 @@ var routes = new Command97("routes").description("list routes").configureHelp(he
9814
10003
  printTable(found);
9815
10004
  });
9816
10005
  function walk(root, dir) {
9817
- const entries = fs41.readdirSync(dir, { withFileTypes: true });
10006
+ const entries = fs42.readdirSync(dir, { withFileTypes: true });
9818
10007
  const routes2 = [];
9819
10008
  const hasLeaf = entries.some((e) => e.isFile() && isRouteFile(e.name));
9820
10009
  if (hasLeaf) {
@@ -9840,7 +10029,7 @@ function isRouteFile(name) {
9840
10029
  }
9841
10030
  function extractMethods(file) {
9842
10031
  try {
9843
- const content = fs41.readFileSync(file, "utf8");
10032
+ const content = fs42.readFileSync(file, "utf8");
9844
10033
  const methods = [];
9845
10034
  const exportRegex = /export\s+(?:const|async\s+function|function)\s+(\w+)/g;
9846
10035
  let match;
@@ -9881,13 +10070,13 @@ function printTable(routes2) {
9881
10070
  }
9882
10071
 
9883
10072
  // src/commands/i18n.ts
9884
- import process35 from "node:process";
10073
+ import process36 from "node:process";
9885
10074
  import { Command as Command98 } from "commander";
9886
10075
  import { x as x6 } from "tinyexec";
9887
10076
  import { detect as detect8 } from "package-manager-detector";
9888
10077
  import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
9889
10078
  async function runWuchale(extraArgs) {
9890
- const cwd = process35.cwd();
10079
+ const cwd = process36.cwd();
9891
10080
  const pm = (await detect8({ cwd }))?.name ?? "npm";
9892
10081
  const resolved = resolveCommand8(pm, "execute", ["wuchale", ...extraArgs]);
9893
10082
  const args = resolved.args.slice();
@@ -9924,7 +10113,7 @@ import pc35 from "picocolors";
9924
10113
  // src/lib/cms-backend.ts
9925
10114
  import { createRequire as createRequire3 } from "node:module";
9926
10115
  import path45 from "node:path";
9927
- import process36 from "node:process";
10116
+ import process37 from "node:process";
9928
10117
  import { pathToFileURL as pathToFileURL3 } from "node:url";
9929
10118
  import pc34 from "picocolors";
9930
10119
  var DEFAULT_PROJECT = "default";
@@ -9941,7 +10130,7 @@ Run ${pc34.cyan("vela enable cms")} first.`
9941
10130
  }
9942
10131
  return await import(pathToFileURL3(entry).href);
9943
10132
  }
9944
- async function withCmsBackend(fn, cwd = process36.cwd()) {
10133
+ async function withCmsBackend(fn, cwd = process37.cwd()) {
9945
10134
  const root = findWorkspaceRoot(cwd);
9946
10135
  if (!root) {
9947
10136
  throw new Error("Could not find workspace root (no package.json found)");
@@ -10060,7 +10249,7 @@ var SELF_CREDENTIALED_COMMANDS = /* @__PURE__ */ new Set(["test:server"]);
10060
10249
  var program = new Command104().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
10061
10250
  program.hook("preAction", (_thisCommand, actionCommand) => {
10062
10251
  if (isStub(actionCommand)) return;
10063
- const envRoot = findWorkspaceRoot() ?? process37.cwd();
10252
+ const envRoot = findWorkspaceRoot() ?? process38.cwd();
10064
10253
  dotenv2.config({ path: nodePath.join(envRoot, ".env"), quiet: true });
10065
10254
  const path46 = getCommandPath(actionCommand);
10066
10255
  if (NO_BACKEND_COMMMANDS.has(path46)) return;
@@ -10077,10 +10266,10 @@ To add a backend to this project, run ${pc38.cyan("vela bless")}.`
10077
10266
  );
10078
10267
  p61.log.message();
10079
10268
  p61.cancel("Operation failed.");
10080
- process37.exit(1);
10269
+ process38.exit(1);
10081
10270
  }
10082
10271
  if (SELF_CREDENTIALED_COMMANDS.has(path46)) return;
10083
- if (!process37.env.POCKETBASE_SUPERUSER_EMAIL || !process37.env.POCKETBASE_SUPERUSER_PASSWORD) {
10272
+ if (!process38.env.POCKETBASE_SUPERUSER_EMAIL || !process38.env.POCKETBASE_SUPERUSER_PASSWORD) {
10084
10273
  p61.log.error(
10085
10274
  `PocketBase superuser credentials are required.
10086
10275
 
@@ -10091,7 +10280,7 @@ To set up an existing project, run ${pc38.cyan("vela bless")}.`
10091
10280
  );
10092
10281
  p61.log.message();
10093
10282
  p61.cancel("Operation failed.");
10094
- process37.exit(1);
10283
+ process38.exit(1);
10095
10284
  }
10096
10285
  });
10097
10286
  function getCommandPath(cmd) {
@@ -10145,5 +10334,5 @@ for (const command of [
10145
10334
  }
10146
10335
 
10147
10336
  // src/bin.ts
10148
- program.parse(normalizeArgv(process38.argv.slice(2)), { from: "user" });
10337
+ program.parse(normalizeArgv(process39.argv.slice(2)), { from: "user" });
10149
10338
  //# sourceMappingURL=bin.js.map