vela 0.11.2 → 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.2",
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,17 +45,19 @@ 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",
57
59
  "@faker-js/faker": "^10.6.0",
58
- "@velastack/patterns": "^0.2.2",
60
+ "@velastack/patterns": "^0.2.3",
59
61
  "@velastack/pocketbase-codegen": "^0.1.0",
60
62
  "annotate-json-schema": "^0.1.0",
61
63
  commander: "^13.1.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",
@@ -2462,7 +2465,7 @@ var form = new Command4("form").description("generate a form from a model").argu
2462
2465
  ).option(
2463
2466
  "--ai <description>",
2464
2467
  "design the form with AI from a natural-language description (two stages: schema \u2192 layout)"
2465
- ).allowUnknownOption(true).configureHelp(helpConfig).action(
2468
+ ).allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2466
2469
  (model, fields, options) => runCommand(async () => {
2467
2470
  let argv;
2468
2471
  let modelName;
@@ -2521,7 +2524,7 @@ var form = new Command4("form").description("generate a form from a model").argu
2521
2524
  // src/commands/generate/schema.ts
2522
2525
  import { Command as Command5 } from "commander";
2523
2526
  import * as p12 from "@clack/prompts";
2524
- var schema = new Command5("schema").description("generate a schema from a model").argument("[model]", "model name").argument("[fields...]", "field definitions").option("--ai <description>", "design the schema with AI from a natural-language description").allowUnknownOption(true).configureHelp(helpConfig).action(
2527
+ var schema = new Command5("schema").description("generate a schema from a model").argument("[model]", "model name").argument("[fields...]", "field definitions").option("--ai <description>", "design the schema with AI from a natural-language description").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2525
2528
  (model, fields, options) => runCommand(async () => {
2526
2529
  let argv;
2527
2530
  let modelName;
@@ -2565,7 +2568,7 @@ var schema = new Command5("schema").description("generate a schema from a model"
2565
2568
 
2566
2569
  // src/commands/generate/resource.ts
2567
2570
  import { Command as Command6 } from "commander";
2568
- var resource = new Command6("resource").description("generate a resource (model + CRUD pages)").argument("<model>", "model name").argument("[fields...]", "field definitions").allowUnknownOption(true).configureHelp(helpConfig).action(
2571
+ var resource = new Command6("resource").description("generate a resource (model + CRUD pages)").argument("<model>", "model name").argument("[fields...]", "field definitions").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2569
2572
  (model, fields) => runCommand(
2570
2573
  () => runPattern(
2571
2574
  "generate-resource",
@@ -2598,7 +2601,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2598
2601
  ).option(
2599
2602
  "--ai <description>",
2600
2603
  "design the scaffold with AI from a natural-language description (two stages: schema \u2192 layout)"
2601
- ).allowUnknownOption(true).configureHelp(helpConfig).action(
2604
+ ).allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2602
2605
  (model, fields, options) => runCommand(async () => {
2603
2606
  let argv;
2604
2607
  let modelName;
@@ -2657,7 +2660,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2657
2660
 
2658
2661
  // src/commands/generate/migration.ts
2659
2662
  import { Command as Command8 } from "commander";
2660
- var migration = new Command8("migration").description("generate a migration for an existing collection").argument("<collection>", "collection name").argument("<op>", "operation (add, remove, rename, references)").argument("[args...]", 'operation arguments (e.g. "birthday:date")').allowUnknownOption(true).configureHelp(helpConfig).action(
2663
+ var migration = new Command8("migration").description("generate a migration for an existing collection").argument("<collection>", "collection name").argument("<op>", "operation (add, remove, rename, references)").argument("[args...]", 'operation arguments (e.g. "birthday:date")').allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2661
2664
  (collection, op, args) => runCommand(
2662
2665
  () => runPattern(
2663
2666
  "generate-migration",
@@ -2688,7 +2691,7 @@ import { Command as Command24 } from "commander";
2688
2691
 
2689
2692
  // src/commands/enable/auth.ts
2690
2693
  import { Command as Command10 } from "commander";
2691
- var auth = new Command10("auth").description("enable authentication (email/password + OAuth scaffold)").allowUnknownOption(true).configureHelp(helpConfig).action(
2694
+ var auth = new Command10("auth").description("enable authentication (email/password + OAuth scaffold)").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2692
2695
  (_opts, cmd) => runCommand(
2693
2696
  () => runPattern(
2694
2697
  "enable-auth",
@@ -2714,7 +2717,7 @@ var auth = new Command10("auth").description("enable authentication (email/passw
2714
2717
 
2715
2718
  // src/commands/enable/api.ts
2716
2719
  import { Command as Command11 } from "commander";
2717
- var api = new Command11("api").description("enable the PocketBase REST API").allowUnknownOption(true).configureHelp(helpConfig).action(
2720
+ var api = new Command11("api").description("enable the PocketBase REST API").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2718
2721
  (_opts, cmd) => runCommand(
2719
2722
  () => runPattern(
2720
2723
  "enable-api",
@@ -2740,7 +2743,7 @@ var api = new Command11("api").description("enable the PocketBase REST API").all
2740
2743
 
2741
2744
  // src/commands/enable/api-keys.ts
2742
2745
  import { Command as Command12 } from "commander";
2743
- var apiKeys = new Command12("api-keys").description("enable API key management").allowUnknownOption(true).configureHelp(helpConfig).action(
2746
+ var apiKeys = new Command12("api-keys").description("enable API key management").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2744
2747
  (_opts, cmd) => runCommand(
2745
2748
  () => runPattern(
2746
2749
  "enable-api-keys",
@@ -2766,7 +2769,7 @@ var apiKeys = new Command12("api-keys").description("enable API key management")
2766
2769
 
2767
2770
  // src/commands/enable/backend.ts
2768
2771
  import { Command as Command13 } from "commander";
2769
- var backend = new Command13("backend").description("enable the PocketBase backend").allowUnknownOption(true).configureHelp(helpConfig).action(
2772
+ var backend = new Command13("backend").description("enable the PocketBase backend").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2770
2773
  (_opts, cmd) => runCommand(
2771
2774
  () => runPattern(
2772
2775
  "enable-backend",
@@ -2792,7 +2795,7 @@ var backend = new Command13("backend").description("enable the PocketBase backen
2792
2795
 
2793
2796
  // src/commands/enable/i18n.ts
2794
2797
  import { Command as Command14 } from "commander";
2795
- var i18n = new Command14("i18n").description("enable internationalization").allowUnknownOption(true).configureHelp(helpConfig).action(
2798
+ var i18n = new Command14("i18n").description("enable internationalization").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2796
2799
  (_opts, cmd) => runCommand(
2797
2800
  () => runPattern(
2798
2801
  "enable-i18n",
@@ -2818,7 +2821,7 @@ var i18n = new Command14("i18n").description("enable internationalization").allo
2818
2821
 
2819
2822
  // src/commands/enable/teams.ts
2820
2823
  import { Command as Command15 } from "commander";
2821
- var teams = new Command15("teams").description("enable team / multi-tenant support").allowUnknownOption(true).configureHelp(helpConfig).action(
2824
+ var teams = new Command15("teams").description("enable team / multi-tenant support").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2822
2825
  (_opts, cmd) => runCommand(
2823
2826
  () => runPattern(
2824
2827
  "enable-teams",
@@ -2847,7 +2850,7 @@ import process9 from "node:process";
2847
2850
  import { Command as Command16 } from "commander";
2848
2851
  import * as p14 from "@clack/prompts";
2849
2852
  var PROVIDERS = [{ value: "stripe", label: "Stripe" }];
2850
- var payments = new Command16("payments").description("enable payments").option("--provider <provider>", "payment provider", "stripe").allowUnknownOption(true).configureHelp(helpConfig).action(
2853
+ var payments = new Command16("payments").description("enable payments").option("--provider <provider>", "payment provider", "stripe").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2851
2854
  (opts, cmd) => runCommand(async () => {
2852
2855
  const provider = await resolveProvider(opts.provider, cmd.getOptionValueSource("provider"));
2853
2856
  const input = {
@@ -2945,7 +2948,7 @@ async function promptPassword(message) {
2945
2948
 
2946
2949
  // src/commands/enable/subscriptions.ts
2947
2950
  import { Command as Command17 } from "commander";
2948
- var subscriptions = new Command17("subscriptions").description("enable Stripe subscriptions (requires auth and payments)").allowUnknownOption(true).configureHelp(helpConfig).action(
2951
+ var subscriptions = new Command17("subscriptions").description("enable Stripe subscriptions (requires auth and payments)").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2949
2952
  (_opts, cmd) => runCommand(
2950
2953
  () => runPattern(
2951
2954
  "enable-subscriptions",
@@ -2971,7 +2974,7 @@ var subscriptions = new Command17("subscriptions").description("enable Stripe su
2971
2974
 
2972
2975
  // src/commands/enable/notifications.ts
2973
2976
  import { Command as Command18 } from "commander";
2974
- var notifications = new Command18("notifications").description("enable in-app notifications with a bell dropdown (requires auth)").allowUnknownOption(true).configureHelp(helpConfig).action(
2977
+ var notifications = new Command18("notifications").description("enable in-app notifications with a bell dropdown (requires auth)").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
2975
2978
  (_opts, cmd) => runCommand(
2976
2979
  () => runPattern(
2977
2980
  "enable-notifications",
@@ -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,47 +4455,45 @@ 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";
4364
- var cms = new Command21("cms").description("enable an inline-editing CMS with an admin bar").allowUnknownOption(true).configureHelp(helpConfig).action(
4365
- (_opts, cmd) => runCommand(async () => {
4366
- if (!hasBackend()) {
4462
+ var cms = new Command21("cms").description("enable an inline-editing CMS with an admin bar").option(
4463
+ "--endpoint <url>",
4464
+ "read from a hosted CMS at this URL instead of installing the backend in this app"
4465
+ ).allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4466
+ (opts, cmd) => runCommand(async () => {
4467
+ if (!opts.endpoint && !hasBackend()) {
4367
4468
  p19.log.error(
4368
4469
  `${pc9.cyan("vela enable cms")} needs a server to host the CMS backend, and this project is static.
4369
4470
 
4370
- Run ${pc9.cyan("vela bless")} to add a backend first.`
4471
+ Run ${pc9.cyan("vela bless")} to add a backend, or point at a hosted CMS with ${pc9.cyan("vela enable cms --endpoint <url>")}.`
4371
4472
  );
4372
4473
  p19.log.message();
4373
4474
  p19.cancel("Operation failed.");
4374
- process14.exitCode = 1;
4475
+ process15.exitCode = 1;
4375
4476
  return;
4376
4477
  }
4377
- await runPattern(
4378
- "enable-cms",
4379
- cmd.args,
4380
- {},
4381
- {
4382
- summary: "Enabled CMS.",
4383
- nextSteps: [
4384
- "Run `vela cms editor add you@example.com` to create the first editor login.",
4385
- "Run `vela dev`, open any page with `?edit` on the URL, and sign in from the admin bar.",
4386
- "Wrap page copy in `<CmsText>` and images in `<CmsImage>` to make them editable."
4387
- ],
4388
- task: {
4389
- title: "Enabling CMS",
4390
- success: "Enabled CMS",
4391
- error: "Failed to enable CMS"
4392
- }
4478
+ await runPattern("enable-cms", cmd.args, opts.endpoint ? { endpoint: opts.endpoint } : {}, {
4479
+ summary: "Enabled CMS.",
4480
+ nextSteps: [
4481
+ opts.endpoint ? "Editors are managed where the CMS is hosted, not with `vela cms editor`." : "Run `vela cms editor add you@example.com` to create the first editor login.",
4482
+ "Run `vela dev`, open any page with `?edit` on the URL, and sign in from the admin bar.",
4483
+ "Wrap page copy in `<CmsText>` and images in `<CmsImage>` to make them editable."
4484
+ ],
4485
+ task: {
4486
+ title: "Enabling CMS",
4487
+ success: "Enabled CMS",
4488
+ error: "Failed to enable CMS"
4393
4489
  }
4394
- );
4490
+ });
4395
4491
  }, "Failed to enable CMS.")
4396
4492
  );
4397
4493
 
4398
4494
  // src/commands/enable/blog.ts
4399
4495
  import { Command as Command22 } from "commander";
4400
- var blog = new Command22("blog").description("enable an mdsvex blog with posts, tags, and RSS").allowUnknownOption(true).configureHelp(helpConfig).action(
4496
+ var blog = new Command22("blog").description("enable an mdsvex blog with posts, tags, and RSS").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4401
4497
  (_opts, cmd) => runCommand(
4402
4498
  () => runPattern(
4403
4499
  "enable-blog",
@@ -4423,7 +4519,7 @@ var blog = new Command22("blog").description("enable an mdsvex blog with posts,
4423
4519
 
4424
4520
  // src/commands/enable/content-negotiation.ts
4425
4521
  import { Command as Command23 } from "commander";
4426
- var contentNegotiation = new Command23("content-negotiation").description("enable content negotiation (sveltekit-negotiate)").allowUnknownOption(true).configureHelp(helpConfig).action(
4522
+ var contentNegotiation = new Command23("content-negotiation").description("enable content negotiation (sveltekit-negotiate)").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4427
4523
  (_opts, cmd) => runCommand(
4428
4524
  () => runPattern(
4429
4525
  "enable-content-negotiation",
@@ -4456,7 +4552,7 @@ import { Command as Command35 } from "commander";
4456
4552
  import { Command as Command25 } from "commander";
4457
4553
 
4458
4554
  // src/commands/disable/_shared.ts
4459
- import process15 from "node:process";
4555
+ import process16 from "node:process";
4460
4556
  import * as p20 from "@clack/prompts";
4461
4557
  async function runDisable(opts, flags, cmdArgs) {
4462
4558
  if (!flags.yes) {
@@ -4466,14 +4562,14 @@ async function runDisable(opts, flags, cmdArgs) {
4466
4562
  });
4467
4563
  if (p20.isCancel(ok) || !ok) {
4468
4564
  p20.cancel("Operation cancelled.");
4469
- process15.exit(0);
4565
+ process16.exit(0);
4470
4566
  }
4471
4567
  }
4472
4568
  await runPattern(opts.slug, cmdArgs, { destructive: true }, opts.report);
4473
4569
  }
4474
4570
 
4475
4571
  // src/commands/disable/auth.ts
4476
- var auth2 = new Command25("auth").description("disable authentication").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4572
+ var auth2 = new Command25("auth").description("disable authentication").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4477
4573
  (opts, cmd) => runCommand(
4478
4574
  () => runDisable(
4479
4575
  {
@@ -4500,7 +4596,7 @@ var auth2 = new Command25("auth").description("disable authentication").option("
4500
4596
 
4501
4597
  // src/commands/disable/api.ts
4502
4598
  import { Command as Command26 } from "commander";
4503
- var api2 = new Command26("api").description("disable the REST API").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4599
+ var api2 = new Command26("api").description("disable the REST API").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4504
4600
  (opts, cmd) => runCommand(
4505
4601
  () => runDisable(
4506
4602
  {
@@ -4524,7 +4620,7 @@ var api2 = new Command26("api").description("disable the REST API").option("-y,
4524
4620
 
4525
4621
  // src/commands/disable/api-keys.ts
4526
4622
  import { Command as Command27 } from "commander";
4527
- var apiKeys2 = new Command27("api-keys").description("disable API key management").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4623
+ var apiKeys2 = new Command27("api-keys").description("disable API key management").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4528
4624
  (opts, cmd) => runCommand(
4529
4625
  () => runDisable(
4530
4626
  {
@@ -4548,7 +4644,7 @@ var apiKeys2 = new Command27("api-keys").description("disable API key management
4548
4644
 
4549
4645
  // src/commands/disable/backend.ts
4550
4646
  import { Command as Command28 } from "commander";
4551
- var backend2 = new Command28("backend").description("disable the PocketBase backend").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4647
+ var backend2 = new Command28("backend").description("disable the PocketBase backend").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4552
4648
  (opts, cmd) => runCommand(
4553
4649
  () => runDisable(
4554
4650
  {
@@ -4572,7 +4668,7 @@ var backend2 = new Command28("backend").description("disable the PocketBase back
4572
4668
 
4573
4669
  // src/commands/disable/content-negotiation.ts
4574
4670
  import { Command as Command29 } from "commander";
4575
- var contentNegotiation2 = new Command29("content-negotiation").description("disable content negotiation").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4671
+ var contentNegotiation2 = new Command29("content-negotiation").description("disable content negotiation").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4576
4672
  (opts, cmd) => runCommand(
4577
4673
  () => runDisable(
4578
4674
  {
@@ -4596,7 +4692,7 @@ var contentNegotiation2 = new Command29("content-negotiation").description("disa
4596
4692
 
4597
4693
  // src/commands/disable/i18n.ts
4598
4694
  import { Command as Command30 } from "commander";
4599
- var i18n2 = new Command30("i18n").description("disable internationalization").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4695
+ var i18n2 = new Command30("i18n").description("disable internationalization").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4600
4696
  (opts, cmd) => runCommand(
4601
4697
  () => runDisable(
4602
4698
  {
@@ -4623,7 +4719,7 @@ var i18n2 = new Command30("i18n").description("disable internationalization").op
4623
4719
 
4624
4720
  // src/commands/disable/teams.ts
4625
4721
  import { Command as Command31 } from "commander";
4626
- var teams2 = new Command31("teams").description("disable teams").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4722
+ var teams2 = new Command31("teams").description("disable teams").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4627
4723
  (opts, cmd) => runCommand(
4628
4724
  () => runDisable(
4629
4725
  {
@@ -4650,7 +4746,7 @@ var teams2 = new Command31("teams").description("disable teams").option("-y, --y
4650
4746
 
4651
4747
  // src/commands/disable/payments.ts
4652
4748
  import { Command as Command32 } from "commander";
4653
- var payments2 = new Command32("payments").description("disable payments").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4749
+ var payments2 = new Command32("payments").description("disable payments").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4654
4750
  (opts, cmd) => runCommand(
4655
4751
  () => runDisable(
4656
4752
  {
@@ -4737,14 +4833,14 @@ import { Command as Command41 } from "commander";
4737
4833
  import { Command as Command36 } from "commander";
4738
4834
 
4739
4835
  // src/commands/destroy/_shared.ts
4740
- import process16 from "node:process";
4836
+ import process17 from "node:process";
4741
4837
  import * as p22 from "@clack/prompts";
4742
4838
  async function runDestroy(slug2, model, confirmMessage, report4, flags) {
4743
4839
  if (!flags.yes) {
4744
4840
  const ok = await p22.confirm({ message: confirmMessage, initialValue: false });
4745
4841
  if (p22.isCancel(ok) || !ok) {
4746
4842
  p22.cancel("Operation cancelled.");
4747
- process16.exit(0);
4843
+ process17.exit(0);
4748
4844
  }
4749
4845
  }
4750
4846
  await runPattern(slug2, [model], { destructive: true, route: flags.route }, report4);
@@ -4754,7 +4850,7 @@ async function runDestroy(slug2, model, confirmMessage, report4, flags) {
4754
4850
  var form2 = new Command36("form").description("destroy a form generated by `vela generate form`").argument("<model>", "model name used when the form was generated (e.g., contact, login)").option("-y, --yes", "skip confirmation prompt").option(
4755
4851
  "--route <route>",
4756
4852
  "custom route the form was generated at (must match the --route used at generation)"
4757
- ).allowUnknownOption(true).configureHelp(helpConfig).action(
4853
+ ).allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4758
4854
  (model, opts) => runCommand(
4759
4855
  () => runDestroy(
4760
4856
  "destroy-form",
@@ -4776,7 +4872,7 @@ var form2 = new Command36("form").description("destroy a form generated by `vela
4776
4872
 
4777
4873
  // src/commands/destroy/schema.ts
4778
4874
  import { Command as Command37 } from "commander";
4779
- var schema2 = new Command37("schema").description("destroy a zod schema generated by `vela generate schema`").argument("<model>", "schema model name (e.g., contact, login)").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4875
+ var schema2 = new Command37("schema").description("destroy a zod schema generated by `vela generate schema`").argument("<model>", "schema model name (e.g., contact, login)").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4780
4876
  (model, opts) => runCommand(
4781
4877
  () => runDestroy(
4782
4878
  "destroy-schema",
@@ -4798,7 +4894,7 @@ var schema2 = new Command37("schema").description("destroy a zod schema generate
4798
4894
 
4799
4895
  // src/commands/destroy/resource.ts
4800
4896
  import { Command as Command38 } from "commander";
4801
- var resource2 = new Command38("resource").description("destroy a resource generated by `vela generate resource`").argument("<model>", "model path used when the resource was generated (e.g., contacts, articles)").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).configureHelp(helpConfig).action(
4897
+ var resource2 = new Command38("resource").description("destroy a resource generated by `vela generate resource`").argument("<model>", "model path used when the resource was generated (e.g., contacts, articles)").option("-y, --yes", "skip confirmation prompt").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4802
4898
  (model, opts) => runCommand(
4803
4899
  () => runDestroy(
4804
4900
  "destroy-resource",
@@ -4823,7 +4919,7 @@ import { Command as Command39 } from "commander";
4823
4919
  var scaffold2 = new Command39("scaffold").description("destroy a scaffold generated by `vela generate scaffold`").argument("<model>", "model name used when the scaffold was generated (e.g., contact, todo)").option("-y, --yes", "skip confirmation prompt").option(
4824
4920
  "--route <route>",
4825
4921
  "custom route the scaffold was generated at (must match the --route used at generation)"
4826
- ).allowUnknownOption(true).configureHelp(helpConfig).action(
4922
+ ).allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(
4827
4923
  (model, opts) => runCommand(
4828
4924
  () => runDestroy(
4829
4925
  "destroy-scaffold",
@@ -4847,11 +4943,55 @@ var scaffold2 = new Command39("scaffold").description("destroy a scaffold genera
4847
4943
  );
4848
4944
 
4849
4945
  // src/commands/destroy/deployment.ts
4850
- import process17 from "node:process";
4946
+ import process18 from "node:process";
4851
4947
  import { Command as Command40 } from "commander";
4852
4948
  import * as p25 from "@clack/prompts";
4853
4949
  import pc13 from "picocolors";
4854
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
+
4855
4995
  // src/lib/deploy-report.ts
4856
4996
  import * as p24 from "@clack/prompts";
4857
4997
  import pc12 from "picocolors";
@@ -5044,26 +5184,52 @@ ${pc12.dim(err instanceof Error ? err.message : String(err))}`
5044
5184
  }
5045
5185
 
5046
5186
  // src/commands/destroy/deployment.ts
5047
- var deployment = addTargetOptions(
5048
- new Command40("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
5049
- "production"
5050
- ).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(
5051
5196
  (raw) => runCommand(async () => {
5052
5197
  const options = raw;
5053
5198
  await withTarget(
5054
5199
  raw,
5055
5200
  {
5056
5201
  remote: async (ctx) => {
5057
- if (!options.yes) {
5058
- 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);
5059
5213
  }
5060
5214
  const result = await runServerScript(ctx.session, "destroy.sh", {
5061
- args: [ctx.instance, ...options.purge ? ["--purge"] : []],
5215
+ args: [
5216
+ ctx.instance,
5217
+ ...options.purge ? ["--purge"] : [],
5218
+ ...lockWaitArgs(options.lockWait)
5219
+ ],
5062
5220
  stream: true
5063
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
+ }
5064
5228
  await reportEnvironmentDestroyed(ctx.workspaceRootDir, ctx.envTag);
5065
5229
  p25.log.success(
5066
- `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.` : "" : `
5067
5233
 
5068
5234
  The database and uploads are still in the instance's shared directory. Pass ${pc13.cyan("--purge")} to delete them.`)
5069
5235
  );
@@ -5076,15 +5242,15 @@ The database and uploads are still in the instance's shared directory. Pass ${pc
5076
5242
  );
5077
5243
  }, "Failed to remove the deployment.")
5078
5244
  );
5079
- async function confirm7(appName, targetName, envTag, purge) {
5080
- if (isProd(envTag) || purge) {
5245
+ async function confirm8(appName, targetName, byName) {
5246
+ if (byName) {
5081
5247
  const answer = await p25.text({
5082
- 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`,
5083
5249
  validate: (value) => value === appName ? void 0 : `Type ${appName} to confirm`
5084
5250
  });
5085
5251
  if (p25.isCancel(answer)) {
5086
5252
  p25.cancel("Operation cancelled.");
5087
- process17.exit(0);
5253
+ process18.exit(0);
5088
5254
  }
5089
5255
  return;
5090
5256
  }
@@ -5094,7 +5260,7 @@ async function confirm7(appName, targetName, envTag, purge) {
5094
5260
  });
5095
5261
  if (p25.isCancel(ok) || !ok) {
5096
5262
  p25.cancel("Operation cancelled.");
5097
- process17.exit(0);
5263
+ process18.exit(0);
5098
5264
  }
5099
5265
  }
5100
5266
 
@@ -5378,13 +5544,13 @@ var ui = new Command47("ui").description("generate ui components").configureHelp
5378
5544
  import { Command as Command50 } from "commander";
5379
5545
 
5380
5546
  // src/commands/legal/terms.ts
5381
- import fs25 from "node:fs";
5547
+ import fs26 from "node:fs";
5382
5548
  import path23 from "node:path";
5383
5549
  import { Command as Command48 } from "commander";
5384
5550
  import * as p32 from "@clack/prompts";
5385
5551
 
5386
5552
  // src/commands/legal/shared.ts
5387
- import process18 from "node:process";
5553
+ import process19 from "node:process";
5388
5554
  import * as p31 from "@clack/prompts";
5389
5555
  var sharedFields = {
5390
5556
  websiteUrl: () => p31.text({
@@ -5453,7 +5619,7 @@ var sharedFields = {
5453
5619
  };
5454
5620
  var onCancel = () => {
5455
5621
  p31.cancel("Operation cancelled.");
5456
- process18.exit(0);
5622
+ process19.exit(0);
5457
5623
  };
5458
5624
  async function contactMethods(type) {
5459
5625
  const selection = await p31.multiselect({
@@ -6046,9 +6212,9 @@ async function termsAction() {
6046
6212
  "+page.svelte"
6047
6213
  );
6048
6214
  const termsPageTs = path23.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
6049
- fs25.mkdirSync(path23.dirname(termsPage), { recursive: true });
6050
- fs25.writeFileSync(termsPage, html);
6051
- fs25.writeFileSync(
6215
+ fs26.mkdirSync(path23.dirname(termsPage), { recursive: true });
6216
+ fs26.writeFileSync(termsPage, html);
6217
+ fs26.writeFileSync(
6052
6218
  termsPageTs,
6053
6219
  pageMetaTagsLoader("Terms of Service", `Terms of Service for ${core.websiteName}`)
6054
6220
  );
@@ -6067,7 +6233,7 @@ async function termsAction() {
6067
6233
  var terms = new Command48("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
6068
6234
 
6069
6235
  // src/commands/legal/privacy.ts
6070
- import fs26 from "node:fs";
6236
+ import fs27 from "node:fs";
6071
6237
  import path24 from "node:path";
6072
6238
  import { Command as Command49 } from "commander";
6073
6239
  import * as p33 from "@clack/prompts";
@@ -6795,9 +6961,9 @@ async function privacyAction() {
6795
6961
  "privacy",
6796
6962
  "+page.ts"
6797
6963
  );
6798
- fs26.mkdirSync(path24.dirname(privacyPage), { recursive: true });
6799
- fs26.writeFileSync(privacyPage, html);
6800
- fs26.writeFileSync(
6964
+ fs27.mkdirSync(path24.dirname(privacyPage), { recursive: true });
6965
+ fs27.writeFileSync(privacyPage, html);
6966
+ fs27.writeFileSync(
6801
6967
  privacyPageTs,
6802
6968
  pageMetaTagsLoader("Privacy Policy", `Privacy Policy for ${core.websiteName}`)
6803
6969
  );
@@ -6825,7 +6991,7 @@ import { Command as Command56 } from "commander";
6825
6991
  import { Command as Command51 } from "commander";
6826
6992
 
6827
6993
  // src/lib/data.ts
6828
- import fs27 from "node:fs";
6994
+ import fs28 from "node:fs";
6829
6995
  import path25 from "node:path";
6830
6996
  import { ClientResponseError } from "pocketbase";
6831
6997
 
@@ -6867,8 +7033,8 @@ function dataDir(cwd, kind) {
6867
7033
  }
6868
7034
  function getDataFiles(cwd, kind) {
6869
7035
  const dir = dataDir(cwd, kind);
6870
- if (!fs27.existsSync(dir)) return [];
6871
- 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) => ({
6872
7038
  collectionName: file.replace(/\.json$/i, "").replace(/^\d+[-_]?/, ""),
6873
7039
  filePath: path25.join(dir, file)
6874
7040
  }));
@@ -6880,7 +7046,7 @@ function getFixtureFiles(cwd) {
6880
7046
  return getDataFiles(cwd, "fixtures");
6881
7047
  }
6882
7048
  function readRecords(filePath) {
6883
- return JSON.parse(fs27.readFileSync(filePath, "utf8"));
7049
+ return JSON.parse(fs28.readFileSync(filePath, "utf8"));
6884
7050
  }
6885
7051
  function readSeedIds(cwd) {
6886
7052
  const ids = /* @__PURE__ */ new Map();
@@ -7099,7 +7265,7 @@ var reset = new Command53("reset").description("clear and reload fixtures").conf
7099
7265
  );
7100
7266
 
7101
7267
  // src/commands/fixtures/generate.ts
7102
- import fs28 from "node:fs";
7268
+ import fs29 from "node:fs";
7103
7269
  import path26 from "node:path";
7104
7270
  import { Command as Command54, InvalidArgumentError } from "commander";
7105
7271
  import * as p34 from "@clack/prompts";
@@ -7137,9 +7303,9 @@ async function loadCollections(pb) {
7137
7303
  }
7138
7304
  async function generateFixtureFiles(pb, workspaceRootDir, opts) {
7139
7305
  const fixturesDir = dataDir(workspaceRootDir, "fixtures");
7140
- fs28.mkdirSync(fixturesDir, { recursive: true });
7141
- for (const file of fs28.readdirSync(fixturesDir)) {
7142
- 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));
7143
7309
  }
7144
7310
  if (opts.seed !== void 0) faker.seed(opts.seed);
7145
7311
  const generator = createGenerator({
@@ -7206,7 +7372,7 @@ async function generateFixtureFiles(pb, workspaceRootDir, opts) {
7206
7372
  items.push(record);
7207
7373
  }
7208
7374
  const filename = `${padZeros(fileIndex, 2)}-${collection.name}.json`;
7209
- fs28.writeFileSync(path26.join(fixturesDir, filename), JSON.stringify(items, null, 2));
7375
+ fs29.writeFileSync(path26.join(fixturesDir, filename), JSON.stringify(items, null, 2));
7210
7376
  writtenFiles.push(`${path26.join(DATA_DIR, "fixtures", filename)} (${items.length} records)`);
7211
7377
  fileIndex++;
7212
7378
  }
@@ -7354,7 +7520,7 @@ var load2 = new Command57("load").description("load seeds into the database").op
7354
7520
  );
7355
7521
 
7356
7522
  // src/commands/seeds/save.ts
7357
- import fs29 from "node:fs";
7523
+ import fs30 from "node:fs";
7358
7524
  import path27 from "node:path";
7359
7525
  import { Command as Command58 } from "commander";
7360
7526
  var padZeros2 = (num, length) => num.toString().padStart(length, "0");
@@ -7375,10 +7541,10 @@ var save = new Command58("save").description("save the current data as seeds").o
7375
7541
  if (existing.length > 0 && !opts.force) {
7376
7542
  throw new Error("Existing seed files found in data/seeds. Pass --force to overwrite.");
7377
7543
  }
7378
- fs29.mkdirSync(seedsPath, { recursive: true });
7544
+ fs30.mkdirSync(seedsPath, { recursive: true });
7379
7545
  if (opts.force) {
7380
- for (const file of fs29.readdirSync(seedsPath)) {
7381
- 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));
7382
7548
  }
7383
7549
  }
7384
7550
  const saved = [];
@@ -7413,7 +7579,7 @@ var save = new Command58("save").description("save the current data as seeds").o
7413
7579
  `${padZeros2(count, 2)}-${collectionName}.json`
7414
7580
  );
7415
7581
  const seedPath = path27.join(workspaceRootDir, relativeSeedPath);
7416
- fs29.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
7582
+ fs30.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
7417
7583
  saved.push(`${relativeSeedPath} (${filtered.length} records)`);
7418
7584
  count++;
7419
7585
  }
@@ -7625,10 +7791,10 @@ import { Command as Command65 } from "commander";
7625
7791
 
7626
7792
  // src/lib/migrate.ts
7627
7793
  import path28 from "node:path";
7628
- import process19 from "node:process";
7794
+ import process20 from "node:process";
7629
7795
  import { x as x2 } from "tinyexec";
7630
7796
  async function runPocketbaseMigrate(args) {
7631
- const cwd = process19.cwd();
7797
+ const cwd = process20.cwd();
7632
7798
  const { getBinaryPath } = await import("pocketbase-server");
7633
7799
  const binaryPath = getBinaryPath();
7634
7800
  await x2(
@@ -7685,13 +7851,13 @@ var down = new Command66("down").alias("rollback").description("revert the last
7685
7851
  );
7686
7852
 
7687
7853
  // src/commands/migrate/create.ts
7688
- import fs30 from "node:fs";
7854
+ import fs31 from "node:fs";
7689
7855
  import path29 from "node:path";
7690
- import process20 from "node:process";
7856
+ import process21 from "node:process";
7691
7857
  import { Command as Command67 } from "commander";
7692
7858
  var create2 = new Command67("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
7693
7859
  (name) => runCommand(async () => {
7694
- const cwd = process20.cwd();
7860
+ const cwd = process21.cwd();
7695
7861
  const before = listMigrationFiles(cwd);
7696
7862
  await runPocketbaseMigrate(["create", name]);
7697
7863
  const after = listMigrationFiles(cwd);
@@ -7708,18 +7874,18 @@ var create2 = new Command67("create").alias("new").description("create a new bla
7708
7874
  );
7709
7875
  function listMigrationFiles(cwd) {
7710
7876
  const dir = path29.join(cwd, MIGRATIONS_DIR);
7711
- if (!fs30.existsSync(dir)) return /* @__PURE__ */ new Set();
7712
- 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)));
7713
7879
  }
7714
7880
 
7715
7881
  // src/commands/migrate/collections.ts
7716
- import fs31 from "node:fs";
7882
+ import fs32 from "node:fs";
7717
7883
  import path30 from "node:path";
7718
- import process21 from "node:process";
7884
+ import process22 from "node:process";
7719
7885
  import { Command as Command68 } from "commander";
7720
7886
  var collections = new Command68("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
7721
7887
  () => runCommand(async () => {
7722
- const cwd = process21.cwd();
7888
+ const cwd = process22.cwd();
7723
7889
  const before = listMigrationFiles2(cwd);
7724
7890
  await runPocketbaseMigrate(["collections"]);
7725
7891
  const after = listMigrationFiles2(cwd);
@@ -7743,8 +7909,8 @@ var collections = new Command68("collections").alias("snapshot").description("sn
7743
7909
  );
7744
7910
  function listMigrationFiles2(cwd) {
7745
7911
  const dir = path30.join(cwd, MIGRATIONS_DIR);
7746
- if (!fs31.existsSync(dir)) return /* @__PURE__ */ new Set();
7747
- 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)));
7748
7914
  }
7749
7915
 
7750
7916
  // src/commands/migrate/history-sync.ts
@@ -7763,9 +7929,9 @@ var historySync = new Command69("history-sync").description("drop _migrations ro
7763
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);
7764
7930
 
7765
7931
  // src/commands/dev.ts
7766
- import fs32 from "node:fs";
7932
+ import fs33 from "node:fs";
7767
7933
  import path32 from "node:path";
7768
- import process23 from "node:process";
7934
+ import process24 from "node:process";
7769
7935
  import { performance } from "node:perf_hooks";
7770
7936
  import { Command as Command71, InvalidArgumentError as InvalidArgumentError4 } from "commander";
7771
7937
  import pc15 from "picocolors";
@@ -7773,7 +7939,7 @@ import PocketBase4 from "pocketbase";
7773
7939
 
7774
7940
  // src/lib/vite.ts
7775
7941
  import path31 from "node:path";
7776
- import process22 from "node:process";
7942
+ import process23 from "node:process";
7777
7943
  import { createRequire as createRequire2 } from "node:module";
7778
7944
  import { pathToFileURL as pathToFileURL2 } from "node:url";
7779
7945
  import pc14 from "picocolors";
@@ -7797,13 +7963,13 @@ function resolveProjectVite(cwd) {
7797
7963
  return null;
7798
7964
  }
7799
7965
  }
7800
- async function loadVite(cwd = process22.cwd()) {
7966
+ async function loadVite(cwd = process23.cwd()) {
7801
7967
  const entry = resolveProjectVite(cwd);
7802
7968
  const vite = entry ? await import(pathToFileURL2(entry).href) : await import("vite");
7803
7969
  const error = viteVersionError(vite.version);
7804
7970
  if (error) {
7805
7971
  console.error(`${pc14.redBright("\u2717")} ${error}`);
7806
- process22.exit(1);
7972
+ process23.exit(1);
7807
7973
  }
7808
7974
  return vite;
7809
7975
  }
@@ -7817,18 +7983,18 @@ function parsePort(value) {
7817
7983
  return port;
7818
7984
  }
7819
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) => {
7820
- const cwd = process23.cwd();
7821
- process23.env.VELA_DATA_DIR ??= localDataDir(cwd);
7986
+ const cwd = process24.cwd();
7987
+ process24.env.VELA_DATA_DIR ??= localDataDir(cwd);
7822
7988
  const startTime = performance.now();
7823
7989
  const { createServer, version } = await loadVite(cwd);
7824
7990
  const viteMetadataDir = path32.join(cwd, "node_modules", ".vite");
7825
7991
  const viteMetadataFile = path32.join(viteMetadataDir, "_pocketbase_metadata.json");
7826
7992
  let pbProc;
7827
7993
  const backend3 = hasBackend(cwd);
7828
- const needsStart = backend3 && !process23.env.POCKETBASE_URL;
7994
+ const needsStart = backend3 && !process24.env.POCKETBASE_URL;
7829
7995
  const cleanup = () => {
7830
7996
  if (pbProc?.pid) pbProc.kill();
7831
- if (fs32.existsSync(viteMetadataFile)) fs32.rmSync(viteMetadataFile);
7997
+ if (fs33.existsSync(viteMetadataFile)) fs33.rmSync(viteMetadataFile);
7832
7998
  };
7833
7999
  if (needsStart) {
7834
8000
  const dataDir2 = path32.join(cwd, DATA_DIR);
@@ -7840,15 +8006,15 @@ var dev = new Command71("dev").description("start the development server").optio
7840
8006
  stdio: "pipe"
7841
8007
  });
7842
8008
  pbProc = started.proc;
7843
- process23.env.POCKETBASE_URL = started.url;
7844
- pbProc.stdout?.pipe(process23.stdout);
7845
- pbProc.stderr?.pipe(process23.stderr);
8009
+ process24.env.POCKETBASE_URL = started.url;
8010
+ pbProc.stdout?.pipe(process24.stdout);
8011
+ pbProc.stderr?.pipe(process24.stderr);
7846
8012
  pbProc.on("error", (err) => console.error("PocketBase error:", err));
7847
8013
  pbProc.on("exit", (code) => console.log(`PocketBase exited with code ${code}`));
7848
- process23.on("exit", cleanup);
7849
- process23.on("SIGINT", () => {
8014
+ process24.on("exit", cleanup);
8015
+ process24.on("SIGINT", () => {
7850
8016
  cleanup();
7851
- process23.exit(0);
8017
+ process24.exit(0);
7852
8018
  });
7853
8019
  }
7854
8020
  const serverOptions = {};
@@ -7866,25 +8032,25 @@ var dev = new Command71("dev").description("start the development server").optio
7866
8032
  if (!backend3) return;
7867
8033
  const { address, port: vitePort } = server.httpServer.address();
7868
8034
  const viteHost = address === "::1" ? "localhost" : address;
7869
- await fs32.promises.mkdir(viteMetadataDir, { recursive: true });
7870
- await fs32.promises.writeFile(
8035
+ await fs33.promises.mkdir(viteMetadataDir, { recursive: true });
8036
+ await fs33.promises.writeFile(
7871
8037
  viteMetadataFile,
7872
8038
  JSON.stringify({
7873
- pocketbaseUrl: process23.env.POCKETBASE_URL,
8039
+ pocketbaseUrl: process24.env.POCKETBASE_URL,
7874
8040
  vitePort,
7875
8041
  viteHost
7876
8042
  })
7877
8043
  );
7878
- const pb = new PocketBase4(process23.env.POCKETBASE_URL);
8044
+ const pb = new PocketBase4(process24.env.POCKETBASE_URL);
7879
8045
  await pb.collection("_superusers").authWithPassword(
7880
- process23.env.POCKETBASE_SUPERUSER_EMAIL,
7881
- process23.env.POCKETBASE_SUPERUSER_PASSWORD
8046
+ process24.env.POCKETBASE_SUPERUSER_EMAIL,
8047
+ process24.env.POCKETBASE_SUPERUSER_PASSWORD
7882
8048
  );
7883
8049
  await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
7884
8050
  await startWatchingTypes(cwd, pb);
7885
8051
  });
7886
8052
  await server.listen();
7887
- const hasExistingLogs = process23.stdout.bytesWritten > 0 || process23.stderr.bytesWritten > 0;
8053
+ const hasExistingLogs = process24.stdout.bytesWritten > 0 || process24.stderr.bytesWritten > 0;
7888
8054
  const startupDurationString = pc15.dim(
7889
8055
  `ready in ${pc15.reset(pc15.bold(Math.ceil(performance.now() - startTime)))} ms`
7890
8056
  );
@@ -7908,9 +8074,9 @@ async function startWatchingTypes(cwd, pb) {
7908
8074
  void (async () => {
7909
8075
  for (; ; ) {
7910
8076
  try {
7911
- await fs32.promises.mkdir(pocketbaseDir, { recursive: true });
7912
- for await (const event of fs32.promises.watch(pocketbaseDir)) {
7913
- 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)) {
7914
8080
  setTimeout(regenerate, 100);
7915
8081
  }
7916
8082
  }
@@ -7922,9 +8088,9 @@ async function startWatchingTypes(cwd, pb) {
7922
8088
  }
7923
8089
 
7924
8090
  // src/commands/build.ts
7925
- import fs33 from "node:fs";
8091
+ import fs34 from "node:fs";
7926
8092
  import path33 from "node:path";
7927
- import process25 from "node:process";
8093
+ import process26 from "node:process";
7928
8094
  import { Command as Command72 } from "commander";
7929
8095
  import * as p40 from "@clack/prompts";
7930
8096
  import pc16 from "picocolors";
@@ -7939,10 +8105,10 @@ function applyBuildEnv(cwd, env2 = process.env) {
7939
8105
  }
7940
8106
 
7941
8107
  // src/lib/origin.ts
7942
- import process24 from "node:process";
8108
+ import process25 from "node:process";
7943
8109
  function resolveOrigin(workspaceRootDir, envTag, config = {}) {
7944
8110
  return normalizeOrigin(
7945
- process24.env.VELA_ORIGIN ?? readBinding(workspaceRootDir, envTag)?.domain ?? config.deploy?.domain
8111
+ process25.env.VELA_ORIGIN ?? readBinding(workspaceRootDir, envTag)?.domain ?? config.deploy?.domain
7946
8112
  );
7947
8113
  }
7948
8114
  function normalizeOrigin(value) {
@@ -7963,12 +8129,12 @@ function splitHosts(value) {
7963
8129
  // src/commands/build.ts
7964
8130
  var PRERENDERED_DIR = path33.join(".svelte-kit", "output", "prerendered");
7965
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) => {
7966
- const cwd = process25.cwd();
8132
+ const cwd = process26.cwd();
7967
8133
  applyBuildEnv(cwd);
7968
8134
  const origin = await originForBuild(cwd, options.target);
7969
- if (origin) process25.env.VELA_ORIGIN = origin;
8135
+ if (origin) process26.env.VELA_ORIGIN = origin;
7970
8136
  let pbProc;
7971
- const needsStart = hasBackend(cwd) && !process25.env.POCKETBASE_URL;
8137
+ const needsStart = hasBackend(cwd) && !process26.env.POCKETBASE_URL;
7972
8138
  const cleanup = () => {
7973
8139
  if (pbProc?.pid) pbProc.kill();
7974
8140
  };
@@ -7982,11 +8148,11 @@ var build = new Command72("build").description("build the app").configureHelp(he
7982
8148
  dev: true
7983
8149
  });
7984
8150
  pbProc = started.proc;
7985
- process25.env.POCKETBASE_URL = started.url;
7986
- process25.on("exit", cleanup);
7987
- process25.on("SIGINT", () => {
8151
+ process26.env.POCKETBASE_URL = started.url;
8152
+ process26.on("exit", cleanup);
8153
+ process26.on("SIGINT", () => {
7988
8154
  cleanup();
7989
- process25.exit(0);
8155
+ process26.exit(0);
7990
8156
  });
7991
8157
  }
7992
8158
  try {
@@ -8018,7 +8184,7 @@ async function originForBuild(cwd, target) {
8018
8184
  }
8019
8185
  function warnIfPrerendered(cwd) {
8020
8186
  const dir = path33.join(cwd, PRERENDERED_DIR);
8021
- if (!fs33.existsSync(dir) || fs33.readdirSync(dir).length === 0) return;
8187
+ if (!fs34.existsSync(dir) || fs34.readdirSync(dir).length === 0) return;
8022
8188
  p40.log.warn(
8023
8189
  `Prerendered pages were built with no domain configured, so their canonical
8024
8190
  links point at SvelteKit's placeholder host rather than at this site.
@@ -8029,16 +8195,16 @@ Set one with ${pc16.cyan("vela deploy --domain example.com")}, or pass ${pc16.cy
8029
8195
 
8030
8196
  // src/commands/preview.ts
8031
8197
  import path34 from "node:path";
8032
- import process26 from "node:process";
8198
+ import process27 from "node:process";
8033
8199
  import { Command as Command73 } from "commander";
8034
8200
  import { x as x4 } from "tinyexec";
8035
8201
  import { detect as detect6 } from "package-manager-detector";
8036
8202
  import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
8037
8203
  var preview = new Command73("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
8038
- const cwd = process26.cwd();
8039
- process26.env.VELA_DATA_DIR ??= localDataDir(cwd);
8204
+ const cwd = process27.cwd();
8205
+ process27.env.VELA_DATA_DIR ??= localDataDir(cwd);
8040
8206
  let pbProc;
8041
- const needsStart = hasBackend(cwd) && !process26.env.POCKETBASE_URL;
8207
+ const needsStart = hasBackend(cwd) && !process27.env.POCKETBASE_URL;
8042
8208
  const cleanup = () => {
8043
8209
  if (pbProc?.pid) pbProc.kill();
8044
8210
  };
@@ -8051,11 +8217,11 @@ var preview = new Command73("preview").description("preview the built app").conf
8051
8217
  dev: true
8052
8218
  });
8053
8219
  pbProc = started.proc;
8054
- process26.env.POCKETBASE_URL = started.url;
8055
- process26.on("exit", cleanup);
8056
- process26.on("SIGINT", () => {
8220
+ process27.env.POCKETBASE_URL = started.url;
8221
+ process27.on("exit", cleanup);
8222
+ process27.on("SIGINT", () => {
8057
8223
  cleanup();
8058
- process26.exit(0);
8224
+ process27.exit(0);
8059
8225
  });
8060
8226
  }
8061
8227
  try {
@@ -8141,23 +8307,23 @@ var provision = addSshOptions(
8141
8307
 
8142
8308
  // src/commands/deploy.ts
8143
8309
  import path37 from "node:path";
8144
- import fs35 from "node:fs";
8310
+ import fs36 from "node:fs";
8145
8311
  import { Command as Command76, Option as Option2 } from "commander";
8146
8312
  import * as p42 from "@clack/prompts";
8147
8313
  import pc18 from "picocolors";
8148
8314
  import * as v8 from "valibot";
8149
8315
 
8150
8316
  // src/lib/pocketbase-settings.ts
8151
- import fs34 from "node:fs";
8317
+ import fs35 from "node:fs";
8152
8318
  import path36 from "node:path";
8153
- import process27 from "node:process";
8319
+ import process28 from "node:process";
8154
8320
  import PocketBase5 from "pocketbase";
8155
8321
  var COPIED_KEYS = ["appName", "senderName", "senderAddress"];
8156
8322
  async function readLocalMeta(cwd) {
8157
8323
  const dataDir2 = path36.join(cwd, DATA_DIR);
8158
- if (!fs34.existsSync(dataDir2)) return null;
8159
- const email3 = process27.env.POCKETBASE_SUPERUSER_EMAIL;
8160
- 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;
8161
8327
  if (!email3 || !password11) return null;
8162
8328
  let proc;
8163
8329
  try {
@@ -8210,6 +8376,7 @@ var OptionsSchema3 = v8.object({
8210
8376
  ...SSH_OPTION_SCHEMA,
8211
8377
  env: v8.optional(v8.string()),
8212
8378
  project: v8.optional(v8.string()),
8379
+ ...LOCK_WAIT_SCHEMA,
8213
8380
  remoteDb: v8.optional(v8.boolean()),
8214
8381
  domain: v8.optional(v8.string()),
8215
8382
  healthPath: v8.optional(v8.string()),
@@ -8217,9 +8384,11 @@ var OptionsSchema3 = v8.object({
8217
8384
  pbVersion: v8.optional(v8.string()),
8218
8385
  build: v8.optional(v8.boolean())
8219
8386
  });
8220
- var deploy = addTargetOptions(
8221
- new Command76("deploy").description("deploy the app").configureHelp(helpConfig),
8222
- "production"
8387
+ var deploy = addLockWaitOption(
8388
+ addTargetOptions(
8389
+ new Command76("deploy").description("deploy the app").configureHelp(helpConfig),
8390
+ "production"
8391
+ )
8223
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(
8224
8393
  new Option2(
8225
8394
  "--remote-db",
@@ -8233,7 +8402,6 @@ var deploy = addTargetOptions(
8233
8402
  (raw) => runCommand(async () => {
8234
8403
  const options = parseOptions(OptionsSchema3, raw);
8235
8404
  const backend3 = hasBackend();
8236
- const release = releaseId();
8237
8405
  p42.intro(pc18.bgCyan(pc18.black(" vela deploy ")));
8238
8406
  await withTarget(
8239
8407
  raw,
@@ -8244,8 +8412,9 @@ var deploy = addTargetOptions(
8244
8412
  `${pc18.cyan(ctx.appName)} ${pc18.dim("\u2192")} ${pc18.cyan(ctx.targetName)} ${pc18.dim(`(${ctx.server})`)}`
8245
8413
  );
8246
8414
  const [existing] = await readInstanceStates(session, instance);
8247
- const isPreview = ctx.target.kind === "preview";
8248
- 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) ?? "";
8249
8418
  const askedForRemoteDb = options.remoteDb ?? config.deploy?.buildAgainstRemote;
8250
8419
  const remoteDb = askedForRemoteDb ?? Boolean(existing?.pbPort);
8251
8420
  const sha = await gitSha(workspaceRootDir);
@@ -8267,17 +8436,17 @@ var deploy = addTargetOptions(
8267
8436
  const managed = managedHosts.join(",");
8268
8437
  const primaryHost = directHosts[0] ?? managedHosts[0] ?? "";
8269
8438
  const primaryUrl = started?.environment.primaryUrl || normalizeOrigin(primaryHost) || "";
8270
- if (isPreview && !primaryHost) {
8271
- throw new Error(
8272
- `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.
8273
8444
 
8274
8445
  Previews get a free velastack.app hostname from velastack.dev: ${pc18.cyan("vela link")} the
8275
8446
  project and ${pc18.cyan("vela login")} (or set ${pc18.cyan("VELA_API_KEY")}). Or pass ${pc18.cyan("--domain <host>")}
8276
8447
  with DNS of your own pointing at ${ctx.server}.`
8277
- );
8278
- }
8279
- let result = null;
8280
- try {
8448
+ );
8449
+ }
8281
8450
  if (options.build !== false) {
8282
8451
  const origin = normalizeOrigin(process.env.VELA_ORIGIN ?? primaryUrl);
8283
8452
  let buildEnv = origin ? { VELA_ORIGIN: origin } : {};
@@ -8335,7 +8504,8 @@ ${pc18.dim(String(err))}`
8335
8504
  "--pb-version",
8336
8505
  options.pbVersion ?? config.deploy?.pocketbaseVersion ?? pocketbaseVersion(),
8337
8506
  "--git-sha",
8338
- sha
8507
+ sha,
8508
+ ...lockWaitArgs(options.lockWait)
8339
8509
  ],
8340
8510
  stream: true
8341
8511
  });
@@ -8349,7 +8519,7 @@ ${pc18.dim(String(err))}`
8349
8519
  writeBinding(
8350
8520
  workspaceRootDir,
8351
8521
  bindingKey(ctx.target),
8352
- isPreview ? { server: ctx.server } : { server: ctx.server, domain: domain || void 0 }
8522
+ isPreview2 ? { server: ctx.server } : { server: ctx.server, domain: domain || void 0 }
8353
8523
  );
8354
8524
  if (!existing) await reportEmptyEnvironment(session, instance, workspaceRootDir);
8355
8525
  const url = result?.url ?? "";
@@ -8500,7 +8670,7 @@ async function superuserAuthenticates(port, identity, password11) {
8500
8670
  }
8501
8671
  async function uploadRelease(session, instance, release, entries) {
8502
8672
  const dir = remotePaths.release(instance, release);
8503
- await session.script(`mkdir -p "$1"`, { args: [dir] });
8673
+ await session.script(`mkdir -p "$(dirname "$1")" && mkdir "$1"`, { args: [dir] });
8504
8674
  const byTarget = /* @__PURE__ */ new Map();
8505
8675
  for (const entry of entries) {
8506
8676
  const target = entry.remoteDir ? `${dir}/${entry.remoteDir}` : dir;
@@ -8514,7 +8684,7 @@ async function uploadRelease(session, instance, release, entries) {
8514
8684
  }
8515
8685
  function isDirectory(target) {
8516
8686
  try {
8517
- return fs35.statSync(target).isDirectory();
8687
+ return fs36.statSync(target).isDirectory();
8518
8688
  } catch {
8519
8689
  return false;
8520
8690
  }
@@ -8522,7 +8692,7 @@ function isDirectory(target) {
8522
8692
  async function reportEmptyEnvironment(session, instance, workspaceRootDir) {
8523
8693
  const remote = await readRemoteEnv(session, instance);
8524
8694
  if (Object.keys(remote).length > 0) return;
8525
- if (!fs35.existsSync(path37.join(workspaceRootDir, ".env"))) return;
8695
+ if (!fs36.existsSync(path37.join(workspaceRootDir, ".env"))) return;
8526
8696
  p42.log.warn(
8527
8697
  `This app has no production environment variables yet.
8528
8698
 
@@ -8530,10 +8700,18 @@ Local ${pc18.cyan(".env")} values are not uploaded by a deploy. Set them with
8530
8700
  ${pc18.cyan("vela env set KEY")}, or copy a file across with ${pc18.cyan("vela env import .env.production")}.`
8531
8701
  );
8532
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
+ }
8533
8711
 
8534
8712
  // src/commands/link.ts
8535
8713
  import path38 from "node:path";
8536
- import process28 from "node:process";
8714
+ import process29 from "node:process";
8537
8715
  import { Command as Command77 } from "commander";
8538
8716
  import * as p43 from "@clack/prompts";
8539
8717
  var CREATE_NEW = "__new__";
@@ -8584,7 +8762,7 @@ async function pickExistingProject(projects) {
8584
8762
  });
8585
8763
  if (p43.isCancel(choice)) {
8586
8764
  p43.cancel("Operation cancelled.");
8587
- process28.exit(0);
8765
+ process29.exit(0);
8588
8766
  }
8589
8767
  return choice;
8590
8768
  }
@@ -8599,7 +8777,7 @@ async function pickTeam(teams3) {
8599
8777
  });
8600
8778
  if (p43.isCancel(choice)) {
8601
8779
  p43.cancel("Operation cancelled.");
8602
- process28.exit(0);
8780
+ process29.exit(0);
8603
8781
  }
8604
8782
  return teams3.find((team) => team.id === choice);
8605
8783
  }
@@ -8614,7 +8792,7 @@ async function promptProjectName(workspaceRootDir) {
8614
8792
  });
8615
8793
  if (p43.isCancel(value)) {
8616
8794
  p43.cancel("Operation cancelled.");
8617
- process28.exit(0);
8795
+ process29.exit(0);
8618
8796
  }
8619
8797
  return value.trim();
8620
8798
  }
@@ -8669,7 +8847,7 @@ function report(keys, where) {
8669
8847
  }
8670
8848
 
8671
8849
  // src/commands/env/set.ts
8672
- import process29 from "node:process";
8850
+ import process30 from "node:process";
8673
8851
  import { Command as Command79 } from "commander";
8674
8852
  import * as p45 from "@clack/prompts";
8675
8853
  import pc20 from "picocolors";
@@ -8711,7 +8889,7 @@ async function promptValue(key) {
8711
8889
  });
8712
8890
  if (p45.isCancel(value)) {
8713
8891
  p45.cancel("Operation cancelled.");
8714
- process29.exit(0);
8892
+ process30.exit(0);
8715
8893
  }
8716
8894
  return value;
8717
8895
  }
@@ -8756,9 +8934,9 @@ var envUnset = addTargetOptions(
8756
8934
  );
8757
8935
 
8758
8936
  // src/commands/env/import.ts
8759
- import fs36 from "node:fs";
8937
+ import fs37 from "node:fs";
8760
8938
  import path39 from "node:path";
8761
- import process30 from "node:process";
8939
+ import process31 from "node:process";
8762
8940
  import { Command as Command81 } from "commander";
8763
8941
  import * as p47 from "@clack/prompts";
8764
8942
  import pc22 from "picocolors";
@@ -8803,10 +8981,10 @@ var envImport = addTargetOptions(
8803
8981
  )
8804
8982
  );
8805
8983
  function resolve(file) {
8806
- return path39.resolve(process30.cwd(), file);
8984
+ return path39.resolve(process31.cwd(), file);
8807
8985
  }
8808
8986
  function read(resolved, shown) {
8809
- if (!fs36.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
8987
+ if (!fs37.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
8810
8988
  const incoming = readLocalEnvFile(resolved);
8811
8989
  if (Object.keys(incoming).length === 0) p47.log.info(`${shown} has no variables to import.`);
8812
8990
  return incoming;
@@ -8855,10 +9033,10 @@ function report2(states, json) {
8855
9033
  return;
8856
9034
  }
8857
9035
  for (const state of states) {
8858
- p48.log.info(describe(state));
9036
+ p48.log.info(describe2(state));
8859
9037
  }
8860
9038
  }
8861
- function describe(state) {
9039
+ function describe2(state) {
8862
9040
  const health = (value) => value === "active" ? pc23.green(value) : pc23.red(value || "inactive");
8863
9041
  const rows = [
8864
9042
  ["Instance", state.instance],
@@ -8882,9 +9060,11 @@ function describe(state) {
8882
9060
  import { Command as Command84 } from "commander";
8883
9061
  import * as p49 from "@clack/prompts";
8884
9062
  import pc24 from "picocolors";
8885
- var rollback = addTargetOptions(
8886
- new Command84("rollback").description("put the previous release back").configureHelp(helpConfig),
8887
- "production"
9063
+ var rollback = addLockWaitOption(
9064
+ addTargetOptions(
9065
+ new Command84("rollback").description("put the previous release back").configureHelp(helpConfig),
9066
+ "production"
9067
+ )
8888
9068
  ).option("--to <release>", "roll back to a specific release instead of the previous one").action(
8889
9069
  (raw) => runCommand(async () => {
8890
9070
  const options = raw;
@@ -8899,7 +9079,11 @@ var rollback = addTargetOptions(
8899
9079
  ctx.session,
8900
9080
  "rollback.sh",
8901
9081
  {
8902
- args: [ctx.instance, ...options.to ? ["--to", options.to] : []],
9082
+ args: [
9083
+ ctx.instance,
9084
+ ...options.to ? ["--to", options.to] : [],
9085
+ ...lockWaitArgs(options.lockWait)
9086
+ ],
8903
9087
  stream: true
8904
9088
  }
8905
9089
  );
@@ -8956,7 +9140,7 @@ var logs = addTargetOptions(
8956
9140
  import { Command as Command87 } from "commander";
8957
9141
 
8958
9142
  // src/commands/admin/create.ts
8959
- import process31 from "node:process";
9143
+ import process32 from "node:process";
8960
9144
  import { Command as Command86 } from "commander";
8961
9145
  import * as p50 from "@clack/prompts";
8962
9146
  import pc25 from "picocolors";
@@ -9023,7 +9207,7 @@ async function upsertSuperuser(pb, email3, password11) {
9023
9207
  });
9024
9208
  if (p50.isCancel(confirmed) || !confirmed) {
9025
9209
  p50.cancel("Operation cancelled.");
9026
- process31.exit(0);
9210
+ process32.exit(0);
9027
9211
  }
9028
9212
  await pb.collection("_superusers").update(existing, { password: password11, passwordConfirm: password11 });
9029
9213
  p50.log.success(`Password updated for ${pc25.cyan(email3)}, you can sign in now`);
@@ -9047,7 +9231,7 @@ async function promptEmail() {
9047
9231
  });
9048
9232
  if (p50.isCancel(value)) {
9049
9233
  p50.cancel("Operation cancelled.");
9050
- process31.exit(0);
9234
+ process32.exit(0);
9051
9235
  }
9052
9236
  return value.trim();
9053
9237
  }
@@ -9058,7 +9242,7 @@ async function promptPassword2() {
9058
9242
  });
9059
9243
  if (p50.isCancel(value)) {
9060
9244
  p50.cancel("Operation cancelled.");
9061
- process31.exit(0);
9245
+ process32.exit(0);
9062
9246
  }
9063
9247
  const again = await p50.password({
9064
9248
  message: "Password again",
@@ -9066,7 +9250,7 @@ async function promptPassword2() {
9066
9250
  });
9067
9251
  if (p50.isCancel(again)) {
9068
9252
  p50.cancel("Operation cancelled.");
9069
- process31.exit(0);
9253
+ process32.exit(0);
9070
9254
  }
9071
9255
  return value;
9072
9256
  }
@@ -9078,7 +9262,7 @@ var admin = new Command87("admin").description("manage admin panel logins").conf
9078
9262
  import { Command as Command93 } from "commander";
9079
9263
 
9080
9264
  // src/commands/backup/create.ts
9081
- import fs37 from "node:fs";
9265
+ import fs38 from "node:fs";
9082
9266
  import path40 from "node:path";
9083
9267
  import { Command as Command88 } from "commander";
9084
9268
  import * as p51 from "@clack/prompts";
@@ -9234,10 +9418,10 @@ Your bucket's own versioning is what protects the uploaded files.`
9234
9418
  );
9235
9419
  async function download(ctx, key, outputDir) {
9236
9420
  const dir = path40.resolve(ctx.workspaceRootDir, outputDir);
9237
- fs37.mkdirSync(dir, { recursive: true });
9421
+ fs38.mkdirSync(dir, { recursive: true });
9238
9422
  const destination = path40.join(dir, key);
9239
9423
  if (!ctx.session) {
9240
- fs37.copyFileSync(path40.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9424
+ fs38.copyFileSync(path40.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9241
9425
  return path40.relative(ctx.workspaceRootDir, destination);
9242
9426
  }
9243
9427
  const spinner7 = p51.spinner();
@@ -9283,7 +9467,7 @@ Take one with ${pc27.cyan("vela backup create")}.`
9283
9467
  );
9284
9468
 
9285
9469
  // src/commands/backup/download.ts
9286
- import fs38 from "node:fs";
9470
+ import fs39 from "node:fs";
9287
9471
  import path41 from "node:path";
9288
9472
  import { Command as Command90 } from "commander";
9289
9473
  import * as p53 from "@clack/prompts";
@@ -9312,10 +9496,10 @@ Fetch it from the bucket directly \u2014 vela does not hold its credentials.`
9312
9496
  );
9313
9497
  }
9314
9498
  const dir = path41.resolve(ctx.workspaceRootDir, options.output);
9315
- fs38.mkdirSync(dir, { recursive: true });
9499
+ fs39.mkdirSync(dir, { recursive: true });
9316
9500
  const destination = path41.join(dir, key);
9317
9501
  if (!ctx.session) {
9318
- fs38.copyFileSync(path41.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9502
+ fs39.copyFileSync(path41.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9319
9503
  } else {
9320
9504
  const spinner7 = p53.spinner();
9321
9505
  spinner7.start(`Downloading ${key} (${formatBytes(found.size)})`);
@@ -9336,7 +9520,7 @@ Fetch it from the bucket directly \u2014 vela does not hold its credentials.`
9336
9520
  );
9337
9521
 
9338
9522
  // src/commands/backup/delete.ts
9339
- import process32 from "node:process";
9523
+ import process33 from "node:process";
9340
9524
  import { Command as Command91 } from "commander";
9341
9525
  import * as p54 from "@clack/prompts";
9342
9526
  import pc29 from "picocolors";
@@ -9362,7 +9546,7 @@ Run ${pc29.cyan("vela backup list")} to see what it does have.`
9362
9546
  });
9363
9547
  if (p54.isCancel(confirmed) || !confirmed) {
9364
9548
  p54.cancel("Operation cancelled.");
9365
- process32.exit(0);
9549
+ process33.exit(0);
9366
9550
  }
9367
9551
  }
9368
9552
  await ctx.pb.backups.delete(key);
@@ -9424,15 +9608,17 @@ Set one with ${pc30.cyan('vela backup schedule "0 3 * * *"')}.`
9424
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);
9425
9609
 
9426
9610
  // src/commands/restore.ts
9427
- import fs39 from "node:fs";
9611
+ import fs40 from "node:fs";
9428
9612
  import path42 from "node:path";
9429
- import process33 from "node:process";
9613
+ import process34 from "node:process";
9430
9614
  import { Command as Command94 } from "commander";
9431
9615
  import * as p56 from "@clack/prompts";
9432
9616
  import pc31 from "picocolors";
9433
- var restore = addTargetOptions(
9434
- 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),
9435
- "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
+ )
9436
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(
9437
9623
  (source, raw) => runCommand(async () => {
9438
9624
  const options = raw;
@@ -9446,10 +9632,10 @@ var restore = addTargetOptions(
9446
9632
  `${ctx.targetName} was deployed without a database, so there is nothing to restore.`
9447
9633
  );
9448
9634
  }
9449
- const local = source && fs39.existsSync(source) ? source : void 0;
9635
+ const local = source && fs40.existsSync(source) ? source : void 0;
9450
9636
  const key = local ? void 0 : await resolveKey(ctx, source);
9451
9637
  if (!options.yes) {
9452
- await confirm12(ctx.appName, ctx.targetName, ctx.envTag, local ?? key);
9638
+ await confirm13(ctx.appName, ctx.targetName, ctx.envTag, local ?? key);
9453
9639
  }
9454
9640
  const archive = local ? await stage(ctx, local) : remotePaths.backup(ctx.instance, key);
9455
9641
  const result = await runServerScript(ctx.session, "restore.sh", {
@@ -9462,7 +9648,8 @@ var restore = addTargetOptions(
9462
9648
  ...options.migrate ? [] : ["--no-migrate"],
9463
9649
  // A copy this machine pushed up is scratch; one already in the
9464
9650
  // instance's own backups directory is not ours to delete.
9465
- ...local ? ["--cleanup-archive"] : []
9651
+ ...local ? ["--cleanup-archive"] : [],
9652
+ ...lockWaitArgs(options.lockWait)
9466
9653
  ],
9467
9654
  stream: true
9468
9655
  });
@@ -9528,7 +9715,7 @@ Take one with ${pc31.cyan("vela backup create")}, or pass the path to an archive
9528
9715
  });
9529
9716
  if (p56.isCancel(chosen)) {
9530
9717
  p56.cancel("Operation cancelled.");
9531
- process33.exit(0);
9718
+ process34.exit(0);
9532
9719
  }
9533
9720
  return chosen;
9534
9721
  }
@@ -9547,7 +9734,7 @@ async function stage(ctx, file) {
9547
9734
  spinner7.stop(`Uploaded ${path42.basename(file)}`);
9548
9735
  return remote;
9549
9736
  }
9550
- async function confirm12(appName, targetName, envTag, from) {
9737
+ async function confirm13(appName, targetName, envTag, from) {
9551
9738
  const what = `${pc31.cyan(`${appName} (${targetName})`)} from ${pc31.cyan(path42.basename(from))}`;
9552
9739
  if (isProd(envTag)) {
9553
9740
  const answer = await p56.text({
@@ -9556,7 +9743,7 @@ async function confirm12(appName, targetName, envTag, from) {
9556
9743
  });
9557
9744
  if (p56.isCancel(answer)) {
9558
9745
  p56.cancel("Operation cancelled.");
9559
- process33.exit(0);
9746
+ process34.exit(0);
9560
9747
  }
9561
9748
  return;
9562
9749
  }
@@ -9566,7 +9753,7 @@ async function confirm12(appName, targetName, envTag, from) {
9566
9753
  });
9567
9754
  if (p56.isCancel(ok) || !ok) {
9568
9755
  p56.cancel("Operation cancelled.");
9569
- process33.exit(0);
9756
+ process34.exit(0);
9570
9757
  }
9571
9758
  }
9572
9759
 
@@ -9660,20 +9847,20 @@ Release and domain are shown from what this project recorded.`
9660
9847
 
9661
9848
  // src/commands/test.ts
9662
9849
  import path43 from "node:path";
9663
- import process34 from "node:process";
9850
+ import process35 from "node:process";
9664
9851
  import { Command as Command96 } from "commander";
9665
9852
  import PocketBase6 from "pocketbase";
9666
9853
  import pc33 from "picocolors";
9667
9854
  import { x as x5 } from "tinyexec";
9668
9855
  import { detect as detect7 } from "package-manager-detector";
9669
9856
  import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
9670
- import fs40 from "node:fs";
9857
+ import fs41 from "node:fs";
9671
9858
  var testServer = new Command96("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
9672
- const cwd = process34.cwd();
9859
+ const cwd = process35.cwd();
9673
9860
  const email3 = `test-${Math.random().toString(36).slice(2)}@example.com`;
9674
9861
  const password11 = "password";
9675
9862
  const testDataDir = path43.join(cwd, "test-data");
9676
- fs40.rmSync(testDataDir, { recursive: true, force: true });
9863
+ fs41.rmSync(testDataDir, { recursive: true, force: true });
9677
9864
  const { stop, url } = await launchPocketbase(cwd, {
9678
9865
  dir: testDataDir,
9679
9866
  migrationsDir: path43.join(cwd, MIGRATIONS_DIR),
@@ -9684,11 +9871,11 @@ var testServer = new Command96("test:server").description("run server tests").al
9684
9871
  email: email3,
9685
9872
  password: password11
9686
9873
  });
9687
- process34.env.POCKETBASE_URL = url;
9688
- process34.env.POCKETBASE_SUPERUSER_EMAIL = email3;
9689
- process34.env.POCKETBASE_SUPERUSER_PASSWORD = password11;
9690
- process34.env.VELA_DATA_DIR = testDataDir;
9691
- 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";
9692
9879
  console.log(`${pc33.greenBright("\u2713")} Created test database`);
9693
9880
  let vite;
9694
9881
  let cleanedUp = false;
@@ -9696,7 +9883,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9696
9883
  if (cleanedUp) return;
9697
9884
  cleanedUp = true;
9698
9885
  stop();
9699
- fs40.rmSync(testDataDir, { recursive: true, force: true });
9886
+ fs41.rmSync(testDataDir, { recursive: true, force: true });
9700
9887
  };
9701
9888
  const cleanup = async () => {
9702
9889
  if (cleanedUp) return;
@@ -9707,12 +9894,12 @@ var testServer = new Command96("test:server").description("run server tests").al
9707
9894
  const fail = async (message) => {
9708
9895
  console.error(`${pc33.redBright("\u2717")} ${message}`);
9709
9896
  await cleanup();
9710
- process34.exit(1);
9897
+ process35.exit(1);
9711
9898
  };
9712
- process34.on("exit", cleanupSync);
9713
- process34.on("SIGINT", () => {
9899
+ process35.on("exit", cleanupSync);
9900
+ process35.on("SIGINT", () => {
9714
9901
  cleanupSync();
9715
- process34.exit(130);
9902
+ process35.exit(130);
9716
9903
  });
9717
9904
  const pb = new PocketBase6(url);
9718
9905
  try {
@@ -9740,7 +9927,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9740
9927
  });
9741
9928
  const vitePort = await findFreePort();
9742
9929
  await vite.listen(vitePort);
9743
- process34.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
9930
+ process35.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
9744
9931
  console.log(`${pc33.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
9745
9932
  console.log(`${pc33.greenBright("\u2713")} Started PocketBase: ${url}`);
9746
9933
  const extraArgs = (cmd.parent?.args ?? []).slice(1);
@@ -9759,12 +9946,12 @@ var testServer = new Command96("test:server").description("run server tests").al
9759
9946
  const resolvedArgs = resolved.args.slice();
9760
9947
  if (pm === "npm") resolvedArgs.unshift("--yes");
9761
9948
  const result = await x5(resolved.command, resolvedArgs, {
9762
- nodeOptions: { cwd, stdio: "inherit", env: { ...process34.env, CI: "1" } }
9949
+ nodeOptions: { cwd, stdio: "inherit", env: { ...process35.env, CI: "1" } }
9763
9950
  });
9764
- process34.exitCode = result.exitCode ?? 1;
9951
+ process35.exitCode = result.exitCode ?? 1;
9765
9952
  } catch (e) {
9766
9953
  console.error(`${pc33.redBright("\u2717")} ${e.message}`);
9767
- process34.exitCode = 1;
9954
+ process35.exitCode = 1;
9768
9955
  } finally {
9769
9956
  await cleanup();
9770
9957
  }
@@ -9795,7 +9982,7 @@ function stubPagesPlugin() {
9795
9982
  }
9796
9983
 
9797
9984
  // src/commands/routes.ts
9798
- import fs41 from "node:fs";
9985
+ import fs42 from "node:fs";
9799
9986
  import path44 from "node:path";
9800
9987
  import { Command as Command97 } from "commander";
9801
9988
  var HTTP_METHODS = /* @__PURE__ */ new Set([
@@ -9816,7 +10003,7 @@ var routes = new Command97("routes").description("list routes").configureHelp(he
9816
10003
  printTable(found);
9817
10004
  });
9818
10005
  function walk(root, dir) {
9819
- const entries = fs41.readdirSync(dir, { withFileTypes: true });
10006
+ const entries = fs42.readdirSync(dir, { withFileTypes: true });
9820
10007
  const routes2 = [];
9821
10008
  const hasLeaf = entries.some((e) => e.isFile() && isRouteFile(e.name));
9822
10009
  if (hasLeaf) {
@@ -9842,7 +10029,7 @@ function isRouteFile(name) {
9842
10029
  }
9843
10030
  function extractMethods(file) {
9844
10031
  try {
9845
- const content = fs41.readFileSync(file, "utf8");
10032
+ const content = fs42.readFileSync(file, "utf8");
9846
10033
  const methods = [];
9847
10034
  const exportRegex = /export\s+(?:const|async\s+function|function)\s+(\w+)/g;
9848
10035
  let match;
@@ -9883,13 +10070,13 @@ function printTable(routes2) {
9883
10070
  }
9884
10071
 
9885
10072
  // src/commands/i18n.ts
9886
- import process35 from "node:process";
10073
+ import process36 from "node:process";
9887
10074
  import { Command as Command98 } from "commander";
9888
10075
  import { x as x6 } from "tinyexec";
9889
10076
  import { detect as detect8 } from "package-manager-detector";
9890
10077
  import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
9891
10078
  async function runWuchale(extraArgs) {
9892
- const cwd = process35.cwd();
10079
+ const cwd = process36.cwd();
9893
10080
  const pm = (await detect8({ cwd }))?.name ?? "npm";
9894
10081
  const resolved = resolveCommand8(pm, "execute", ["wuchale", ...extraArgs]);
9895
10082
  const args = resolved.args.slice();
@@ -9926,7 +10113,7 @@ import pc35 from "picocolors";
9926
10113
  // src/lib/cms-backend.ts
9927
10114
  import { createRequire as createRequire3 } from "node:module";
9928
10115
  import path45 from "node:path";
9929
- import process36 from "node:process";
10116
+ import process37 from "node:process";
9930
10117
  import { pathToFileURL as pathToFileURL3 } from "node:url";
9931
10118
  import pc34 from "picocolors";
9932
10119
  var DEFAULT_PROJECT = "default";
@@ -9943,7 +10130,7 @@ Run ${pc34.cyan("vela enable cms")} first.`
9943
10130
  }
9944
10131
  return await import(pathToFileURL3(entry).href);
9945
10132
  }
9946
- async function withCmsBackend(fn, cwd = process36.cwd()) {
10133
+ async function withCmsBackend(fn, cwd = process37.cwd()) {
9947
10134
  const root = findWorkspaceRoot(cwd);
9948
10135
  if (!root) {
9949
10136
  throw new Error("Could not find workspace root (no package.json found)");
@@ -10062,7 +10249,7 @@ var SELF_CREDENTIALED_COMMANDS = /* @__PURE__ */ new Set(["test:server"]);
10062
10249
  var program = new Command104().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
10063
10250
  program.hook("preAction", (_thisCommand, actionCommand) => {
10064
10251
  if (isStub(actionCommand)) return;
10065
- const envRoot = findWorkspaceRoot() ?? process37.cwd();
10252
+ const envRoot = findWorkspaceRoot() ?? process38.cwd();
10066
10253
  dotenv2.config({ path: nodePath.join(envRoot, ".env"), quiet: true });
10067
10254
  const path46 = getCommandPath(actionCommand);
10068
10255
  if (NO_BACKEND_COMMMANDS.has(path46)) return;
@@ -10079,10 +10266,10 @@ To add a backend to this project, run ${pc38.cyan("vela bless")}.`
10079
10266
  );
10080
10267
  p61.log.message();
10081
10268
  p61.cancel("Operation failed.");
10082
- process37.exit(1);
10269
+ process38.exit(1);
10083
10270
  }
10084
10271
  if (SELF_CREDENTIALED_COMMANDS.has(path46)) return;
10085
- if (!process37.env.POCKETBASE_SUPERUSER_EMAIL || !process37.env.POCKETBASE_SUPERUSER_PASSWORD) {
10272
+ if (!process38.env.POCKETBASE_SUPERUSER_EMAIL || !process38.env.POCKETBASE_SUPERUSER_PASSWORD) {
10086
10273
  p61.log.error(
10087
10274
  `PocketBase superuser credentials are required.
10088
10275
 
@@ -10093,7 +10280,7 @@ To set up an existing project, run ${pc38.cyan("vela bless")}.`
10093
10280
  );
10094
10281
  p61.log.message();
10095
10282
  p61.cancel("Operation failed.");
10096
- process37.exit(1);
10283
+ process38.exit(1);
10097
10284
  }
10098
10285
  });
10099
10286
  function getCommandPath(cmd) {
@@ -10147,5 +10334,5 @@ for (const command of [
10147
10334
  }
10148
10335
 
10149
10336
  // src/bin.ts
10150
- program.parse(normalizeArgv(process38.argv.slice(2)), { from: "user" });
10337
+ program.parse(normalizeArgv(process39.argv.slice(2)), { from: "user" });
10151
10338
  //# sourceMappingURL=bin.js.map