vela 0.11.3 → 0.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin.ts
4
- import process38 from "node:process";
4
+ import process39 from "node:process";
5
5
 
6
6
  // src/lib/argv.ts
7
7
  function normalizeArgv(argv) {
@@ -11,7 +11,7 @@ function normalizeArgv(argv) {
11
11
  }
12
12
 
13
13
  // src/program.ts
14
- import process37 from "node:process";
14
+ import process38 from "node:process";
15
15
  import * as p61 from "@clack/prompts";
16
16
  import { Command as Command104 } from "commander";
17
17
  import nodePath from "node:path";
@@ -21,7 +21,7 @@ import pc38 from "picocolors";
21
21
  // package.json
22
22
  var package_default = {
23
23
  name: "vela",
24
- version: "0.11.3",
24
+ version: "0.11.5",
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.3",
60
+ "@velastack/patterns": "^0.2.4",
59
61
  "@velastack/pocketbase-codegen": "^0.1.0",
60
62
  "annotate-json-schema": "^0.1.0",
61
63
  commander: "^13.1.0",
@@ -81,6 +83,7 @@ var package_default = {
81
83
  "@types/node": "^22.0.0",
82
84
  esbuild: "^0.28.2",
83
85
  prettier: "^3.9.6",
86
+ shellcheck: "^4.1.0",
84
87
  typescript: "^5.6.0",
85
88
  vite: "^8.2.2",
86
89
  vitest: "^4.1.11"
@@ -97,6 +100,18 @@ var package_default = {
97
100
  optional: true
98
101
  }
99
102
  },
103
+ overrides: {
104
+ "global-agent": "^4.1.3",
105
+ "@xhmikosr/decompress-unzip": {
106
+ "file-type": "^21.3.1"
107
+ },
108
+ "@xhmikosr/decompress-tar": {
109
+ "file-type": "^21.3.1"
110
+ },
111
+ "@felipecrs/decompress-tarxz": {
112
+ "file-type": "^21.3.1"
113
+ }
114
+ },
100
115
  keywords: [
101
116
  "svelte",
102
117
  "sveltekit",
@@ -288,8 +303,20 @@ function mergePackageJson(user, template) {
288
303
  }
289
304
  return { merged, added, conflicts, replaced };
290
305
  }
291
- function readPackageJson(path46) {
292
- return JSON.parse(fs.readFileSync(path46, "utf8"));
306
+ function dropTemplateAdapters(user, template) {
307
+ const isAdapter = (name) => name.startsWith("@sveltejs/adapter-");
308
+ const userHasOne = DEP_KINDS.some((kind) => Object.keys(user[kind] ?? {}).some(isAdapter));
309
+ if (!userHasOne) return template;
310
+ const copy = { ...template };
311
+ for (const kind of DEP_KINDS) {
312
+ const deps = template[kind];
313
+ if (!deps) continue;
314
+ copy[kind] = Object.fromEntries(Object.entries(deps).filter(([name]) => !isAdapter(name)));
315
+ }
316
+ return copy;
317
+ }
318
+ function readPackageJson(path47) {
319
+ return JSON.parse(fs.readFileSync(path47, "utf8"));
293
320
  }
294
321
  var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
295
322
  var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
@@ -302,12 +329,12 @@ function fillTemplatePlaceholders(raw, values) {
302
329
  function escapeSingleQuoted(value) {
303
330
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
304
331
  }
305
- function readTemplatePackageJson(path46, values) {
306
- const raw = fillTemplatePlaceholders(fs.readFileSync(path46, "utf8"), values);
332
+ function readTemplatePackageJson(path47, values) {
333
+ const raw = fillTemplatePlaceholders(fs.readFileSync(path47, "utf8"), values);
307
334
  return JSON.parse(raw);
308
335
  }
309
- function writePackageJson(path46, pkg) {
310
- fs.writeFileSync(path46, JSON.stringify(pkg, null, " ") + "\n");
336
+ function writePackageJson(path47, pkg) {
337
+ fs.writeFileSync(path47, JSON.stringify(pkg, null, " ") + "\n");
311
338
  }
312
339
  function toValidPackageName(name) {
313
340
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
@@ -863,7 +890,7 @@ async function packageManagerPrompt(cwd) {
863
890
  }
864
891
  return pm;
865
892
  }
866
- async function installDependencies(agent, cwd) {
893
+ async function installDependencies(agent, cwd, { exitOnFailure = true } = {}) {
867
894
  const task = p2.taskLog({
868
895
  title: `Installing dependencies with ${agent}...`,
869
896
  limit: Math.ceil(process5.stdout.rows / 2),
@@ -880,14 +907,16 @@ async function installDependencies(agent, cwd) {
880
907
  proc.process?.stderr?.on("data", (data) => task.message(data.toString(), { raw: true }));
881
908
  await proc;
882
909
  task.success("Successfully installed dependencies");
910
+ return true;
883
911
  } catch {
884
912
  task.error("Failed to install dependencies");
913
+ if (!exitOnFailure) return false;
885
914
  p2.cancel("Operation failed.");
886
915
  process5.exit(2);
887
916
  }
888
917
  }
889
- function addPnpmBuildDependencies(cwd, packageManager, allowedPackages) {
890
- if (!packageManager || packageManager !== "pnpm") return;
918
+ function addPnpmBuildDependencies(cwd, packageManager2, allowedPackages) {
919
+ if (!packageManager2 || packageManager2 !== "pnpm") return;
891
920
  const pkgPath = path6.join(cwd, "package.json");
892
921
  if (!fs7.existsSync(pkgPath)) return;
893
922
  const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
@@ -1024,11 +1053,11 @@ function getPocketbaseMetadata(cwd) {
1024
1053
  return null;
1025
1054
  }
1026
1055
  async function execPackageBin(cwd, args, stdio = "pipe") {
1027
- const packageManager = (await detect2({ cwd }))?.name ?? "npm";
1028
- const resolved = resolveCommand(packageManager, "execute", args);
1029
- if (!resolved) throw new Error(`Could not resolve command for ${packageManager}`);
1056
+ const packageManager2 = (await detect2({ cwd }))?.name ?? "npm";
1057
+ const resolved = resolveCommand(packageManager2, "execute", args);
1058
+ if (!resolved) throw new Error(`Could not resolve command for ${packageManager2}`);
1030
1059
  const { command, args: resolvedArgs } = resolved;
1031
- if (packageManager === "npm") resolvedArgs.unshift("--yes");
1060
+ if (packageManager2 === "npm") resolvedArgs.unshift("--yes");
1032
1061
  return x(command, resolvedArgs, { nodeOptions: { cwd, stdio }, throwOnError: true });
1033
1062
  }
1034
1063
  async function withPocketbase(cwd, fn, creds) {
@@ -1476,7 +1505,14 @@ var GITIGNORE_ENTRIES = [
1476
1505
  "!.env.example",
1477
1506
  "!.env.test",
1478
1507
  "vite.config.js.timestamp-*",
1479
- "vite.config.ts.timestamp-*"
1508
+ "vite.config.ts.timestamp-*",
1509
+ // The local database, minus the parts that are source. Same block as the
1510
+ // template's _gitignore; without it a blessed project commits its SQLite.
1511
+ "/data/*",
1512
+ "!/data/fixtures",
1513
+ "!/data/seeds",
1514
+ "!/data/hooks",
1515
+ "/backups"
1480
1516
  ];
1481
1517
  function mergeGitignore(filePath) {
1482
1518
  const existing = fs13.existsSync(filePath) ? fs13.readFileSync(filePath, "utf8") : "";
@@ -1644,13 +1680,13 @@ async function blessProject(cwdArg, options) {
1644
1680
  mergeAppDts(templateDir, projectPath);
1645
1681
  maybeReplaceRoutes(projectPath, templateDir, options);
1646
1682
  p4.log.success("Vela files in place");
1647
- let packageManager;
1683
+ let packageManager2;
1648
1684
  if (options.install !== false) {
1649
1685
  const pm = typeof options.install === "string" ? options.install : await packageManagerPrompt(projectPath);
1650
1686
  if (pm) {
1651
1687
  addPnpmBuildDependencies(projectPath, pm, ["esbuild", "pocketbase-server"]);
1652
1688
  await installDependencies(pm, projectPath);
1653
- packageManager = pm;
1689
+ packageManager2 = pm;
1654
1690
  }
1655
1691
  }
1656
1692
  p4.log.step("Initializing PocketBase...");
@@ -1664,7 +1700,7 @@ async function blessProject(cwdArg, options) {
1664
1700
  ["PocketBase superuser credentials \u2014 used by `vela` commands"]
1665
1701
  );
1666
1702
  p4.log.success("PocketBase initialized");
1667
- printNextSteps(projectPath, packageManager);
1703
+ printNextSteps(projectPath, packageManager2);
1668
1704
  }
1669
1705
  function ensureShadcnCss(projectPath) {
1670
1706
  if (ensureShadcnImport(path13.join(projectPath, "src", "app.css"))) {
@@ -1710,7 +1746,10 @@ function mergeDependencies(projectPath, templateDir) {
1710
1746
  appName,
1711
1747
  cliVersion: package_default.version
1712
1748
  });
1713
- const { merged, added, conflicts, replaced } = mergePackageJson(userPkg, templatePkg);
1749
+ const { merged, added, conflicts, replaced } = mergePackageJson(
1750
+ userPkg,
1751
+ dropTemplateAdapters(userPkg, templatePkg)
1752
+ );
1714
1753
  writePackageJson(userPkgPath, merged);
1715
1754
  if (added.length > 0) {
1716
1755
  p4.log.info(
@@ -1848,9 +1887,9 @@ function summarize(names) {
1848
1887
  if (names.length <= 4) return names.join(", ");
1849
1888
  return `${names.slice(0, 3).join(", ")}, and ${names.length - 3} more`;
1850
1889
  }
1851
- function printNextSteps(projectPath, packageManager) {
1890
+ function printNextSteps(projectPath, packageManager2) {
1852
1891
  const relative = path13.relative(process7.cwd(), projectPath);
1853
- const pm = packageManager ?? getUserAgent() ?? "npm";
1892
+ const pm = packageManager2 ?? getUserAgent() ?? "npm";
1854
1893
  const nextSteps = [];
1855
1894
  if (relative !== "") {
1856
1895
  const hasSpaces = relative.includes(" ");
@@ -1898,19 +1937,19 @@ var create = new Command3("create").description("scaffold a new velastack projec
1898
1937
  return runCommand(async () => {
1899
1938
  const listing = await listAllTemplates();
1900
1939
  const options = parseOptions(optionsSchema2(listing), rawOpts);
1901
- const { directory, packageManager, name, template } = await createProject(
1940
+ const { directory, packageManager: packageManager2, name, template } = await createProject(
1902
1941
  projectPath,
1903
1942
  options,
1904
1943
  listing
1905
1944
  );
1906
1945
  const relative = path14.relative(process8.cwd(), directory);
1907
- const pm = packageManager ?? (await detect3({ cwd: directory }))?.name ?? getUserAgent() ?? "npm";
1946
+ const pm = packageManager2 ?? (await detect3({ cwd: directory }))?.name ?? getUserAgent() ?? "npm";
1908
1947
  const nextSteps = [];
1909
1948
  if (relative !== "") {
1910
1949
  const hasSpaces = relative.includes(" ");
1911
1950
  nextSteps.push(`\`cd ${hasSpaces ? `"${relative}"` : relative}\``);
1912
1951
  }
1913
- if (!packageManager) {
1952
+ if (!packageManager2) {
1914
1953
  const resolved = resolveCommand3(pm, "install", []);
1915
1954
  if (resolved) {
1916
1955
  nextSteps.push(
@@ -1999,14 +2038,14 @@ async function createProject(cwdArg, options, listing) {
1999
2038
  throw new Error(`Template ${template.name} is missing package.template.json`);
2000
2039
  }
2001
2040
  p5.log.success("Project created");
2002
- let packageManager;
2041
+ let packageManager2;
2003
2042
  if (options.install !== false) {
2004
2043
  const pm = typeof options.install === "string" ? options.install : await packageManagerPrompt(projectPath);
2005
2044
  if (pm) {
2006
2045
  const builds = template.backend ? ["esbuild", "pocketbase-server"] : ["esbuild"];
2007
2046
  addPnpmBuildDependencies(projectPath, pm, builds);
2008
2047
  await installDependencies(pm, projectPath);
2009
- packageManager = pm;
2048
+ packageManager2 = pm;
2010
2049
  }
2011
2050
  }
2012
2051
  if (credentials) {
@@ -2032,7 +2071,7 @@ async function createProject(cwdArg, options, listing) {
2032
2071
  );
2033
2072
  p5.log.success("PocketBase initialized");
2034
2073
  }
2035
- return { directory: projectPath, packageManager, name, template };
2074
+ return { directory: projectPath, packageManager: packageManager2, name, template };
2036
2075
  }
2037
2076
  function promptCredentials(options, onCancel2) {
2038
2077
  return p5.group(
@@ -2996,13 +3035,13 @@ var notifications = new Command18("notifications").description("enable in-app no
2996
3035
  );
2997
3036
 
2998
3037
  // src/commands/enable/s3.ts
2999
- import process12 from "node:process";
3038
+ import process13 from "node:process";
3000
3039
  import { Command as Command19 } from "commander";
3001
3040
  import * as p17 from "@clack/prompts";
3002
3041
  import pc8 from "picocolors";
3003
3042
 
3004
3043
  // src/lib/server-command.ts
3005
- import process11 from "node:process";
3044
+ import process12 from "node:process";
3006
3045
  import * as v6 from "valibot";
3007
3046
  import * as p15 from "@clack/prompts";
3008
3047
  import pc5 from "picocolors";
@@ -3133,6 +3172,7 @@ function writeBinding(workspaceRootDir, envTag, binding) {
3133
3172
  }
3134
3173
 
3135
3174
  // src/lib/instance.ts
3175
+ import crypto2 from "node:crypto";
3136
3176
  var PROD_ENV = "prod";
3137
3177
  var MAX_SEGMENT = 48;
3138
3178
  function normalizeEnvTag(tag) {
@@ -3145,9 +3185,14 @@ function normalizeEnvTag(tag) {
3145
3185
  }
3146
3186
  return normalized === "production" ? PROD_ENV : normalized;
3147
3187
  }
3188
+ var SLUG_HASH = 6;
3148
3189
  function branchToEnvTag(branch) {
3149
- const slug2 = branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SEGMENT);
3150
- return `preview--${slug2}`;
3190
+ const lower = branch.toLowerCase();
3191
+ const slug2 = lower.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3192
+ if (slug2 === branch && slug2.length <= MAX_SEGMENT) return `preview--${slug2}`;
3193
+ const hash = crypto2.createHash("sha256").update(branch).digest("hex").slice(0, SLUG_HASH);
3194
+ const head = slug2.slice(0, MAX_SEGMENT - SLUG_HASH - 1).replace(/-+$/, "");
3195
+ return `preview--${head}-${hash}`;
3151
3196
  }
3152
3197
  function instanceId(appId, envTag = PROD_ENV) {
3153
3198
  const env2 = normalizeEnvTag(envTag);
@@ -3156,8 +3201,12 @@ function instanceId(appId, envTag = PROD_ENV) {
3156
3201
  function isProd(envTag) {
3157
3202
  return normalizeEnvTag(envTag) === PROD_ENV;
3158
3203
  }
3159
- function releaseId(date = /* @__PURE__ */ new Date()) {
3160
- return date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
3204
+ function releaseId(date = /* @__PURE__ */ new Date(), suffix = randomSuffix()) {
3205
+ const stamp = date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
3206
+ return `${stamp}-${suffix}`;
3207
+ }
3208
+ function randomSuffix() {
3209
+ return crypto2.randomBytes(2).toString("hex");
3161
3210
  }
3162
3211
 
3163
3212
  // src/lib/artifact.ts
@@ -3170,7 +3219,7 @@ import { resolveCommand as resolveCommand4 } from "package-manager-detector/comm
3170
3219
  import fs20 from "node:fs";
3171
3220
  import os3 from "node:os";
3172
3221
  import path19 from "node:path";
3173
- import crypto2 from "node:crypto";
3222
+ import crypto3 from "node:crypto";
3174
3223
  import process10 from "node:process";
3175
3224
  import { spawn as spawn2 } from "node:child_process";
3176
3225
  var RemoteCommandError = class extends Error {
@@ -3214,7 +3263,7 @@ var SshSession = class {
3214
3263
  if (this.controlPath) return;
3215
3264
  const dir = path19.join(os3.tmpdir(), "vela-ssh");
3216
3265
  fs20.mkdirSync(dir, { recursive: true, mode: 448 });
3217
- const socket = path19.join(dir, `${crypto2.randomBytes(6).toString("hex")}.sock`);
3266
+ const socket = path19.join(dir, `${crypto3.randomBytes(6).toString("hex")}.sock`);
3218
3267
  this.controlPath = socket;
3219
3268
  const args = [
3220
3269
  ...this.sshArgs(),
@@ -3510,10 +3559,11 @@ function collectArtifact(cwd, config = {}) {
3510
3559
  const buildPath = path20.join(cwd, outputDir);
3511
3560
  if (!fs21.existsSync(path20.join(buildPath, "index.js"))) {
3512
3561
  throw new BuildError(
3513
- `No ${outputDir}/index.js after the build.
3562
+ `No ${outputDir}/index.js to deploy.
3514
3563
 
3515
- Deploying to a server needs @sveltejs/adapter-node. Install it and set it as
3516
- the adapter in your Vite or Svelte config, then build again.`
3564
+ Deploying to a server needs the output of @sveltejs/adapter-node. Check that the
3565
+ build ran with it as the adapter (and that \`outputDir\` in velastack.config
3566
+ matches where it writes), then deploy again.`
3517
3567
  );
3518
3568
  }
3519
3569
  entries.push({ localPath: buildPath, remoteDir: "" });
@@ -3562,24 +3612,79 @@ function sshOptionsFrom(options) {
3562
3612
  }
3563
3613
 
3564
3614
  // src/lib/remote.ts
3615
+ import crypto4 from "node:crypto";
3616
+ import fs22 from "node:fs";
3565
3617
  import path21 from "node:path";
3618
+ import process11 from "node:process";
3566
3619
  var VELA_ROOT = "/var/lib/vela";
3567
3620
  var VELA_ETC = "/etc/vela";
3568
3621
  var VELA_USER = "vela";
3569
- var SCRIPTS_DIR = `${VELA_ROOT}/scripts`;
3622
+ var SCRIPT_VERSIONS_DIR = `${VELA_ROOT}/script-versions`;
3570
3623
  var PROVISIONED_MARKER = `${VELA_ETC}/provisioned`;
3571
3624
  var ORIGIN_FILE = `${VELA_ETC}/origin.json`;
3625
+ function instanceHasBackend(state) {
3626
+ if (!state) return false;
3627
+ return state.backend ?? Boolean(state.pbPort);
3628
+ }
3572
3629
  function serverTemplatesDir() {
3573
3630
  return path21.join(templatesDir(), "server");
3574
3631
  }
3632
+ var DIGEST_LENGTH = 12;
3633
+ function serverScriptsDigest(dir = serverTemplatesDir()) {
3634
+ const hash = crypto4.createHash("sha256");
3635
+ for (const file of listFiles(dir).sort()) {
3636
+ hash.update(file).update("\0").update(fs22.readFileSync(path21.join(dir, file))).update("\0");
3637
+ }
3638
+ return hash.digest("hex").slice(0, DIGEST_LENGTH);
3639
+ }
3640
+ function listFiles(root, prefix = "") {
3641
+ const files = [];
3642
+ for (const entry of fs22.readdirSync(path21.join(root, prefix), { withFileTypes: true })) {
3643
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
3644
+ if (entry.isDirectory()) files.push(...listFiles(root, rel));
3645
+ else if (entry.isFile()) files.push(rel);
3646
+ }
3647
+ return files;
3648
+ }
3649
+ var scriptDirs = /* @__PURE__ */ new WeakMap();
3575
3650
  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] });
3651
+ const digest = serverScriptsDigest();
3652
+ const dir = `${SCRIPT_VERSIONS_DIR}/${digest}`;
3653
+ const present = await session.script(`[ -d "$1" ] && echo yes || echo no`, { args: [dir] });
3654
+ if (present.stdout.trim() !== "yes") {
3655
+ const incoming = `${SCRIPT_VERSIONS_DIR}/.incoming.${digest}.${process11.pid}.${Date.now()}`;
3656
+ await session.script(`mkdir -p "$1" && chmod 0755 "$(dirname "$1")" "$1"`, {
3657
+ args: [incoming]
3658
+ });
3659
+ await session.uploadDir(serverTemplatesDir(), incoming, ["--chmod=D755,F755"]);
3660
+ await session.script(
3661
+ `chown -R root:root "$1"
3662
+ if [ -d "$2" ]; then rm -rf "$1"; else mv -T "$1" "$2" || { rm -rf "$1"; [ -d "$2" ]; }; fi`,
3663
+ { args: [incoming, dir] }
3664
+ );
3665
+ }
3666
+ await session.script(
3667
+ `touch "$1"
3668
+ ln -sfn "$1" "$2/.current.tmp" && mv -Tf "$2/.current.tmp" "$2/current"
3669
+ find "$2" -mindepth 1 -maxdepth 1 -type d -name '[0-9a-f]*' ! -name "$(basename "$1")" -mtime +30 -exec rm -rf {} + 2>/dev/null || true
3670
+ find "$2" -mindepth 1 -maxdepth 1 -type d -name '.incoming.*' -mmin +120 -exec rm -rf {} + 2>/dev/null || true`,
3671
+ { args: [dir, SCRIPT_VERSIONS_DIR] }
3672
+ );
3673
+ scriptDirs.set(session, dir);
3674
+ return dir;
3675
+ }
3676
+ async function serverTime(session) {
3677
+ const result = await session.script(`date -u +%s`);
3678
+ const seconds = Number(result.stdout.trim());
3679
+ if (!Number.isFinite(seconds) || seconds <= 0) {
3680
+ throw new Error(`could not read the clock on ${session.target}`);
3681
+ }
3682
+ return new Date(seconds * 1e3);
3579
3683
  }
3580
3684
  async function runServerScript(session, name, opts = {}) {
3685
+ const dir = scriptDirs.get(session) ?? `${SCRIPT_VERSIONS_DIR}/current`;
3581
3686
  const result = await session.script(`script="$1"; shift; exec "$script" "$@"`, {
3582
- args: [`${SCRIPTS_DIR}/${name}`, ...opts.args ?? []],
3687
+ args: [`${dir}/${name}`, ...opts.args ?? []],
3583
3688
  stream: opts.stream
3584
3689
  });
3585
3690
  return parseResult(result);
@@ -3653,7 +3758,7 @@ var remotePaths = {
3653
3758
  };
3654
3759
 
3655
3760
  // src/lib/remote-env.ts
3656
- import fs22 from "node:fs";
3761
+ import fs23 from "node:fs";
3657
3762
  import dotenv from "dotenv";
3658
3763
  var KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
3659
3764
  function isValidKey(key) {
@@ -3701,7 +3806,7 @@ function quote(value) {
3701
3806
  return `"${escaped}"`;
3702
3807
  }
3703
3808
  function readLocalEnvFile(file) {
3704
- const parsed = dotenv.parse(fs22.readFileSync(file));
3809
+ const parsed = dotenv.parse(fs23.readFileSync(file));
3705
3810
  const result = {};
3706
3811
  for (const [key, value] of Object.entries(parsed)) {
3707
3812
  if (isValidKey(key)) result[key] = value;
@@ -3817,6 +3922,20 @@ var SERVER_OPTIONS_SCHEMA = {
3817
3922
  server: v6.optional(v6.string())
3818
3923
  };
3819
3924
  var OptionsSchema = v6.object(SERVER_OPTIONS_SCHEMA);
3925
+ var DEFAULT_LOCK_WAIT = "300";
3926
+ var LOCK_WAIT_SCHEMA = {
3927
+ lockWait: v6.optional(v6.pipe(v6.string(), v6.regex(/^\d+$/, "must be a whole number of seconds")))
3928
+ };
3929
+ function addLockWaitOption(command) {
3930
+ return command.option(
3931
+ "--lock-wait <seconds>",
3932
+ "how long to wait for another operation on this target to finish before giving up",
3933
+ DEFAULT_LOCK_WAIT
3934
+ );
3935
+ }
3936
+ function lockWaitArgs(lockWait) {
3937
+ return ["--lock-wait", lockWait ?? DEFAULT_LOCK_WAIT];
3938
+ }
3820
3939
  function addTargetOptions(command, fallback) {
3821
3940
  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
3941
  }
@@ -3883,6 +4002,7 @@ async function ensureBinding(workspaceRootDir, target, request = {}) {
3883
4002
  const key = bindingKey(target);
3884
4003
  const existing = readBinding(workspaceRootDir, key);
3885
4004
  const moving = Boolean(request.server && existing && existing.server !== request.server);
4005
+ if (moving) await confirmMove(target, existing.server, request.server);
3886
4006
  let server = request.server ?? existing?.server;
3887
4007
  if (!server) {
3888
4008
  server = await promptServer(target);
@@ -3905,12 +4025,31 @@ longer reach it. Remove it there first if that was not intended.`
3905
4025
  }
3906
4026
  return binding;
3907
4027
  }
4028
+ async function confirmMove(target, from, to) {
4029
+ const name = bindingName(target);
4030
+ if (!process12.stdout.isTTY || process12.env.CI) {
4031
+ throw new Error(
4032
+ `${pc5.cyan(`--server ${to}`)} does not match the server recorded for ${pc5.cyan(name)}, ${pc5.cyan(from)}.
4033
+
4034
+ Drop ${pc5.cyan("--server")} to use the recorded one, or move the binding on purpose by running
4035
+ this command once from a terminal (or editing .vela/project.json).`
4036
+ );
4037
+ }
4038
+ const ok = await p15.confirm({
4039
+ message: `${name} ${target.kind === "preview" ? "run" : "runs"} on ${from}. Point ${name} at ${to} instead?`,
4040
+ initialValue: false
4041
+ });
4042
+ if (p15.isCancel(ok) || !ok) {
4043
+ p15.cancel("Operation cancelled.");
4044
+ process12.exit(0);
4045
+ }
4046
+ }
3908
4047
  function bindingName(target) {
3909
4048
  return target.kind === "preview" ? "previews" : target.name;
3910
4049
  }
3911
4050
  async function promptServer(target) {
3912
4051
  const name = bindingName(target);
3913
- if (!process11.stdout.isTTY || process11.env.CI) {
4052
+ if (!process12.stdout.isTTY || process12.env.CI) {
3914
4053
  throw new Error(
3915
4054
  `${name} ${target.kind === "preview" ? "are" : "is"} not bound to a server yet, and there is no terminal to ask on.
3916
4055
 
@@ -3928,19 +4067,19 @@ and commit ${pc5.cyan(".vela/project.json")}.`
3928
4067
  });
3929
4068
  if (p15.isCancel(value)) {
3930
4069
  p15.cancel("Operation cancelled.");
3931
- process11.exit(0);
4070
+ process12.exit(0);
3932
4071
  }
3933
4072
  return value.trim();
3934
4073
  }
3935
4074
  async function promptDomain(target) {
3936
- if (!process11.stdout.isTTY || process11.env.CI) return void 0;
4075
+ if (!process12.stdout.isTTY || process12.env.CI) return void 0;
3937
4076
  const value = await p15.text({
3938
4077
  message: `Which hostname should ${target.name} be served on?`,
3939
4078
  placeholder: "example.com \u2014 leave blank to decide later"
3940
4079
  });
3941
4080
  if (p15.isCancel(value)) {
3942
4081
  p15.cancel("Operation cancelled.");
3943
- process11.exit(0);
4082
+ process12.exit(0);
3944
4083
  }
3945
4084
  return value.trim() || void 0;
3946
4085
  }
@@ -3996,17 +4135,17 @@ function envFilePath(workspaceRootDir) {
3996
4135
  import pc7 from "picocolors";
3997
4136
 
3998
4137
  // src/lib/local-env.ts
3999
- import fs23 from "node:fs";
4138
+ import fs24 from "node:fs";
4000
4139
  import * as p16 from "@clack/prompts";
4001
4140
  import pc6 from "picocolors";
4002
4141
  function readLocalEnv(envFile) {
4003
- if (!fs23.existsSync(envFile)) return {};
4142
+ if (!fs24.existsSync(envFile)) return {};
4004
4143
  return readLocalEnvFile(envFile);
4005
4144
  }
4006
4145
  function editLocalEnv(envFile, edit) {
4007
- const before = fs23.existsSync(envFile) ? fs23.readFileSync(envFile, "utf8") : "";
4146
+ const before = fs24.existsSync(envFile) ? fs24.readFileSync(envFile, "utf8") : "";
4008
4147
  const after = edit(before);
4009
- if (after !== before) fs23.writeFileSync(envFile, after);
4148
+ if (after !== before) fs24.writeFileSync(envFile, after);
4010
4149
  }
4011
4150
  function setLocalEnv(envFile, key, value) {
4012
4151
  editLocalEnv(envFile, (content) => upsertEnvVar(content, key, value));
@@ -4040,12 +4179,13 @@ Set both ${pc6.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc6.cyan("POCKETBASE_SU
4040
4179
  import PocketBase2 from "pocketbase";
4041
4180
  async function openRemoteDatabase(session, instance) {
4042
4181
  const [state] = await readInstanceStates(session, instance);
4043
- const pbPort = state?.pbPort;
4182
+ const pbPort = instanceHasBackend(state) ? state?.pbPort : void 0;
4044
4183
  if (!pbPort) {
4045
4184
  throw new Error(
4046
4185
  `${instance} has no deployed database yet.
4047
4186
 
4048
- Run \`vela deploy\` first \u2014 the database is created by the first deploy.`
4187
+ Run \`vela deploy\` first \u2014 the database is created by the first deploy of a
4188
+ project with a backend (\`vela bless\` adds one).`
4049
4189
  );
4050
4190
  }
4051
4191
  const env2 = await readRemoteEnv(session, instance);
@@ -4138,7 +4278,7 @@ Set ${pc7.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc7.cyan("POCKETBASE_SUPERUS
4138
4278
  }
4139
4279
 
4140
4280
  // src/lib/s3-settings.ts
4141
- import fs24 from "node:fs";
4281
+ import fs25 from "node:fs";
4142
4282
  import path22 from "node:path";
4143
4283
  var VIRTUAL_HOSTED = [/\.amazonaws\.com$/i, /\.r2\.cloudflarestorage\.com$/i];
4144
4284
  function defaultForcePathStyle(endpoint) {
@@ -4182,7 +4322,7 @@ async function hasLocalUploads(session, instance, workspaceRootDir) {
4182
4322
  function hasFile(dir) {
4183
4323
  let entries;
4184
4324
  try {
4185
- entries = fs24.readdirSync(dir, { withFileTypes: true });
4325
+ entries = fs25.readdirSync(dir, { withFileTypes: true });
4186
4326
  } catch {
4187
4327
  return false;
4188
4328
  }
@@ -4261,8 +4401,8 @@ async function promptConfig(options, existing) {
4261
4401
  const bucket = options.bucket ?? await text7("S3 bucket name", existing.bucket);
4262
4402
  const region = options.region ?? await text7("S3 region", existing.region || "us-east-1");
4263
4403
  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(
4404
+ const secret = options.secret ?? process13.env.VELA_S3_SECRET ?? await password5("S3 secret key");
4405
+ const forcePathStyle = options.forcePathStyle ?? await confirm4(
4266
4406
  "Address the bucket in the path? (MinIO, Ceph and most self-hosted gateways need this)",
4267
4407
  defaultForcePathStyle(endpoint)
4268
4408
  );
@@ -4276,7 +4416,7 @@ async function text7(message, initialValue = "") {
4276
4416
  });
4277
4417
  if (p17.isCancel(value)) {
4278
4418
  p17.cancel("Operation cancelled.");
4279
- process12.exit(0);
4419
+ process13.exit(0);
4280
4420
  }
4281
4421
  return value.trim();
4282
4422
  }
@@ -4287,21 +4427,21 @@ async function password5(message) {
4287
4427
  });
4288
4428
  if (p17.isCancel(value)) {
4289
4429
  p17.cancel("Operation cancelled.");
4290
- process12.exit(0);
4430
+ process13.exit(0);
4291
4431
  }
4292
4432
  return value;
4293
4433
  }
4294
- async function confirm3(message, initialValue) {
4434
+ async function confirm4(message, initialValue) {
4295
4435
  const value = await p17.confirm({ message, initialValue });
4296
4436
  if (p17.isCancel(value)) {
4297
4437
  p17.cancel("Operation cancelled.");
4298
- process12.exit(0);
4438
+ process13.exit(0);
4299
4439
  }
4300
4440
  return value;
4301
4441
  }
4302
4442
 
4303
4443
  // src/commands/enable/smtp.ts
4304
- import process13 from "node:process";
4444
+ import process14 from "node:process";
4305
4445
  import { Command as Command20 } from "commander";
4306
4446
  import * as p18 from "@clack/prompts";
4307
4447
  var smtp = new Command20("smtp").description("configure SMTP for transactional email").configureHelp(helpConfig).action(
@@ -4330,7 +4470,7 @@ var smtp = new Command20("smtp").description("configure SMTP for transactional e
4330
4470
  {
4331
4471
  onCancel: () => {
4332
4472
  p18.cancel("Operation cancelled.");
4333
- process13.exit(0);
4473
+ process14.exit(0);
4334
4474
  }
4335
4475
  }
4336
4476
  );
@@ -4357,7 +4497,7 @@ var smtp = new Command20("smtp").description("configure SMTP for transactional e
4357
4497
  );
4358
4498
 
4359
4499
  // src/commands/enable/cms.ts
4360
- import process14 from "node:process";
4500
+ import process15 from "node:process";
4361
4501
  import { Command as Command21 } from "commander";
4362
4502
  import * as p19 from "@clack/prompts";
4363
4503
  import pc9 from "picocolors";
@@ -4374,7 +4514,7 @@ Run ${pc9.cyan("vela bless")} to add a backend, or point at a hosted CMS with ${
4374
4514
  );
4375
4515
  p19.log.message();
4376
4516
  p19.cancel("Operation failed.");
4377
- process14.exitCode = 1;
4517
+ process15.exitCode = 1;
4378
4518
  return;
4379
4519
  }
4380
4520
  await runPattern("enable-cms", cmd.args, opts.endpoint ? { endpoint: opts.endpoint } : {}, {
@@ -4454,7 +4594,7 @@ import { Command as Command35 } from "commander";
4454
4594
  import { Command as Command25 } from "commander";
4455
4595
 
4456
4596
  // src/commands/disable/_shared.ts
4457
- import process15 from "node:process";
4597
+ import process16 from "node:process";
4458
4598
  import * as p20 from "@clack/prompts";
4459
4599
  async function runDisable(opts, flags, cmdArgs) {
4460
4600
  if (!flags.yes) {
@@ -4464,7 +4604,7 @@ async function runDisable(opts, flags, cmdArgs) {
4464
4604
  });
4465
4605
  if (p20.isCancel(ok) || !ok) {
4466
4606
  p20.cancel("Operation cancelled.");
4467
- process15.exit(0);
4607
+ process16.exit(0);
4468
4608
  }
4469
4609
  }
4470
4610
  await runPattern(opts.slug, cmdArgs, { destructive: true }, opts.report);
@@ -4735,14 +4875,14 @@ import { Command as Command41 } from "commander";
4735
4875
  import { Command as Command36 } from "commander";
4736
4876
 
4737
4877
  // src/commands/destroy/_shared.ts
4738
- import process16 from "node:process";
4878
+ import process17 from "node:process";
4739
4879
  import * as p22 from "@clack/prompts";
4740
4880
  async function runDestroy(slug2, model, confirmMessage, report4, flags) {
4741
4881
  if (!flags.yes) {
4742
4882
  const ok = await p22.confirm({ message: confirmMessage, initialValue: false });
4743
4883
  if (p22.isCancel(ok) || !ok) {
4744
4884
  p22.cancel("Operation cancelled.");
4745
- process16.exit(0);
4885
+ process17.exit(0);
4746
4886
  }
4747
4887
  }
4748
4888
  await runPattern(slug2, [model], { destructive: true, route: flags.route }, report4);
@@ -4845,11 +4985,55 @@ var scaffold2 = new Command39("scaffold").description("destroy a scaffold genera
4845
4985
  );
4846
4986
 
4847
4987
  // src/commands/destroy/deployment.ts
4848
- import process17 from "node:process";
4988
+ import process18 from "node:process";
4849
4989
  import { Command as Command40 } from "commander";
4850
4990
  import * as p25 from "@clack/prompts";
4851
4991
  import pc13 from "picocolors";
4852
4992
 
4993
+ // src/lib/destroy-guard.ts
4994
+ function isPreview(envTag) {
4995
+ return envTag.startsWith("preview--");
4996
+ }
4997
+ function destroyDecision(request) {
4998
+ const { appName, purge, yes, confirm: confirm14, interactive } = request;
4999
+ const dangerous = isProd(request.envTag) || purge && !isPreview(request.envTag);
5000
+ const what = describe(request);
5001
+ if (confirm14 !== void 0) {
5002
+ if (confirm14 === appName) return { kind: "proceed" };
5003
+ return {
5004
+ kind: "refuse",
5005
+ message: `\`--confirm ${confirm14}\` does not name this app. Removing ${what} takes \`--confirm ${appName}\`.`
5006
+ };
5007
+ }
5008
+ if (dangerous) {
5009
+ if (yes) {
5010
+ return {
5011
+ kind: "refuse",
5012
+ message: `\`--yes\` is not enough to remove ${what}.
5013
+
5014
+ Pass \`--confirm ${appName}\` as well \u2014 the app name, the same thing a terminal would ask you to type.`
5015
+ };
5016
+ }
5017
+ if (interactive) return { kind: "prompt", byName: true };
5018
+ return {
5019
+ kind: "refuse",
5020
+ message: `Removing ${what} needs a confirmation, and there is no terminal to ask on.
5021
+
5022
+ Pass \`--confirm ${appName}\` \u2014 the app name, the same thing a terminal would ask you to type.`
5023
+ };
5024
+ }
5025
+ if (yes) return { kind: "proceed" };
5026
+ if (interactive) return { kind: "prompt", byName: false };
5027
+ return {
5028
+ kind: "refuse",
5029
+ message: `Removing ${what} needs a confirmation, and there is no terminal to ask on. Pass \`--yes\`.`
5030
+ };
5031
+ }
5032
+ function describe({ appName, envTag, purge }) {
5033
+ const target = isProd(envTag) ? "production" : `the ${envTag} environment`;
5034
+ return purge ? `${target} of ${appName} and its database` : `${target} of ${appName}`;
5035
+ }
5036
+
4853
5037
  // src/lib/deploy-report.ts
4854
5038
  import * as p24 from "@clack/prompts";
4855
5039
  import pc12 from "picocolors";
@@ -5042,26 +5226,52 @@ ${pc12.dim(err instanceof Error ? err.message : String(err))}`
5042
5226
  }
5043
5227
 
5044
5228
  // src/commands/destroy/deployment.ts
5045
- var deployment = addTargetOptions(
5046
- new Command40("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
5047
- "production"
5048
- ).option("--purge", "also delete the database and uploaded files").option("-y, --yes", "skip the confirmation prompt").action(
5229
+ var deployment = addLockWaitOption(
5230
+ addTargetOptions(
5231
+ new Command40("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
5232
+ "production"
5233
+ )
5234
+ ).option("--purge", "also delete the database and uploaded files").option("-y, --yes", "skip the confirmation prompt").option(
5235
+ "--confirm <app-name>",
5236
+ "confirm removing production, or a purge, by typing the app name \u2014 what a terminal would ask for"
5237
+ ).action(
5049
5238
  (raw) => runCommand(async () => {
5050
5239
  const options = raw;
5051
5240
  await withTarget(
5052
5241
  raw,
5053
5242
  {
5054
5243
  remote: async (ctx) => {
5055
- if (!options.yes) {
5056
- await confirm7(ctx.appName, ctx.targetName, ctx.envTag, options.purge === true);
5244
+ const decision = destroyDecision({
5245
+ appName: ctx.appName,
5246
+ envTag: ctx.envTag,
5247
+ purge: options.purge === true,
5248
+ yes: options.yes === true,
5249
+ confirm: options.confirm,
5250
+ interactive: Boolean(process18.stdin.isTTY && process18.stdout.isTTY)
5251
+ });
5252
+ if (decision.kind === "refuse") throw new Error(decision.message);
5253
+ if (decision.kind === "prompt") {
5254
+ await confirm8(ctx.appName, ctx.targetName, decision.byName);
5057
5255
  }
5058
5256
  const result = await runServerScript(ctx.session, "destroy.sh", {
5059
- args: [ctx.instance, ...options.purge ? ["--purge"] : []],
5257
+ args: [
5258
+ ctx.instance,
5259
+ ...options.purge ? ["--purge"] : [],
5260
+ ...lockWaitArgs(options.lockWait)
5261
+ ],
5060
5262
  stream: true
5061
5263
  });
5264
+ if (result?.existed === false) {
5265
+ p25.log.info(
5266
+ `Nothing named ${pc13.cyan(`${ctx.appName} (${ctx.targetName})`)} on ${ctx.server}; nothing to remove.`
5267
+ );
5268
+ return;
5269
+ }
5062
5270
  await reportEnvironmentDestroyed(ctx.workspaceRootDir, ctx.envTag);
5063
5271
  p25.log.success(
5064
- `Removed ${pc13.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${ctx.server}.` + (result?.purged ? "" : `
5272
+ `Removed ${pc13.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${ctx.server}.` + (result?.purged ? result.trash ? `
5273
+
5274
+ A snapshot of its data is at ${pc13.cyan(result.trash)} on the server for two weeks.` : "" : `
5065
5275
 
5066
5276
  The database and uploads are still in the instance's shared directory. Pass ${pc13.cyan("--purge")} to delete them.`)
5067
5277
  );
@@ -5074,15 +5284,15 @@ The database and uploads are still in the instance's shared directory. Pass ${pc
5074
5284
  );
5075
5285
  }, "Failed to remove the deployment.")
5076
5286
  );
5077
- async function confirm7(appName, targetName, envTag, purge) {
5078
- if (isProd(envTag) || purge) {
5287
+ async function confirm8(appName, targetName, byName) {
5288
+ if (byName) {
5079
5289
  const answer = await p25.text({
5080
- message: `This removes ${pc13.cyan(`${appName} (${targetName})`)}${purge ? " and its database" : ""}. Type the app name to confirm`,
5290
+ message: `This removes ${pc13.cyan(`${appName} (${targetName})`)}. Type the app name to confirm`,
5081
5291
  validate: (value) => value === appName ? void 0 : `Type ${appName} to confirm`
5082
5292
  });
5083
5293
  if (p25.isCancel(answer)) {
5084
5294
  p25.cancel("Operation cancelled.");
5085
- process17.exit(0);
5295
+ process18.exit(0);
5086
5296
  }
5087
5297
  return;
5088
5298
  }
@@ -5092,7 +5302,7 @@ async function confirm7(appName, targetName, envTag, purge) {
5092
5302
  });
5093
5303
  if (p25.isCancel(ok) || !ok) {
5094
5304
  p25.cancel("Operation cancelled.");
5095
- process17.exit(0);
5305
+ process18.exit(0);
5096
5306
  }
5097
5307
  }
5098
5308
 
@@ -5376,13 +5586,13 @@ var ui = new Command47("ui").description("generate ui components").configureHelp
5376
5586
  import { Command as Command50 } from "commander";
5377
5587
 
5378
5588
  // src/commands/legal/terms.ts
5379
- import fs25 from "node:fs";
5589
+ import fs26 from "node:fs";
5380
5590
  import path23 from "node:path";
5381
5591
  import { Command as Command48 } from "commander";
5382
5592
  import * as p32 from "@clack/prompts";
5383
5593
 
5384
5594
  // src/commands/legal/shared.ts
5385
- import process18 from "node:process";
5595
+ import process19 from "node:process";
5386
5596
  import * as p31 from "@clack/prompts";
5387
5597
  var sharedFields = {
5388
5598
  websiteUrl: () => p31.text({
@@ -5451,7 +5661,7 @@ var sharedFields = {
5451
5661
  };
5452
5662
  var onCancel = () => {
5453
5663
  p31.cancel("Operation cancelled.");
5454
- process18.exit(0);
5664
+ process19.exit(0);
5455
5665
  };
5456
5666
  async function contactMethods(type) {
5457
5667
  const selection = await p31.multiselect({
@@ -6044,9 +6254,9 @@ async function termsAction() {
6044
6254
  "+page.svelte"
6045
6255
  );
6046
6256
  const termsPageTs = path23.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
6047
- fs25.mkdirSync(path23.dirname(termsPage), { recursive: true });
6048
- fs25.writeFileSync(termsPage, html);
6049
- fs25.writeFileSync(
6257
+ fs26.mkdirSync(path23.dirname(termsPage), { recursive: true });
6258
+ fs26.writeFileSync(termsPage, html);
6259
+ fs26.writeFileSync(
6050
6260
  termsPageTs,
6051
6261
  pageMetaTagsLoader("Terms of Service", `Terms of Service for ${core.websiteName}`)
6052
6262
  );
@@ -6065,7 +6275,7 @@ async function termsAction() {
6065
6275
  var terms = new Command48("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
6066
6276
 
6067
6277
  // src/commands/legal/privacy.ts
6068
- import fs26 from "node:fs";
6278
+ import fs27 from "node:fs";
6069
6279
  import path24 from "node:path";
6070
6280
  import { Command as Command49 } from "commander";
6071
6281
  import * as p33 from "@clack/prompts";
@@ -6793,9 +7003,9 @@ async function privacyAction() {
6793
7003
  "privacy",
6794
7004
  "+page.ts"
6795
7005
  );
6796
- fs26.mkdirSync(path24.dirname(privacyPage), { recursive: true });
6797
- fs26.writeFileSync(privacyPage, html);
6798
- fs26.writeFileSync(
7006
+ fs27.mkdirSync(path24.dirname(privacyPage), { recursive: true });
7007
+ fs27.writeFileSync(privacyPage, html);
7008
+ fs27.writeFileSync(
6799
7009
  privacyPageTs,
6800
7010
  pageMetaTagsLoader("Privacy Policy", `Privacy Policy for ${core.websiteName}`)
6801
7011
  );
@@ -6823,7 +7033,7 @@ import { Command as Command56 } from "commander";
6823
7033
  import { Command as Command51 } from "commander";
6824
7034
 
6825
7035
  // src/lib/data.ts
6826
- import fs27 from "node:fs";
7036
+ import fs28 from "node:fs";
6827
7037
  import path25 from "node:path";
6828
7038
  import { ClientResponseError } from "pocketbase";
6829
7039
 
@@ -6865,8 +7075,8 @@ function dataDir(cwd, kind) {
6865
7075
  }
6866
7076
  function getDataFiles(cwd, kind) {
6867
7077
  const dir = dataDir(cwd, kind);
6868
- if (!fs27.existsSync(dir)) return [];
6869
- return fs27.readdirSync(dir).filter((file) => file.endsWith(".json")).sort((a, b) => a.localeCompare(b)).map((file) => ({
7078
+ if (!fs28.existsSync(dir)) return [];
7079
+ return fs28.readdirSync(dir).filter((file) => file.endsWith(".json")).sort((a, b) => a.localeCompare(b)).map((file) => ({
6870
7080
  collectionName: file.replace(/\.json$/i, "").replace(/^\d+[-_]?/, ""),
6871
7081
  filePath: path25.join(dir, file)
6872
7082
  }));
@@ -6878,7 +7088,7 @@ function getFixtureFiles(cwd) {
6878
7088
  return getDataFiles(cwd, "fixtures");
6879
7089
  }
6880
7090
  function readRecords(filePath) {
6881
- return JSON.parse(fs27.readFileSync(filePath, "utf8"));
7091
+ return JSON.parse(fs28.readFileSync(filePath, "utf8"));
6882
7092
  }
6883
7093
  function readSeedIds(cwd) {
6884
7094
  const ids = /* @__PURE__ */ new Map();
@@ -7097,7 +7307,7 @@ var reset = new Command53("reset").description("clear and reload fixtures").conf
7097
7307
  );
7098
7308
 
7099
7309
  // src/commands/fixtures/generate.ts
7100
- import fs28 from "node:fs";
7310
+ import fs29 from "node:fs";
7101
7311
  import path26 from "node:path";
7102
7312
  import { Command as Command54, InvalidArgumentError } from "commander";
7103
7313
  import * as p34 from "@clack/prompts";
@@ -7135,9 +7345,9 @@ async function loadCollections(pb) {
7135
7345
  }
7136
7346
  async function generateFixtureFiles(pb, workspaceRootDir, opts) {
7137
7347
  const fixturesDir = dataDir(workspaceRootDir, "fixtures");
7138
- fs28.mkdirSync(fixturesDir, { recursive: true });
7139
- for (const file of fs28.readdirSync(fixturesDir)) {
7140
- if (file.endsWith(".json")) fs28.unlinkSync(path26.join(fixturesDir, file));
7348
+ fs29.mkdirSync(fixturesDir, { recursive: true });
7349
+ for (const file of fs29.readdirSync(fixturesDir)) {
7350
+ if (file.endsWith(".json")) fs29.unlinkSync(path26.join(fixturesDir, file));
7141
7351
  }
7142
7352
  if (opts.seed !== void 0) faker.seed(opts.seed);
7143
7353
  const generator = createGenerator({
@@ -7204,7 +7414,7 @@ async function generateFixtureFiles(pb, workspaceRootDir, opts) {
7204
7414
  items.push(record);
7205
7415
  }
7206
7416
  const filename = `${padZeros(fileIndex, 2)}-${collection.name}.json`;
7207
- fs28.writeFileSync(path26.join(fixturesDir, filename), JSON.stringify(items, null, 2));
7417
+ fs29.writeFileSync(path26.join(fixturesDir, filename), JSON.stringify(items, null, 2));
7208
7418
  writtenFiles.push(`${path26.join(DATA_DIR, "fixtures", filename)} (${items.length} records)`);
7209
7419
  fileIndex++;
7210
7420
  }
@@ -7352,7 +7562,7 @@ var load2 = new Command57("load").description("load seeds into the database").op
7352
7562
  );
7353
7563
 
7354
7564
  // src/commands/seeds/save.ts
7355
- import fs29 from "node:fs";
7565
+ import fs30 from "node:fs";
7356
7566
  import path27 from "node:path";
7357
7567
  import { Command as Command58 } from "commander";
7358
7568
  var padZeros2 = (num, length) => num.toString().padStart(length, "0");
@@ -7373,10 +7583,10 @@ var save = new Command58("save").description("save the current data as seeds").o
7373
7583
  if (existing.length > 0 && !opts.force) {
7374
7584
  throw new Error("Existing seed files found in data/seeds. Pass --force to overwrite.");
7375
7585
  }
7376
- fs29.mkdirSync(seedsPath, { recursive: true });
7586
+ fs30.mkdirSync(seedsPath, { recursive: true });
7377
7587
  if (opts.force) {
7378
- for (const file of fs29.readdirSync(seedsPath)) {
7379
- if (file.endsWith(".json")) fs29.unlinkSync(path27.join(seedsPath, file));
7588
+ for (const file of fs30.readdirSync(seedsPath)) {
7589
+ if (file.endsWith(".json")) fs30.unlinkSync(path27.join(seedsPath, file));
7380
7590
  }
7381
7591
  }
7382
7592
  const saved = [];
@@ -7411,7 +7621,7 @@ var save = new Command58("save").description("save the current data as seeds").o
7411
7621
  `${padZeros2(count, 2)}-${collectionName}.json`
7412
7622
  );
7413
7623
  const seedPath = path27.join(workspaceRootDir, relativeSeedPath);
7414
- fs29.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
7624
+ fs30.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
7415
7625
  saved.push(`${relativeSeedPath} (${filtered.length} records)`);
7416
7626
  count++;
7417
7627
  }
@@ -7623,10 +7833,10 @@ import { Command as Command65 } from "commander";
7623
7833
 
7624
7834
  // src/lib/migrate.ts
7625
7835
  import path28 from "node:path";
7626
- import process19 from "node:process";
7836
+ import process20 from "node:process";
7627
7837
  import { x as x2 } from "tinyexec";
7628
7838
  async function runPocketbaseMigrate(args) {
7629
- const cwd = process19.cwd();
7839
+ const cwd = process20.cwd();
7630
7840
  const { getBinaryPath } = await import("pocketbase-server");
7631
7841
  const binaryPath = getBinaryPath();
7632
7842
  await x2(
@@ -7683,13 +7893,13 @@ var down = new Command66("down").alias("rollback").description("revert the last
7683
7893
  );
7684
7894
 
7685
7895
  // src/commands/migrate/create.ts
7686
- import fs30 from "node:fs";
7896
+ import fs31 from "node:fs";
7687
7897
  import path29 from "node:path";
7688
- import process20 from "node:process";
7898
+ import process21 from "node:process";
7689
7899
  import { Command as Command67 } from "commander";
7690
7900
  var create2 = new Command67("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
7691
7901
  (name) => runCommand(async () => {
7692
- const cwd = process20.cwd();
7902
+ const cwd = process21.cwd();
7693
7903
  const before = listMigrationFiles(cwd);
7694
7904
  await runPocketbaseMigrate(["create", name]);
7695
7905
  const after = listMigrationFiles(cwd);
@@ -7706,18 +7916,18 @@ var create2 = new Command67("create").alias("new").description("create a new bla
7706
7916
  );
7707
7917
  function listMigrationFiles(cwd) {
7708
7918
  const dir = path29.join(cwd, MIGRATIONS_DIR);
7709
- if (!fs30.existsSync(dir)) return /* @__PURE__ */ new Set();
7710
- return new Set(fs30.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7919
+ if (!fs31.existsSync(dir)) return /* @__PURE__ */ new Set();
7920
+ return new Set(fs31.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7711
7921
  }
7712
7922
 
7713
7923
  // src/commands/migrate/collections.ts
7714
- import fs31 from "node:fs";
7924
+ import fs32 from "node:fs";
7715
7925
  import path30 from "node:path";
7716
- import process21 from "node:process";
7926
+ import process22 from "node:process";
7717
7927
  import { Command as Command68 } from "commander";
7718
7928
  var collections = new Command68("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
7719
7929
  () => runCommand(async () => {
7720
- const cwd = process21.cwd();
7930
+ const cwd = process22.cwd();
7721
7931
  const before = listMigrationFiles2(cwd);
7722
7932
  await runPocketbaseMigrate(["collections"]);
7723
7933
  const after = listMigrationFiles2(cwd);
@@ -7741,8 +7951,8 @@ var collections = new Command68("collections").alias("snapshot").description("sn
7741
7951
  );
7742
7952
  function listMigrationFiles2(cwd) {
7743
7953
  const dir = path30.join(cwd, MIGRATIONS_DIR);
7744
- if (!fs31.existsSync(dir)) return /* @__PURE__ */ new Set();
7745
- return new Set(fs31.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7954
+ if (!fs32.existsSync(dir)) return /* @__PURE__ */ new Set();
7955
+ return new Set(fs32.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
7746
7956
  }
7747
7957
 
7748
7958
  // src/commands/migrate/history-sync.ts
@@ -7761,9 +7971,9 @@ var historySync = new Command69("history-sync").description("drop _migrations ro
7761
7971
  var migrate = new Command70("migrate").description("manage database migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations.")).addCommand(up).addCommand(down).addCommand(create2).addCommand(collections).addCommand(historySync);
7762
7972
 
7763
7973
  // src/commands/dev.ts
7764
- import fs32 from "node:fs";
7974
+ import fs33 from "node:fs";
7765
7975
  import path32 from "node:path";
7766
- import process23 from "node:process";
7976
+ import process24 from "node:process";
7767
7977
  import { performance } from "node:perf_hooks";
7768
7978
  import { Command as Command71, InvalidArgumentError as InvalidArgumentError4 } from "commander";
7769
7979
  import pc15 from "picocolors";
@@ -7771,7 +7981,7 @@ import PocketBase4 from "pocketbase";
7771
7981
 
7772
7982
  // src/lib/vite.ts
7773
7983
  import path31 from "node:path";
7774
- import process22 from "node:process";
7984
+ import process23 from "node:process";
7775
7985
  import { createRequire as createRequire2 } from "node:module";
7776
7986
  import { pathToFileURL as pathToFileURL2 } from "node:url";
7777
7987
  import pc14 from "picocolors";
@@ -7795,13 +8005,13 @@ function resolveProjectVite(cwd) {
7795
8005
  return null;
7796
8006
  }
7797
8007
  }
7798
- async function loadVite(cwd = process22.cwd()) {
8008
+ async function loadVite(cwd = process23.cwd()) {
7799
8009
  const entry = resolveProjectVite(cwd);
7800
8010
  const vite = entry ? await import(pathToFileURL2(entry).href) : await import("vite");
7801
8011
  const error = viteVersionError(vite.version);
7802
8012
  if (error) {
7803
8013
  console.error(`${pc14.redBright("\u2717")} ${error}`);
7804
- process22.exit(1);
8014
+ process23.exit(1);
7805
8015
  }
7806
8016
  return vite;
7807
8017
  }
@@ -7815,18 +8025,18 @@ function parsePort(value) {
7815
8025
  return port;
7816
8026
  }
7817
8027
  var dev = new Command71("dev").description("start the development server").option("--open [path]", "open the app in a browser once the server is ready").option("--host [host]", "expose the server on the network").option("--port <port>", "port to listen on", parsePort).option("--strictPort", "exit if the port is already in use instead of taking the next one").option("--cors", "enable CORS").option("--force", "re-bundle dependencies, ignoring the optimizer cache").configureHelp(helpConfig).action(async (options) => {
7818
- const cwd = process23.cwd();
7819
- process23.env.VELA_DATA_DIR ??= localDataDir(cwd);
8028
+ const cwd = process24.cwd();
8029
+ process24.env.VELA_DATA_DIR ??= localDataDir(cwd);
7820
8030
  const startTime = performance.now();
7821
8031
  const { createServer, version } = await loadVite(cwd);
7822
8032
  const viteMetadataDir = path32.join(cwd, "node_modules", ".vite");
7823
8033
  const viteMetadataFile = path32.join(viteMetadataDir, "_pocketbase_metadata.json");
7824
8034
  let pbProc;
7825
8035
  const backend3 = hasBackend(cwd);
7826
- const needsStart = backend3 && !process23.env.POCKETBASE_URL;
8036
+ const needsStart = backend3 && !process24.env.POCKETBASE_URL;
7827
8037
  const cleanup = () => {
7828
8038
  if (pbProc?.pid) pbProc.kill();
7829
- if (fs32.existsSync(viteMetadataFile)) fs32.rmSync(viteMetadataFile);
8039
+ if (fs33.existsSync(viteMetadataFile)) fs33.rmSync(viteMetadataFile);
7830
8040
  };
7831
8041
  if (needsStart) {
7832
8042
  const dataDir2 = path32.join(cwd, DATA_DIR);
@@ -7838,15 +8048,15 @@ var dev = new Command71("dev").description("start the development server").optio
7838
8048
  stdio: "pipe"
7839
8049
  });
7840
8050
  pbProc = started.proc;
7841
- process23.env.POCKETBASE_URL = started.url;
7842
- pbProc.stdout?.pipe(process23.stdout);
7843
- pbProc.stderr?.pipe(process23.stderr);
8051
+ process24.env.POCKETBASE_URL = started.url;
8052
+ pbProc.stdout?.pipe(process24.stdout);
8053
+ pbProc.stderr?.pipe(process24.stderr);
7844
8054
  pbProc.on("error", (err) => console.error("PocketBase error:", err));
7845
8055
  pbProc.on("exit", (code) => console.log(`PocketBase exited with code ${code}`));
7846
- process23.on("exit", cleanup);
7847
- process23.on("SIGINT", () => {
8056
+ process24.on("exit", cleanup);
8057
+ process24.on("SIGINT", () => {
7848
8058
  cleanup();
7849
- process23.exit(0);
8059
+ process24.exit(0);
7850
8060
  });
7851
8061
  }
7852
8062
  const serverOptions = {};
@@ -7864,25 +8074,25 @@ var dev = new Command71("dev").description("start the development server").optio
7864
8074
  if (!backend3) return;
7865
8075
  const { address, port: vitePort } = server.httpServer.address();
7866
8076
  const viteHost = address === "::1" ? "localhost" : address;
7867
- await fs32.promises.mkdir(viteMetadataDir, { recursive: true });
7868
- await fs32.promises.writeFile(
8077
+ await fs33.promises.mkdir(viteMetadataDir, { recursive: true });
8078
+ await fs33.promises.writeFile(
7869
8079
  viteMetadataFile,
7870
8080
  JSON.stringify({
7871
- pocketbaseUrl: process23.env.POCKETBASE_URL,
8081
+ pocketbaseUrl: process24.env.POCKETBASE_URL,
7872
8082
  vitePort,
7873
8083
  viteHost
7874
8084
  })
7875
8085
  );
7876
- const pb = new PocketBase4(process23.env.POCKETBASE_URL);
8086
+ const pb = new PocketBase4(process24.env.POCKETBASE_URL);
7877
8087
  await pb.collection("_superusers").authWithPassword(
7878
- process23.env.POCKETBASE_SUPERUSER_EMAIL,
7879
- process23.env.POCKETBASE_SUPERUSER_PASSWORD
8088
+ process24.env.POCKETBASE_SUPERUSER_EMAIL,
8089
+ process24.env.POCKETBASE_SUPERUSER_PASSWORD
7880
8090
  );
7881
8091
  await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
7882
8092
  await startWatchingTypes(cwd, pb);
7883
8093
  });
7884
8094
  await server.listen();
7885
- const hasExistingLogs = process23.stdout.bytesWritten > 0 || process23.stderr.bytesWritten > 0;
8095
+ const hasExistingLogs = process24.stdout.bytesWritten > 0 || process24.stderr.bytesWritten > 0;
7886
8096
  const startupDurationString = pc15.dim(
7887
8097
  `ready in ${pc15.reset(pc15.bold(Math.ceil(performance.now() - startTime)))} ms`
7888
8098
  );
@@ -7906,9 +8116,9 @@ async function startWatchingTypes(cwd, pb) {
7906
8116
  void (async () => {
7907
8117
  for (; ; ) {
7908
8118
  try {
7909
- await fs32.promises.mkdir(pocketbaseDir, { recursive: true });
7910
- for await (const event of fs32.promises.watch(pocketbaseDir)) {
7911
- if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs32.existsSync(pocketbaseTypes)) {
8119
+ await fs33.promises.mkdir(pocketbaseDir, { recursive: true });
8120
+ for await (const event of fs33.promises.watch(pocketbaseDir)) {
8121
+ if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs33.existsSync(pocketbaseTypes)) {
7912
8122
  setTimeout(regenerate, 100);
7913
8123
  }
7914
8124
  }
@@ -7920,9 +8130,9 @@ async function startWatchingTypes(cwd, pb) {
7920
8130
  }
7921
8131
 
7922
8132
  // src/commands/build.ts
7923
- import fs33 from "node:fs";
8133
+ import fs34 from "node:fs";
7924
8134
  import path33 from "node:path";
7925
- import process25 from "node:process";
8135
+ import process26 from "node:process";
7926
8136
  import { Command as Command72 } from "commander";
7927
8137
  import * as p40 from "@clack/prompts";
7928
8138
  import pc16 from "picocolors";
@@ -7937,10 +8147,10 @@ function applyBuildEnv(cwd, env2 = process.env) {
7937
8147
  }
7938
8148
 
7939
8149
  // src/lib/origin.ts
7940
- import process24 from "node:process";
8150
+ import process25 from "node:process";
7941
8151
  function resolveOrigin(workspaceRootDir, envTag, config = {}) {
7942
8152
  return normalizeOrigin(
7943
- process24.env.VELA_ORIGIN ?? readBinding(workspaceRootDir, envTag)?.domain ?? config.deploy?.domain
8153
+ process25.env.VELA_ORIGIN ?? readBinding(workspaceRootDir, envTag)?.domain ?? config.deploy?.domain
7944
8154
  );
7945
8155
  }
7946
8156
  function normalizeOrigin(value) {
@@ -7961,12 +8171,12 @@ function splitHosts(value) {
7961
8171
  // src/commands/build.ts
7962
8172
  var PRERENDERED_DIR = path33.join(".svelte-kit", "output", "prerendered");
7963
8173
  var build = new Command72("build").description("build the app").configureHelp(helpConfig).option("-t, --target <target>", "which copy of the app to build for", PRODUCTION_TARGET).action(async (options) => {
7964
- const cwd = process25.cwd();
8174
+ const cwd = process26.cwd();
7965
8175
  applyBuildEnv(cwd);
7966
8176
  const origin = await originForBuild(cwd, options.target);
7967
- if (origin) process25.env.VELA_ORIGIN = origin;
8177
+ if (origin) process26.env.VELA_ORIGIN = origin;
7968
8178
  let pbProc;
7969
- const needsStart = hasBackend(cwd) && !process25.env.POCKETBASE_URL;
8179
+ const needsStart = hasBackend(cwd) && !process26.env.POCKETBASE_URL;
7970
8180
  const cleanup = () => {
7971
8181
  if (pbProc?.pid) pbProc.kill();
7972
8182
  };
@@ -7980,11 +8190,11 @@ var build = new Command72("build").description("build the app").configureHelp(he
7980
8190
  dev: true
7981
8191
  });
7982
8192
  pbProc = started.proc;
7983
- process25.env.POCKETBASE_URL = started.url;
7984
- process25.on("exit", cleanup);
7985
- process25.on("SIGINT", () => {
8193
+ process26.env.POCKETBASE_URL = started.url;
8194
+ process26.on("exit", cleanup);
8195
+ process26.on("SIGINT", () => {
7986
8196
  cleanup();
7987
- process25.exit(0);
8197
+ process26.exit(0);
7988
8198
  });
7989
8199
  }
7990
8200
  try {
@@ -8016,7 +8226,7 @@ async function originForBuild(cwd, target) {
8016
8226
  }
8017
8227
  function warnIfPrerendered(cwd) {
8018
8228
  const dir = path33.join(cwd, PRERENDERED_DIR);
8019
- if (!fs33.existsSync(dir) || fs33.readdirSync(dir).length === 0) return;
8229
+ if (!fs34.existsSync(dir) || fs34.readdirSync(dir).length === 0) return;
8020
8230
  p40.log.warn(
8021
8231
  `Prerendered pages were built with no domain configured, so their canonical
8022
8232
  links point at SvelteKit's placeholder host rather than at this site.
@@ -8027,16 +8237,16 @@ Set one with ${pc16.cyan("vela deploy --domain example.com")}, or pass ${pc16.cy
8027
8237
 
8028
8238
  // src/commands/preview.ts
8029
8239
  import path34 from "node:path";
8030
- import process26 from "node:process";
8240
+ import process27 from "node:process";
8031
8241
  import { Command as Command73 } from "commander";
8032
8242
  import { x as x4 } from "tinyexec";
8033
8243
  import { detect as detect6 } from "package-manager-detector";
8034
8244
  import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
8035
8245
  var preview = new Command73("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
8036
- const cwd = process26.cwd();
8037
- process26.env.VELA_DATA_DIR ??= localDataDir(cwd);
8246
+ const cwd = process27.cwd();
8247
+ process27.env.VELA_DATA_DIR ??= localDataDir(cwd);
8038
8248
  let pbProc;
8039
- const needsStart = hasBackend(cwd) && !process26.env.POCKETBASE_URL;
8249
+ const needsStart = hasBackend(cwd) && !process27.env.POCKETBASE_URL;
8040
8250
  const cleanup = () => {
8041
8251
  if (pbProc?.pid) pbProc.kill();
8042
8252
  };
@@ -8049,11 +8259,11 @@ var preview = new Command73("preview").description("preview the built app").conf
8049
8259
  dev: true
8050
8260
  });
8051
8261
  pbProc = started.proc;
8052
- process26.env.POCKETBASE_URL = started.url;
8053
- process26.on("exit", cleanup);
8054
- process26.on("SIGINT", () => {
8262
+ process27.env.POCKETBASE_URL = started.url;
8263
+ process27.on("exit", cleanup);
8264
+ process27.on("SIGINT", () => {
8055
8265
  cleanup();
8056
- process26.exit(0);
8266
+ process27.exit(0);
8057
8267
  });
8058
8268
  }
8059
8269
  try {
@@ -8138,24 +8348,24 @@ var provision = addSshOptions(
8138
8348
  );
8139
8349
 
8140
8350
  // src/commands/deploy.ts
8141
- import path37 from "node:path";
8142
- import fs35 from "node:fs";
8351
+ import path38 from "node:path";
8352
+ import fs37 from "node:fs";
8143
8353
  import { Command as Command76, Option as Option2 } from "commander";
8144
8354
  import * as p42 from "@clack/prompts";
8145
8355
  import pc18 from "picocolors";
8146
8356
  import * as v8 from "valibot";
8147
8357
 
8148
8358
  // src/lib/pocketbase-settings.ts
8149
- import fs34 from "node:fs";
8359
+ import fs35 from "node:fs";
8150
8360
  import path36 from "node:path";
8151
- import process27 from "node:process";
8361
+ import process28 from "node:process";
8152
8362
  import PocketBase5 from "pocketbase";
8153
8363
  var COPIED_KEYS = ["appName", "senderName", "senderAddress"];
8154
8364
  async function readLocalMeta(cwd) {
8155
8365
  const dataDir2 = path36.join(cwd, DATA_DIR);
8156
- if (!fs34.existsSync(dataDir2)) return null;
8157
- const email3 = process27.env.POCKETBASE_SUPERUSER_EMAIL;
8158
- const password11 = process27.env.POCKETBASE_SUPERUSER_PASSWORD;
8366
+ if (!fs35.existsSync(dataDir2)) return null;
8367
+ const email3 = process28.env.POCKETBASE_SUPERUSER_EMAIL;
8368
+ const password11 = process28.env.POCKETBASE_SUPERUSER_PASSWORD;
8159
8369
  if (!email3 || !password11) return null;
8160
8370
  let proc;
8161
8371
  try {
@@ -8203,11 +8413,278 @@ async function seedRemoteMeta(session, instance, local, appURL) {
8203
8413
  return Object.keys(patch);
8204
8414
  }
8205
8415
 
8416
+ // src/lib/adapter.ts
8417
+ import fs36 from "node:fs";
8418
+ import path37 from "node:path";
8419
+ import {
8420
+ Project as Project2,
8421
+ QuoteKind as QuoteKind2,
8422
+ SyntaxKind as SyntaxKind2
8423
+ } from "ts-morph";
8424
+ import { detect as detect7 } from "package-manager-detector";
8425
+ var ADAPTER_NODE = "@sveltejs/adapter-node";
8426
+ var ADAPTER_AUTO = "@sveltejs/adapter-auto";
8427
+ var ADAPTER_STATIC = "@sveltejs/adapter-static";
8428
+ var ADAPTER_NODE_RANGE = "^5.5.7";
8429
+ var ADAPTER_SNIPPET = `import adapter from '${ADAPTER_NODE}';
8430
+
8431
+ // ...
8432
+ adapter: adapter()`;
8433
+ var AdapterError = class extends Error {
8434
+ constructor(message, snippet = ADAPTER_SNIPPET) {
8435
+ super(message);
8436
+ this.snippet = snippet;
8437
+ this.name = "AdapterError";
8438
+ }
8439
+ snippet;
8440
+ };
8441
+ function newProject() {
8442
+ return new Project2({
8443
+ compilerOptions: { allowJs: true },
8444
+ manipulationSettings: { quoteKind: QuoteKind2.Single }
8445
+ });
8446
+ }
8447
+ function getDefaultExportObject(sourceFile) {
8448
+ const exported = sourceFile.getExportAssignment((ea) => !ea.isExportEquals())?.getExpression();
8449
+ if (exported?.getKind() === SyntaxKind2.ObjectLiteralExpression) {
8450
+ return exported;
8451
+ }
8452
+ if (exported?.getKind() === SyntaxKind2.Identifier) {
8453
+ const init = sourceFile.getVariableDeclaration(exported.getText())?.getInitializer();
8454
+ if (init?.getKind() === SyntaxKind2.ObjectLiteralExpression) {
8455
+ return init;
8456
+ }
8457
+ }
8458
+ return null;
8459
+ }
8460
+ function resolveKitTarget(root) {
8461
+ const vite = inspectViteSveltekit(root);
8462
+ if (vite?.inlineArg) {
8463
+ return {
8464
+ sourceFile: vite.sourceFile,
8465
+ filePath: vite.filePath,
8466
+ container: "vite-inline",
8467
+ kit: vite.inlineArg,
8468
+ created: false,
8469
+ originalText: vite.sourceFile.getFullText()
8470
+ };
8471
+ }
8472
+ const sveltePath = probeFirstExisting(root, SVELTE_CONFIG_CANDIDATES);
8473
+ if (sveltePath) {
8474
+ const name = path37.basename(sveltePath);
8475
+ if (sveltePath.endsWith(".cjs")) {
8476
+ throw new AdapterError(`${name} is CommonJS, which vela does not edit.`);
8477
+ }
8478
+ const sourceFile = newProject().addSourceFileAtPath(sveltePath);
8479
+ const config = getDefaultExportObject(sourceFile);
8480
+ const kit = config && getOrCreateObjectLiteralProperty(config, "kit", "{}");
8481
+ if (!kit) {
8482
+ throw new AdapterError(`${name} has a shape vela does not understand.`);
8483
+ }
8484
+ return {
8485
+ sourceFile,
8486
+ filePath: sveltePath,
8487
+ container: "svelte-config",
8488
+ kit,
8489
+ created: false,
8490
+ originalText: sourceFile.getFullText()
8491
+ };
8492
+ }
8493
+ if (vite?.sveltekitCall) {
8494
+ const name = path37.basename(vite.filePath);
8495
+ if (vite.nonObjectArg) {
8496
+ throw new AdapterError(`${name} passes sveltekit() something other than an object literal.`);
8497
+ }
8498
+ const originalText = vite.sourceFile.getFullText();
8499
+ const kit = createSveltekitArg(vite);
8500
+ if (!kit)
8501
+ throw new AdapterError(`${name} has a sveltekit() call vela cannot add an argument to.`);
8502
+ return {
8503
+ sourceFile: vite.sourceFile,
8504
+ filePath: vite.filePath,
8505
+ container: "vite-inline",
8506
+ kit,
8507
+ created: true,
8508
+ originalText
8509
+ };
8510
+ }
8511
+ throw new AdapterError(
8512
+ `No svelte.config or vite.config with a sveltekit() plugin found in ${root}.`
8513
+ );
8514
+ }
8515
+ function classify(specifier) {
8516
+ if (specifier === ADAPTER_NODE) return "node";
8517
+ if (specifier === ADAPTER_AUTO) return "auto";
8518
+ if (specifier === ADAPTER_STATIC) return "static";
8519
+ return "other";
8520
+ }
8521
+ function importOf(sourceFile, identifier) {
8522
+ return sourceFile.getImportDeclarations().find((decl) => decl.getDefaultImport()?.getText() === identifier);
8523
+ }
8524
+ function inspectAdapter(target) {
8525
+ const base2 = { file: target.filePath, container: target.container };
8526
+ const prop = target.kit.getProperty("adapter");
8527
+ if (!prop) return { info: { ...base2, kind: "none" } };
8528
+ if (prop.getKind() !== SyntaxKind2.PropertyAssignment) {
8529
+ return { info: { ...base2, kind: "other" } };
8530
+ }
8531
+ const init = prop.asKindOrThrow(SyntaxKind2.PropertyAssignment).getInitializer();
8532
+ if (!init || init.getKind() !== SyntaxKind2.CallExpression) {
8533
+ return { info: { ...base2, kind: "other" } };
8534
+ }
8535
+ const callee = init.asKindOrThrow(SyntaxKind2.CallExpression).getExpression();
8536
+ if (callee.getKind() !== SyntaxKind2.Identifier) {
8537
+ return { info: { ...base2, kind: "other" } };
8538
+ }
8539
+ const importDecl = importOf(target.sourceFile, callee.getText());
8540
+ if (!importDecl) return { info: { ...base2, kind: "other" } };
8541
+ const specifier = importDecl.getModuleSpecifierValue();
8542
+ return { info: { ...base2, kind: classify(specifier), specifier }, importDecl };
8543
+ }
8544
+ async function ensureNodeAdapter(root, { install = true } = {}) {
8545
+ const target = resolveKitTarget(root);
8546
+ const { info, importDecl } = inspectAdapter(target);
8547
+ const name = path37.basename(target.filePath);
8548
+ const outcome = {
8549
+ previous: info.kind,
8550
+ removedDeps: [],
8551
+ packageJsonChanged: false
8552
+ };
8553
+ switch (info.kind) {
8554
+ case "node":
8555
+ break;
8556
+ case "static":
8557
+ throw new AdapterError(
8558
+ `This project builds a static site with ${ADAPTER_STATIC} (${name}).
8559
+
8560
+ vela deploy runs the app as a Node server, which needs ${ADAPTER_NODE}. Switch
8561
+ the adapter to deploy it here, or host the static output elsewhere.`
8562
+ );
8563
+ case "other":
8564
+ throw new AdapterError(
8565
+ `This project's adapter${info.specifier ? ` (${info.specifier})` : ""} in ${name} is not one
8566
+ vela will change for you.
8567
+
8568
+ vela deploy runs the app as a Node server, which needs ${ADAPTER_NODE}:`
8569
+ );
8570
+ case "auto":
8571
+ switchImport(target, importDecl);
8572
+ outcome.configFile = name;
8573
+ break;
8574
+ case "none":
8575
+ addAdapter(target);
8576
+ outcome.configFile = name;
8577
+ break;
8578
+ }
8579
+ if (outcome.configFile) saveTarget(target);
8580
+ const pkgPath = path37.join(root, "package.json");
8581
+ if (fs36.existsSync(pkgPath)) {
8582
+ const pkg = readPackageJson(pkgPath);
8583
+ const { changed, removed } = adoptNodeAdapter(pkg);
8584
+ if (changed) {
8585
+ writePackageJson(pkgPath, pkg);
8586
+ outcome.packageJsonChanged = true;
8587
+ outcome.removedDeps = removed;
8588
+ }
8589
+ }
8590
+ if (outcome.packageJsonChanged && install) {
8591
+ outcome.installedWith = await installAdapterDependencies(root);
8592
+ }
8593
+ return outcome;
8594
+ }
8595
+ async function installAdapterDependencies(root) {
8596
+ const agent = await packageManager(root);
8597
+ const ok = await installDependencies(agent, root, { exitOnFailure: false });
8598
+ if (!ok) {
8599
+ throw new AdapterError(
8600
+ `Installing ${ADAPTER_NODE} with ${agent} failed.
8601
+
8602
+ The config and package.json are already updated: run \`${agent} install\`, then deploy again.`,
8603
+ ""
8604
+ );
8605
+ }
8606
+ return agent;
8607
+ }
8608
+ function switchImport(target, importDecl) {
8609
+ importDecl.setModuleSpecifier(ADAPTER_NODE);
8610
+ const prop = target.kit.getPropertyOrThrow("adapter").asKindOrThrow(SyntaxKind2.PropertyAssignment);
8611
+ const call = prop.getInitializerIfKindOrThrow(SyntaxKind2.CallExpression);
8612
+ for (let i = call.getArguments().length - 1; i >= 0; i--) call.removeArgument(i);
8613
+ dropAdapterAutoComments(target);
8614
+ }
8615
+ var ADAPTER_AUTO_COMMENT = /adapter-auto|svelte\.dev\/docs\/kit\/adapters|switch out the adapter/;
8616
+ function dropAdapterAutoComments(target) {
8617
+ const prop = target.kit.getPropertyOrThrow("adapter");
8618
+ const ranges = prop.getLeadingCommentRanges();
8619
+ if (ranges.length === 0 || !ranges.every((r) => ADAPTER_AUTO_COMMENT.test(r.getText()))) return;
8620
+ const fullStart = prop.getFullStart();
8621
+ const start = prop.getStart();
8622
+ const trivia = target.sourceFile.getFullText().slice(fullStart, start);
8623
+ const lastNewline = trivia.lastIndexOf("\n");
8624
+ if (lastNewline === -1) return;
8625
+ const indent = trivia.slice(lastNewline + 1);
8626
+ target.sourceFile.replaceText([fullStart, start], `
8627
+ ${indent}`);
8628
+ }
8629
+ function addAdapter(target) {
8630
+ const taken = importOf(target.sourceFile, "adapter") ?? target.sourceFile.getVariableDeclaration("adapter");
8631
+ if (taken) {
8632
+ throw new AdapterError(
8633
+ `${path37.basename(target.filePath)} already binds the name \`adapter\` to something that is not the SvelteKit adapter.`
8634
+ );
8635
+ }
8636
+ target.sourceFile.addImportDeclaration({
8637
+ defaultImport: "adapter",
8638
+ moduleSpecifier: ADAPTER_NODE
8639
+ });
8640
+ target.kit.addPropertyAssignment({ name: "adapter", initializer: "adapter()" });
8641
+ }
8642
+ function saveTarget(target) {
8643
+ if (target.created) target.sourceFile.formatText();
8644
+ if (target.sourceFile.getFullText() === target.originalText) return;
8645
+ target.sourceFile.saveSync();
8646
+ }
8647
+ function adoptNodeAdapter(pkg) {
8648
+ const removed = [];
8649
+ let changed = false;
8650
+ for (const kind of ["dependencies", "devDependencies"]) {
8651
+ const deps = pkg[kind];
8652
+ if (deps && ADAPTER_AUTO in deps) {
8653
+ delete deps[ADAPTER_AUTO];
8654
+ removed.push(ADAPTER_AUTO);
8655
+ changed = true;
8656
+ }
8657
+ }
8658
+ if (!pkg.dependencies?.[ADAPTER_NODE] && !pkg.devDependencies?.[ADAPTER_NODE]) {
8659
+ pkg.devDependencies = sortKeys({ ...pkg.devDependencies, [ADAPTER_NODE]: ADAPTER_NODE_RANGE });
8660
+ changed = true;
8661
+ }
8662
+ return { changed, removed: [...new Set(removed)] };
8663
+ }
8664
+ function lockfileFor(agent) {
8665
+ switch (agent) {
8666
+ case "pnpm":
8667
+ return "pnpm-lock.yaml";
8668
+ case "yarn":
8669
+ return "yarn.lock";
8670
+ case "bun":
8671
+ return "bun.lock";
8672
+ case "deno":
8673
+ return "deno.lock";
8674
+ default:
8675
+ return "package-lock.json";
8676
+ }
8677
+ }
8678
+ async function packageManager(root) {
8679
+ return (await detect7({ cwd: root }))?.name ?? getUserAgent() ?? "npm";
8680
+ }
8681
+
8206
8682
  // src/commands/deploy.ts
8207
8683
  var OptionsSchema3 = v8.object({
8208
8684
  ...SSH_OPTION_SCHEMA,
8209
8685
  env: v8.optional(v8.string()),
8210
8686
  project: v8.optional(v8.string()),
8687
+ ...LOCK_WAIT_SCHEMA,
8211
8688
  remoteDb: v8.optional(v8.boolean()),
8212
8689
  domain: v8.optional(v8.string()),
8213
8690
  healthPath: v8.optional(v8.string()),
@@ -8215,9 +8692,11 @@ var OptionsSchema3 = v8.object({
8215
8692
  pbVersion: v8.optional(v8.string()),
8216
8693
  build: v8.optional(v8.boolean())
8217
8694
  });
8218
- var deploy = addTargetOptions(
8219
- new Command76("deploy").description("deploy the app").configureHelp(helpConfig),
8220
- "production"
8695
+ var deploy = addLockWaitOption(
8696
+ addTargetOptions(
8697
+ new Command76("deploy").description("deploy the app").configureHelp(helpConfig),
8698
+ "production"
8699
+ )
8221
8700
  ).option("--project <name>", "override the project name").option("--domain <hosts>", "hostname(s) to serve on, comma separated").option("--health-path <path>", "path the health check requests").option("--keep <count>", "how many old releases to keep on the server").option("--pb-version <version>", "PocketBase version to run").option("--no-build", "deploy the existing build output without rebuilding").addOption(
8222
8701
  new Option2(
8223
8702
  "--remote-db",
@@ -8231,8 +8710,11 @@ var deploy = addTargetOptions(
8231
8710
  (raw) => runCommand(async () => {
8232
8711
  const options = parseOptions(OptionsSchema3, raw);
8233
8712
  const backend3 = hasBackend();
8234
- const release = releaseId();
8235
8713
  p42.intro(pc18.bgCyan(pc18.black(" vela deploy ")));
8714
+ if (options.build !== false) {
8715
+ const { workspaceRootDir } = await getWorkspace();
8716
+ await prepareAdapter(workspaceRootDir);
8717
+ }
8236
8718
  await withTarget(
8237
8719
  raw,
8238
8720
  {
@@ -8242,10 +8724,16 @@ var deploy = addTargetOptions(
8242
8724
  `${pc18.cyan(ctx.appName)} ${pc18.dim("\u2192")} ${pc18.cyan(ctx.targetName)} ${pc18.dim(`(${ctx.server})`)}`
8243
8725
  );
8244
8726
  const [existing] = await readInstanceStates(session, instance);
8245
- const isPreview = ctx.target.kind === "preview";
8246
- const configured = options.domain ?? (isPreview ? existing?.domain : ctx.binding.domain ?? config.deploy?.domain ?? existing?.domain) ?? "";
8727
+ const release = releaseId(await serverTimeOrLocal(session));
8728
+ const isPreview2 = ctx.target.kind === "preview";
8729
+ const configured = options.domain ?? (isPreview2 ? existing?.domain : ctx.binding.domain ?? config.deploy?.domain ?? existing?.domain) ?? "";
8247
8730
  const askedForRemoteDb = options.remoteDb ?? config.deploy?.buildAgainstRemote;
8248
- const remoteDb = askedForRemoteDb ?? Boolean(existing?.pbPort);
8731
+ const remoteDb = askedForRemoteDb ?? instanceHasBackend(existing);
8732
+ if (existing && instanceHasBackend(existing) !== backend3) {
8733
+ p42.log.info(
8734
+ backend3 ? `${pc18.cyan(ctx.targetName)} was deployed without a backend before. This deploy adds PocketBase.` : `${pc18.cyan(ctx.targetName)} was deployed with a backend before. This deploy removes PocketBase; its database stays on the server.`
8735
+ );
8736
+ }
8249
8737
  const sha = await gitSha(workspaceRootDir);
8250
8738
  const reporter = createDeployReporter(workspaceRootDir);
8251
8739
  const server = await reporter.identifyServer(session);
@@ -8265,17 +8753,17 @@ var deploy = addTargetOptions(
8265
8753
  const managed = managedHosts.join(",");
8266
8754
  const primaryHost = directHosts[0] ?? managedHosts[0] ?? "";
8267
8755
  const primaryUrl = started?.environment.primaryUrl || normalizeOrigin(primaryHost) || "";
8268
- if (isPreview && !primaryHost) {
8269
- throw new Error(
8270
- `Nothing routes to this preview.
8756
+ let result = null;
8757
+ try {
8758
+ if (isPreview2 && !primaryHost) {
8759
+ throw new Error(
8760
+ `Nothing routes to this preview.
8271
8761
 
8272
8762
  Previews get a free velastack.app hostname from velastack.dev: ${pc18.cyan("vela link")} the
8273
8763
  project and ${pc18.cyan("vela login")} (or set ${pc18.cyan("VELA_API_KEY")}). Or pass ${pc18.cyan("--domain <host>")}
8274
8764
  with DNS of your own pointing at ${ctx.server}.`
8275
- );
8276
- }
8277
- let result = null;
8278
- try {
8765
+ );
8766
+ }
8279
8767
  if (options.build !== false) {
8280
8768
  const origin = normalizeOrigin(process.env.VELA_ORIGIN ?? primaryUrl);
8281
8769
  let buildEnv = origin ? { VELA_ORIGIN: origin } : {};
@@ -8333,7 +8821,8 @@ ${pc18.dim(String(err))}`
8333
8821
  "--pb-version",
8334
8822
  options.pbVersion ?? config.deploy?.pocketbaseVersion ?? pocketbaseVersion(),
8335
8823
  "--git-sha",
8336
- sha
8824
+ sha,
8825
+ ...lockWaitArgs(options.lockWait)
8337
8826
  ],
8338
8827
  stream: true
8339
8828
  });
@@ -8347,7 +8836,7 @@ ${pc18.dim(String(err))}`
8347
8836
  writeBinding(
8348
8837
  workspaceRootDir,
8349
8838
  bindingKey(ctx.target),
8350
- isPreview ? { server: ctx.server } : { server: ctx.server, domain: domain || void 0 }
8839
+ isPreview2 ? { server: ctx.server } : { server: ctx.server, domain: domain || void 0 }
8351
8840
  );
8352
8841
  if (!existing) await reportEmptyEnvironment(session, instance, workspaceRootDir);
8353
8842
  const url = result?.url ?? "";
@@ -8400,6 +8889,38 @@ or ${pc18.cyan("vela link")} it to get a free velastack.app hostname.`) + `.`
8400
8889
  p42.outro(`${pc18.cyan("vela status")} to see what is running`);
8401
8890
  }, "Failed to deploy.")
8402
8891
  );
8892
+ async function prepareAdapter(workspaceRootDir) {
8893
+ const rethrow = (err) => {
8894
+ if (err instanceof AdapterError) {
8895
+ throw new Error(err.snippet ? `${err.message}
8896
+
8897
+ ${pc18.cyan(err.snippet)}` : err.message);
8898
+ }
8899
+ throw err;
8900
+ };
8901
+ const outcome = await ensureNodeAdapter(workspaceRootDir, { install: false }).catch(rethrow);
8902
+ if (!outcome.configFile && !outcome.packageJsonChanged) return;
8903
+ const changed = [
8904
+ outcome.configFile,
8905
+ outcome.packageJsonChanged ? "package.json" : void 0
8906
+ ].filter((f) => Boolean(f));
8907
+ const why = outcome.previous === "auto" ? `${ADAPTER_AUTO} builds nothing for a server of your own` : outcome.previous === "none" ? "No adapter was configured" : `${ADAPTER_NODE} was configured but not in package.json`;
8908
+ p42.log.step(`Switching the adapter to ${pc18.cyan(ADAPTER_NODE)}`);
8909
+ p42.log.info(
8910
+ `${why}, and vela deploy runs the app as a Node server.
8911
+
8912
+ Changed ${changed.join(", ")}` + (outcome.removedDeps.length ? `
8913
+ Removed ${outcome.removedDeps.join(", ")}` : "")
8914
+ );
8915
+ if (outcome.packageJsonChanged) {
8916
+ try {
8917
+ changed.push(lockfileFor(await installAdapterDependencies(workspaceRootDir)));
8918
+ } catch (err) {
8919
+ rethrow(err);
8920
+ }
8921
+ }
8922
+ p42.log.warn(`Commit ${changed.join(", ")} so every deploy builds the same way.`);
8923
+ }
8403
8924
  async function reportAppURLDrift(session, instance, domain) {
8404
8925
  const expected = normalizeOrigin(domain);
8405
8926
  if (!expected) return;
@@ -8435,10 +8956,10 @@ Set them in the admin panel instead. ${pc18.dim(String(err))}`
8435
8956
  }
8436
8957
  }
8437
8958
  async function openDatabaseTunnel(session, instance, state) {
8438
- const pbPort = state?.pbPort;
8959
+ const pbPort = instanceHasBackend(state) ? state?.pbPort : void 0;
8439
8960
  if (!pbPort) {
8440
8961
  throw new Error(
8441
- `--remote-db needs an existing deployment to build against, and ${instance} has not been deployed yet.
8962
+ `--remote-db needs a deployed database to build against, and ${instance} ${state ? "has no backend" : "has not been deployed yet"}.
8442
8963
 
8443
8964
  Deploy once without it, then turn it on.`
8444
8965
  );
@@ -8498,7 +9019,7 @@ async function superuserAuthenticates(port, identity, password11) {
8498
9019
  }
8499
9020
  async function uploadRelease(session, instance, release, entries) {
8500
9021
  const dir = remotePaths.release(instance, release);
8501
- await session.script(`mkdir -p "$1"`, { args: [dir] });
9022
+ await session.script(`mkdir -p "$(dirname "$1")" && mkdir "$1"`, { args: [dir] });
8502
9023
  const byTarget = /* @__PURE__ */ new Map();
8503
9024
  for (const entry of entries) {
8504
9025
  const target = entry.remoteDir ? `${dir}/${entry.remoteDir}` : dir;
@@ -8512,7 +9033,7 @@ async function uploadRelease(session, instance, release, entries) {
8512
9033
  }
8513
9034
  function isDirectory(target) {
8514
9035
  try {
8515
- return fs35.statSync(target).isDirectory();
9036
+ return fs37.statSync(target).isDirectory();
8516
9037
  } catch {
8517
9038
  return false;
8518
9039
  }
@@ -8520,7 +9041,7 @@ function isDirectory(target) {
8520
9041
  async function reportEmptyEnvironment(session, instance, workspaceRootDir) {
8521
9042
  const remote = await readRemoteEnv(session, instance);
8522
9043
  if (Object.keys(remote).length > 0) return;
8523
- if (!fs35.existsSync(path37.join(workspaceRootDir, ".env"))) return;
9044
+ if (!fs37.existsSync(path38.join(workspaceRootDir, ".env"))) return;
8524
9045
  p42.log.warn(
8525
9046
  `This app has no production environment variables yet.
8526
9047
 
@@ -8528,10 +9049,18 @@ Local ${pc18.cyan(".env")} values are not uploaded by a deploy. Set them with
8528
9049
  ${pc18.cyan("vela env set KEY")}, or copy a file across with ${pc18.cyan("vela env import .env.production")}.`
8529
9050
  );
8530
9051
  }
9052
+ async function serverTimeOrLocal(session) {
9053
+ try {
9054
+ return await serverTime(session);
9055
+ } catch {
9056
+ p42.log.warn("Could not read the clock on the server; stamping this release from this machine.");
9057
+ return /* @__PURE__ */ new Date();
9058
+ }
9059
+ }
8531
9060
 
8532
9061
  // src/commands/link.ts
8533
- import path38 from "node:path";
8534
- import process28 from "node:process";
9062
+ import path39 from "node:path";
9063
+ import process29 from "node:process";
8535
9064
  import { Command as Command77 } from "commander";
8536
9065
  import * as p43 from "@clack/prompts";
8537
9066
  var CREATE_NEW = "__new__";
@@ -8582,7 +9111,7 @@ async function pickExistingProject(projects) {
8582
9111
  });
8583
9112
  if (p43.isCancel(choice)) {
8584
9113
  p43.cancel("Operation cancelled.");
8585
- process28.exit(0);
9114
+ process29.exit(0);
8586
9115
  }
8587
9116
  return choice;
8588
9117
  }
@@ -8597,7 +9126,7 @@ async function pickTeam(teams3) {
8597
9126
  });
8598
9127
  if (p43.isCancel(choice)) {
8599
9128
  p43.cancel("Operation cancelled.");
8600
- process28.exit(0);
9129
+ process29.exit(0);
8601
9130
  }
8602
9131
  return teams3.find((team) => team.id === choice);
8603
9132
  }
@@ -8612,18 +9141,18 @@ async function promptProjectName(workspaceRootDir) {
8612
9141
  });
8613
9142
  if (p43.isCancel(value)) {
8614
9143
  p43.cancel("Operation cancelled.");
8615
- process28.exit(0);
9144
+ process29.exit(0);
8616
9145
  }
8617
9146
  return value.trim();
8618
9147
  }
8619
9148
  function defaultProjectName2(workspaceRootDir) {
8620
9149
  try {
8621
- const pkg = readPackageJson(path38.join(workspaceRootDir, "package.json"));
9150
+ const pkg = readPackageJson(path39.join(workspaceRootDir, "package.json"));
8622
9151
  const name = pkg.name;
8623
9152
  if (typeof name === "string" && name.trim()) return name.trim();
8624
9153
  } catch {
8625
9154
  }
8626
- return path38.basename(workspaceRootDir);
9155
+ return path39.basename(workspaceRootDir);
8627
9156
  }
8628
9157
 
8629
9158
  // src/commands/env.ts
@@ -8667,7 +9196,7 @@ function report(keys, where) {
8667
9196
  }
8668
9197
 
8669
9198
  // src/commands/env/set.ts
8670
- import process29 from "node:process";
9199
+ import process30 from "node:process";
8671
9200
  import { Command as Command79 } from "commander";
8672
9201
  import * as p45 from "@clack/prompts";
8673
9202
  import pc20 from "picocolors";
@@ -8709,7 +9238,7 @@ async function promptValue(key) {
8709
9238
  });
8710
9239
  if (p45.isCancel(value)) {
8711
9240
  p45.cancel("Operation cancelled.");
8712
- process29.exit(0);
9241
+ process30.exit(0);
8713
9242
  }
8714
9243
  return value;
8715
9244
  }
@@ -8754,9 +9283,9 @@ var envUnset = addTargetOptions(
8754
9283
  );
8755
9284
 
8756
9285
  // src/commands/env/import.ts
8757
- import fs36 from "node:fs";
8758
- import path39 from "node:path";
8759
- import process30 from "node:process";
9286
+ import fs38 from "node:fs";
9287
+ import path40 from "node:path";
9288
+ import process31 from "node:process";
8760
9289
  import { Command as Command81 } from "commander";
8761
9290
  import * as p47 from "@clack/prompts";
8762
9291
  import pc22 from "picocolors";
@@ -8801,10 +9330,10 @@ var envImport = addTargetOptions(
8801
9330
  )
8802
9331
  );
8803
9332
  function resolve(file) {
8804
- return path39.resolve(process30.cwd(), file);
9333
+ return path40.resolve(process31.cwd(), file);
8805
9334
  }
8806
9335
  function read(resolved, shown) {
8807
- if (!fs36.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
9336
+ if (!fs38.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
8808
9337
  const incoming = readLocalEnvFile(resolved);
8809
9338
  if (Object.keys(incoming).length === 0) p47.log.info(`${shown} has no variables to import.`);
8810
9339
  return incoming;
@@ -8853,10 +9382,10 @@ function report2(states, json) {
8853
9382
  return;
8854
9383
  }
8855
9384
  for (const state of states) {
8856
- p48.log.info(describe(state));
9385
+ p48.log.info(describe2(state));
8857
9386
  }
8858
9387
  }
8859
- function describe(state) {
9388
+ function describe2(state) {
8860
9389
  const health = (value) => value === "active" ? pc23.green(value) : pc23.red(value || "inactive");
8861
9390
  const rows = [
8862
9391
  ["Instance", state.instance],
@@ -8880,9 +9409,11 @@ function describe(state) {
8880
9409
  import { Command as Command84 } from "commander";
8881
9410
  import * as p49 from "@clack/prompts";
8882
9411
  import pc24 from "picocolors";
8883
- var rollback = addTargetOptions(
8884
- new Command84("rollback").description("put the previous release back").configureHelp(helpConfig),
8885
- "production"
9412
+ var rollback = addLockWaitOption(
9413
+ addTargetOptions(
9414
+ new Command84("rollback").description("put the previous release back").configureHelp(helpConfig),
9415
+ "production"
9416
+ )
8886
9417
  ).option("--to <release>", "roll back to a specific release instead of the previous one").action(
8887
9418
  (raw) => runCommand(async () => {
8888
9419
  const options = raw;
@@ -8897,7 +9428,11 @@ var rollback = addTargetOptions(
8897
9428
  ctx.session,
8898
9429
  "rollback.sh",
8899
9430
  {
8900
- args: [ctx.instance, ...options.to ? ["--to", options.to] : []],
9431
+ args: [
9432
+ ctx.instance,
9433
+ ...options.to ? ["--to", options.to] : [],
9434
+ ...lockWaitArgs(options.lockWait)
9435
+ ],
8901
9436
  stream: true
8902
9437
  }
8903
9438
  );
@@ -8954,7 +9489,7 @@ var logs = addTargetOptions(
8954
9489
  import { Command as Command87 } from "commander";
8955
9490
 
8956
9491
  // src/commands/admin/create.ts
8957
- import process31 from "node:process";
9492
+ import process32 from "node:process";
8958
9493
  import { Command as Command86 } from "commander";
8959
9494
  import * as p50 from "@clack/prompts";
8960
9495
  import pc25 from "picocolors";
@@ -9021,7 +9556,7 @@ async function upsertSuperuser(pb, email3, password11) {
9021
9556
  });
9022
9557
  if (p50.isCancel(confirmed) || !confirmed) {
9023
9558
  p50.cancel("Operation cancelled.");
9024
- process31.exit(0);
9559
+ process32.exit(0);
9025
9560
  }
9026
9561
  await pb.collection("_superusers").update(existing, { password: password11, passwordConfirm: password11 });
9027
9562
  p50.log.success(`Password updated for ${pc25.cyan(email3)}, you can sign in now`);
@@ -9045,7 +9580,7 @@ async function promptEmail() {
9045
9580
  });
9046
9581
  if (p50.isCancel(value)) {
9047
9582
  p50.cancel("Operation cancelled.");
9048
- process31.exit(0);
9583
+ process32.exit(0);
9049
9584
  }
9050
9585
  return value.trim();
9051
9586
  }
@@ -9056,7 +9591,7 @@ async function promptPassword2() {
9056
9591
  });
9057
9592
  if (p50.isCancel(value)) {
9058
9593
  p50.cancel("Operation cancelled.");
9059
- process31.exit(0);
9594
+ process32.exit(0);
9060
9595
  }
9061
9596
  const again = await p50.password({
9062
9597
  message: "Password again",
@@ -9064,7 +9599,7 @@ async function promptPassword2() {
9064
9599
  });
9065
9600
  if (p50.isCancel(again)) {
9066
9601
  p50.cancel("Operation cancelled.");
9067
- process31.exit(0);
9602
+ process32.exit(0);
9068
9603
  }
9069
9604
  return value;
9070
9605
  }
@@ -9076,8 +9611,8 @@ var admin = new Command87("admin").description("manage admin panel logins").conf
9076
9611
  import { Command as Command93 } from "commander";
9077
9612
 
9078
9613
  // src/commands/backup/create.ts
9079
- import fs37 from "node:fs";
9080
- import path40 from "node:path";
9614
+ import fs39 from "node:fs";
9615
+ import path41 from "node:path";
9081
9616
  import { Command as Command88 } from "commander";
9082
9617
  import * as p51 from "@clack/prompts";
9083
9618
  import pc26 from "picocolors";
@@ -9231,12 +9766,12 @@ Your bucket's own versioning is what protects the uploaded files.`
9231
9766
  }, "Failed to create the backup.")
9232
9767
  );
9233
9768
  async function download(ctx, key, outputDir) {
9234
- const dir = path40.resolve(ctx.workspaceRootDir, outputDir);
9235
- fs37.mkdirSync(dir, { recursive: true });
9236
- const destination = path40.join(dir, key);
9769
+ const dir = path41.resolve(ctx.workspaceRootDir, outputDir);
9770
+ fs39.mkdirSync(dir, { recursive: true });
9771
+ const destination = path41.join(dir, key);
9237
9772
  if (!ctx.session) {
9238
- fs37.copyFileSync(path40.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9239
- return path40.relative(ctx.workspaceRootDir, destination);
9773
+ fs39.copyFileSync(path41.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9774
+ return path41.relative(ctx.workspaceRootDir, destination);
9240
9775
  }
9241
9776
  const spinner7 = p51.spinner();
9242
9777
  spinner7.start(`Downloading ${key}`);
@@ -9247,7 +9782,7 @@ async function download(ctx, key, outputDir) {
9247
9782
  throw error;
9248
9783
  }
9249
9784
  spinner7.stop(`Downloaded ${key}`);
9250
- return path40.relative(ctx.workspaceRootDir, destination);
9785
+ return path41.relative(ctx.workspaceRootDir, destination);
9251
9786
  }
9252
9787
 
9253
9788
  // src/commands/backup/list.ts
@@ -9281,8 +9816,8 @@ Take one with ${pc27.cyan("vela backup create")}.`
9281
9816
  );
9282
9817
 
9283
9818
  // src/commands/backup/download.ts
9284
- import fs38 from "node:fs";
9285
- import path41 from "node:path";
9819
+ import fs40 from "node:fs";
9820
+ import path42 from "node:path";
9286
9821
  import { Command as Command90 } from "commander";
9287
9822
  import * as p53 from "@clack/prompts";
9288
9823
  import pc28 from "picocolors";
@@ -9309,11 +9844,11 @@ Run ${pc28.cyan("vela backup list")} to see what it does have.`
9309
9844
  Fetch it from the bucket directly \u2014 vela does not hold its credentials.`
9310
9845
  );
9311
9846
  }
9312
- const dir = path41.resolve(ctx.workspaceRootDir, options.output);
9313
- fs38.mkdirSync(dir, { recursive: true });
9314
- const destination = path41.join(dir, key);
9847
+ const dir = path42.resolve(ctx.workspaceRootDir, options.output);
9848
+ fs40.mkdirSync(dir, { recursive: true });
9849
+ const destination = path42.join(dir, key);
9315
9850
  if (!ctx.session) {
9316
- fs38.copyFileSync(path41.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9851
+ fs40.copyFileSync(path42.join(ctx.workspaceRootDir, "data", "backups", key), destination);
9317
9852
  } else {
9318
9853
  const spinner7 = p53.spinner();
9319
9854
  spinner7.start(`Downloading ${key} (${formatBytes(found.size)})`);
@@ -9327,14 +9862,14 @@ Fetch it from the bucket directly \u2014 vela does not hold its credentials.`
9327
9862
  }
9328
9863
  reportResult({
9329
9864
  summary: `Saved ${key} from ${ctx.targetName}.`,
9330
- filesCreated: [path41.relative(ctx.workspaceRootDir, destination)]
9865
+ filesCreated: [path42.relative(ctx.workspaceRootDir, destination)]
9331
9866
  });
9332
9867
  });
9333
9868
  }, "Failed to download the backup.")
9334
9869
  );
9335
9870
 
9336
9871
  // src/commands/backup/delete.ts
9337
- import process32 from "node:process";
9872
+ import process33 from "node:process";
9338
9873
  import { Command as Command91 } from "commander";
9339
9874
  import * as p54 from "@clack/prompts";
9340
9875
  import pc29 from "picocolors";
@@ -9360,7 +9895,7 @@ Run ${pc29.cyan("vela backup list")} to see what it does have.`
9360
9895
  });
9361
9896
  if (p54.isCancel(confirmed) || !confirmed) {
9362
9897
  p54.cancel("Operation cancelled.");
9363
- process32.exit(0);
9898
+ process33.exit(0);
9364
9899
  }
9365
9900
  }
9366
9901
  await ctx.pb.backups.delete(key);
@@ -9422,15 +9957,17 @@ Set one with ${pc30.cyan('vela backup schedule "0 3 * * *"')}.`
9422
9957
  var backup = new Command93("backup").description("back up the database and uploads, locally or on a target").configureHelp(helpConfig).addCommand(backupCreate).addCommand(backupList).addCommand(backupDownload).addCommand(backupDelete).addCommand(backupSchedule);
9423
9958
 
9424
9959
  // src/commands/restore.ts
9425
- import fs39 from "node:fs";
9426
- import path42 from "node:path";
9427
- import process33 from "node:process";
9960
+ import fs41 from "node:fs";
9961
+ import path43 from "node:path";
9962
+ import process34 from "node:process";
9428
9963
  import { Command as Command94 } from "commander";
9429
9964
  import * as p56 from "@clack/prompts";
9430
9965
  import pc31 from "picocolors";
9431
- var restore = addTargetOptions(
9432
- new Command94("restore").description("replace a deployment\u2019s database and uploads from a backup").argument("[source]", "a backup key on the target, or a path to a local archive").configureHelp(helpConfig),
9433
- "production"
9966
+ var restore = addLockWaitOption(
9967
+ addTargetOptions(
9968
+ 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),
9969
+ "production"
9970
+ )
9434
9971
  ).option("-y, --yes", "skip the confirmation prompt").option("--no-migrate", "do not run migrations against the restored database").option("--keep-previous <n>", "how many replaced databases to keep", "1").action(
9435
9972
  (source, raw) => runCommand(async () => {
9436
9973
  const options = raw;
@@ -9444,10 +9981,10 @@ var restore = addTargetOptions(
9444
9981
  `${ctx.targetName} was deployed without a database, so there is nothing to restore.`
9445
9982
  );
9446
9983
  }
9447
- const local = source && fs39.existsSync(source) ? source : void 0;
9984
+ const local = source && fs41.existsSync(source) ? source : void 0;
9448
9985
  const key = local ? void 0 : await resolveKey(ctx, source);
9449
9986
  if (!options.yes) {
9450
- await confirm12(ctx.appName, ctx.targetName, ctx.envTag, local ?? key);
9987
+ await confirm13(ctx.appName, ctx.targetName, ctx.envTag, local ?? key);
9451
9988
  }
9452
9989
  const archive = local ? await stage(ctx, local) : remotePaths.backup(ctx.instance, key);
9453
9990
  const result = await runServerScript(ctx.session, "restore.sh", {
@@ -9460,13 +9997,14 @@ var restore = addTargetOptions(
9460
9997
  ...options.migrate ? [] : ["--no-migrate"],
9461
9998
  // A copy this machine pushed up is scratch; one already in the
9462
9999
  // instance's own backups directory is not ours to delete.
9463
- ...local ? ["--cleanup-archive"] : []
10000
+ ...local ? ["--cleanup-archive"] : [],
10001
+ ...lockWaitArgs(options.lockWait)
9464
10002
  ],
9465
10003
  stream: true
9466
10004
  });
9467
10005
  p56.log.success(
9468
10006
  `Restored ${pc31.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${pc31.cyan(
9469
- local ? path42.basename(local) : key
10007
+ local ? path43.basename(local) : key
9470
10008
  )}.`
9471
10009
  );
9472
10010
  if (result?.storageCarriedOver) {
@@ -9526,27 +10064,27 @@ Take one with ${pc31.cyan("vela backup create")}, or pass the path to an archive
9526
10064
  });
9527
10065
  if (p56.isCancel(chosen)) {
9528
10066
  p56.cancel("Operation cancelled.");
9529
- process33.exit(0);
10067
+ process34.exit(0);
9530
10068
  }
9531
10069
  return chosen;
9532
10070
  }
9533
10071
  async function stage(ctx, file) {
9534
10072
  const dir = remotePaths.restoreStage(ctx.instance);
9535
- const remote = `${dir}/${path42.basename(file)}`;
10073
+ const remote = `${dir}/${path43.basename(file)}`;
9536
10074
  const spinner7 = p56.spinner();
9537
- spinner7.start(`Uploading ${path42.basename(file)}`);
10075
+ spinner7.start(`Uploading ${path43.basename(file)}`);
9538
10076
  try {
9539
10077
  await ctx.session.script(`mkdir -p "$1"`, { args: [dir] });
9540
10078
  await ctx.session.upload([file], dir);
9541
10079
  } catch (error) {
9542
- spinner7.stop(`Could not upload ${path42.basename(file)}.`);
10080
+ spinner7.stop(`Could not upload ${path43.basename(file)}.`);
9543
10081
  throw error;
9544
10082
  }
9545
- spinner7.stop(`Uploaded ${path42.basename(file)}`);
10083
+ spinner7.stop(`Uploaded ${path43.basename(file)}`);
9546
10084
  return remote;
9547
10085
  }
9548
- async function confirm12(appName, targetName, envTag, from) {
9549
- const what = `${pc31.cyan(`${appName} (${targetName})`)} from ${pc31.cyan(path42.basename(from))}`;
10086
+ async function confirm13(appName, targetName, envTag, from) {
10087
+ const what = `${pc31.cyan(`${appName} (${targetName})`)} from ${pc31.cyan(path43.basename(from))}`;
9550
10088
  if (isProd(envTag)) {
9551
10089
  const answer = await p56.text({
9552
10090
  message: `This replaces the database and uploads of ${what}. Type the app name to confirm`,
@@ -9554,7 +10092,7 @@ async function confirm12(appName, targetName, envTag, from) {
9554
10092
  });
9555
10093
  if (p56.isCancel(answer)) {
9556
10094
  p56.cancel("Operation cancelled.");
9557
- process33.exit(0);
10095
+ process34.exit(0);
9558
10096
  }
9559
10097
  return;
9560
10098
  }
@@ -9564,7 +10102,7 @@ async function confirm12(appName, targetName, envTag, from) {
9564
10102
  });
9565
10103
  if (p56.isCancel(ok) || !ok) {
9566
10104
  p56.cancel("Operation cancelled.");
9567
- process33.exit(0);
10105
+ process34.exit(0);
9568
10106
  }
9569
10107
  }
9570
10108
 
@@ -9657,36 +10195,36 @@ Release and domain are shown from what this project recorded.`
9657
10195
  }
9658
10196
 
9659
10197
  // src/commands/test.ts
9660
- import path43 from "node:path";
9661
- import process34 from "node:process";
10198
+ import path44 from "node:path";
10199
+ import process35 from "node:process";
9662
10200
  import { Command as Command96 } from "commander";
9663
10201
  import PocketBase6 from "pocketbase";
9664
10202
  import pc33 from "picocolors";
9665
10203
  import { x as x5 } from "tinyexec";
9666
- import { detect as detect7 } from "package-manager-detector";
10204
+ import { detect as detect8 } from "package-manager-detector";
9667
10205
  import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
9668
- import fs40 from "node:fs";
10206
+ import fs42 from "node:fs";
9669
10207
  var testServer = new Command96("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
9670
- const cwd = process34.cwd();
10208
+ const cwd = process35.cwd();
9671
10209
  const email3 = `test-${Math.random().toString(36).slice(2)}@example.com`;
9672
10210
  const password11 = "password";
9673
- const testDataDir = path43.join(cwd, "test-data");
9674
- fs40.rmSync(testDataDir, { recursive: true, force: true });
10211
+ const testDataDir = path44.join(cwd, "test-data");
10212
+ fs42.rmSync(testDataDir, { recursive: true, force: true });
9675
10213
  const { stop, url } = await launchPocketbase(cwd, {
9676
10214
  dir: testDataDir,
9677
- migrationsDir: path43.join(cwd, MIGRATIONS_DIR),
10215
+ migrationsDir: path44.join(cwd, MIGRATIONS_DIR),
9678
10216
  // The app's PocketBase hooks (slug generation, personal teams, …) are part
9679
10217
  // of its behaviour; the suite runs against the same server dev and build
9680
10218
  // start, so it loads them from the same place.
9681
- hooksDir: path43.join(cwd, DATA_DIR, "hooks"),
10219
+ hooksDir: path44.join(cwd, DATA_DIR, "hooks"),
9682
10220
  email: email3,
9683
10221
  password: password11
9684
10222
  });
9685
- process34.env.POCKETBASE_URL = url;
9686
- process34.env.POCKETBASE_SUPERUSER_EMAIL = email3;
9687
- process34.env.POCKETBASE_SUPERUSER_PASSWORD = password11;
9688
- process34.env.VELA_DATA_DIR = testDataDir;
9689
- process34.env.TEST = "true";
10223
+ process35.env.POCKETBASE_URL = url;
10224
+ process35.env.POCKETBASE_SUPERUSER_EMAIL = email3;
10225
+ process35.env.POCKETBASE_SUPERUSER_PASSWORD = password11;
10226
+ process35.env.VELA_DATA_DIR = testDataDir;
10227
+ process35.env.TEST = "true";
9690
10228
  console.log(`${pc33.greenBright("\u2713")} Created test database`);
9691
10229
  let vite;
9692
10230
  let cleanedUp = false;
@@ -9694,7 +10232,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9694
10232
  if (cleanedUp) return;
9695
10233
  cleanedUp = true;
9696
10234
  stop();
9697
- fs40.rmSync(testDataDir, { recursive: true, force: true });
10235
+ fs42.rmSync(testDataDir, { recursive: true, force: true });
9698
10236
  };
9699
10237
  const cleanup = async () => {
9700
10238
  if (cleanedUp) return;
@@ -9705,12 +10243,12 @@ var testServer = new Command96("test:server").description("run server tests").al
9705
10243
  const fail = async (message) => {
9706
10244
  console.error(`${pc33.redBright("\u2717")} ${message}`);
9707
10245
  await cleanup();
9708
- process34.exit(1);
10246
+ process35.exit(1);
9709
10247
  };
9710
- process34.on("exit", cleanupSync);
9711
- process34.on("SIGINT", () => {
10248
+ process35.on("exit", cleanupSync);
10249
+ process35.on("SIGINT", () => {
9712
10250
  cleanupSync();
9713
- process34.exit(130);
10251
+ process35.exit(130);
9714
10252
  });
9715
10253
  const pb = new PocketBase6(url);
9716
10254
  try {
@@ -9738,7 +10276,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9738
10276
  });
9739
10277
  const vitePort = await findFreePort();
9740
10278
  await vite.listen(vitePort);
9741
- process34.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
10279
+ process35.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
9742
10280
  console.log(`${pc33.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
9743
10281
  console.log(`${pc33.greenBright("\u2713")} Started PocketBase: ${url}`);
9744
10282
  const extraArgs = (cmd.parent?.args ?? []).slice(1);
@@ -9746,7 +10284,7 @@ var testServer = new Command96("test:server").description("run server tests").al
9746
10284
  const passthrough = filter ? extraArgs.filter((a) => a !== filter) : extraArgs;
9747
10285
  if (!filter) filter = "server";
9748
10286
  try {
9749
- const pm = (await detect7({ cwd }))?.name ?? "npm";
10287
+ const pm = (await detect8({ cwd }))?.name ?? "npm";
9750
10288
  const resolved = resolveCommand7(pm, "execute", [
9751
10289
  "vitest",
9752
10290
  "run",
@@ -9757,12 +10295,12 @@ var testServer = new Command96("test:server").description("run server tests").al
9757
10295
  const resolvedArgs = resolved.args.slice();
9758
10296
  if (pm === "npm") resolvedArgs.unshift("--yes");
9759
10297
  const result = await x5(resolved.command, resolvedArgs, {
9760
- nodeOptions: { cwd, stdio: "inherit", env: { ...process34.env, CI: "1" } }
10298
+ nodeOptions: { cwd, stdio: "inherit", env: { ...process35.env, CI: "1" } }
9761
10299
  });
9762
- process34.exitCode = result.exitCode ?? 1;
10300
+ process35.exitCode = result.exitCode ?? 1;
9763
10301
  } catch (e) {
9764
10302
  console.error(`${pc33.redBright("\u2717")} ${e.message}`);
9765
- process34.exitCode = 1;
10303
+ process35.exitCode = 1;
9766
10304
  } finally {
9767
10305
  await cleanup();
9768
10306
  }
@@ -9793,8 +10331,8 @@ function stubPagesPlugin() {
9793
10331
  }
9794
10332
 
9795
10333
  // src/commands/routes.ts
9796
- import fs41 from "node:fs";
9797
- import path44 from "node:path";
10334
+ import fs43 from "node:fs";
10335
+ import path45 from "node:path";
9798
10336
  import { Command as Command97 } from "commander";
9799
10337
  var HTTP_METHODS = /* @__PURE__ */ new Set([
9800
10338
  "GET",
@@ -9808,30 +10346,30 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
9808
10346
  ]);
9809
10347
  var routes = new Command97("routes").description("list routes").configureHelp(helpConfig).action(async () => {
9810
10348
  const { workspaceRootDir, routesDir } = await getWorkspace();
9811
- const routesRoot = path44.join(workspaceRootDir, routesDir);
10349
+ const routesRoot = path45.join(workspaceRootDir, routesDir);
9812
10350
  const found = walk(routesRoot, routesRoot).filter((r) => r.methods.length > 0);
9813
10351
  found.sort((a, b) => a.urlPattern.localeCompare(b.urlPattern));
9814
10352
  printTable(found);
9815
10353
  });
9816
10354
  function walk(root, dir) {
9817
- const entries = fs41.readdirSync(dir, { withFileTypes: true });
10355
+ const entries = fs43.readdirSync(dir, { withFileTypes: true });
9818
10356
  const routes2 = [];
9819
10357
  const hasLeaf = entries.some((e) => e.isFile() && isRouteFile(e.name));
9820
10358
  if (hasLeaf) {
9821
- const id = "/" + path44.relative(root, dir).split(path44.sep).filter(Boolean).join("/");
10359
+ const id = "/" + path45.relative(root, dir).split(path45.sep).filter(Boolean).join("/");
9822
10360
  const urlPattern = id.replace(/\([^)]+\)\/?/g, "").replace(/\/$/, "") || "/";
9823
10361
  const methods = /* @__PURE__ */ new Set();
9824
10362
  for (const entry of entries) {
9825
10363
  if (!entry.isFile()) continue;
9826
10364
  if (entry.name.endsWith("+page.svelte")) methods.add("GET");
9827
10365
  if (entry.name.endsWith("+server.ts") || entry.name.endsWith("+server.js") || entry.name.endsWith("+page.server.ts") || entry.name.endsWith("+page.server.js")) {
9828
- extractMethods(path44.join(dir, entry.name)).forEach((m) => methods.add(m));
10366
+ extractMethods(path45.join(dir, entry.name)).forEach((m) => methods.add(m));
9829
10367
  }
9830
10368
  }
9831
10369
  routes2.push({ id: id || "/", urlPattern, methods: [...methods] });
9832
10370
  }
9833
10371
  for (const entry of entries) {
9834
- if (entry.isDirectory()) routes2.push(...walk(root, path44.join(dir, entry.name)));
10372
+ if (entry.isDirectory()) routes2.push(...walk(root, path45.join(dir, entry.name)));
9835
10373
  }
9836
10374
  return routes2;
9837
10375
  }
@@ -9840,7 +10378,7 @@ function isRouteFile(name) {
9840
10378
  }
9841
10379
  function extractMethods(file) {
9842
10380
  try {
9843
- const content = fs41.readFileSync(file, "utf8");
10381
+ const content = fs43.readFileSync(file, "utf8");
9844
10382
  const methods = [];
9845
10383
  const exportRegex = /export\s+(?:const|async\s+function|function)\s+(\w+)/g;
9846
10384
  let match;
@@ -9881,14 +10419,14 @@ function printTable(routes2) {
9881
10419
  }
9882
10420
 
9883
10421
  // src/commands/i18n.ts
9884
- import process35 from "node:process";
10422
+ import process36 from "node:process";
9885
10423
  import { Command as Command98 } from "commander";
9886
10424
  import { x as x6 } from "tinyexec";
9887
- import { detect as detect8 } from "package-manager-detector";
10425
+ import { detect as detect9 } from "package-manager-detector";
9888
10426
  import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
9889
10427
  async function runWuchale(extraArgs) {
9890
- const cwd = process35.cwd();
9891
- const pm = (await detect8({ cwd }))?.name ?? "npm";
10428
+ const cwd = process36.cwd();
10429
+ const pm = (await detect9({ cwd }))?.name ?? "npm";
9892
10430
  const resolved = resolveCommand8(pm, "execute", ["wuchale", ...extraArgs]);
9893
10431
  const args = resolved.args.slice();
9894
10432
  if (pm === "npm") args.unshift("--yes");
@@ -9923,15 +10461,15 @@ import pc35 from "picocolors";
9923
10461
 
9924
10462
  // src/lib/cms-backend.ts
9925
10463
  import { createRequire as createRequire3 } from "node:module";
9926
- import path45 from "node:path";
9927
- import process36 from "node:process";
10464
+ import path46 from "node:path";
10465
+ import process37 from "node:process";
9928
10466
  import { pathToFileURL as pathToFileURL3 } from "node:url";
9929
10467
  import pc34 from "picocolors";
9930
10468
  var DEFAULT_PROJECT = "default";
9931
10469
  async function loadBackendModule(root) {
9932
10470
  let entry;
9933
10471
  try {
9934
- entry = createRequire3(path45.join(root, "package.json")).resolve("@velastack/cms/backend");
10472
+ entry = createRequire3(path46.join(root, "package.json")).resolve("@velastack/cms/backend");
9935
10473
  } catch {
9936
10474
  throw new Error(
9937
10475
  `@velastack/cms is not installed in this project.
@@ -9941,7 +10479,7 @@ Run ${pc34.cyan("vela enable cms")} first.`
9941
10479
  }
9942
10480
  return await import(pathToFileURL3(entry).href);
9943
10481
  }
9944
- async function withCmsBackend(fn, cwd = process36.cwd()) {
10482
+ async function withCmsBackend(fn, cwd = process37.cwd()) {
9945
10483
  const root = findWorkspaceRoot(cwd);
9946
10484
  if (!root) {
9947
10485
  throw new Error("Could not find workspace root (no package.json found)");
@@ -9949,8 +10487,8 @@ async function withCmsBackend(fn, cwd = process36.cwd()) {
9949
10487
  const { createCmsBackend } = await loadBackendModule(root);
9950
10488
  const dataDir2 = localDataDir(root);
9951
10489
  const backend3 = createCmsBackend({
9952
- dbPath: path45.join(dataDir2, "cms.sqlite"),
9953
- uploadDir: path45.join(dataDir2, "uploads")
10490
+ dbPath: path46.join(dataDir2, "cms.sqlite"),
10491
+ uploadDir: path46.join(dataDir2, "uploads")
9954
10492
  });
9955
10493
  try {
9956
10494
  return await fn(backend3);
@@ -10053,23 +10591,26 @@ var NO_BACKEND_COMMMANDS = /* @__PURE__ */ new Set([
10053
10591
  // Acts on whichever target `-t` names; the local path reads the project's
10054
10592
  // own credentials rather than requiring them in this process.
10055
10593
  "backup",
10056
- "restore"
10594
+ "restore",
10595
+ // Removes a copy from its server; the other `destroy` subcommands edit the
10596
+ // local schema and stay gated.
10597
+ "destroy deployment"
10057
10598
  ]);
10058
10599
  var BACKEND_OPTIONAL_COMMANDS = /* @__PURE__ */ new Set(["dev", "build", "preview", "deploy"]);
10059
10600
  var SELF_CREDENTIALED_COMMANDS = /* @__PURE__ */ new Set(["test:server"]);
10060
10601
  var program = new Command104().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
10061
10602
  program.hook("preAction", (_thisCommand, actionCommand) => {
10062
10603
  if (isStub(actionCommand)) return;
10063
- const envRoot = findWorkspaceRoot() ?? process37.cwd();
10604
+ const envRoot = findWorkspaceRoot() ?? process38.cwd();
10064
10605
  dotenv2.config({ path: nodePath.join(envRoot, ".env"), quiet: true });
10065
- const path46 = getCommandPath(actionCommand);
10066
- if (NO_BACKEND_COMMMANDS.has(path46)) return;
10067
- const top = path46.split(" ", 1)[0];
10606
+ const path47 = getCommandPath(actionCommand);
10607
+ if (NO_BACKEND_COMMMANDS.has(path47)) return;
10608
+ const top = path47.split(" ", 1)[0];
10068
10609
  if (NO_BACKEND_COMMMANDS.has(top)) return;
10069
10610
  if (!hasBackend()) {
10070
10611
  if (BACKEND_OPTIONAL_COMMANDS.has(top)) return;
10071
10612
  p61.log.error(
10072
- `${pc38.cyan(`vela ${path46}`)} needs a backend, and this project does not have one.
10613
+ `${pc38.cyan(`vela ${path47}`)} needs a backend, and this project does not have one.
10073
10614
 
10074
10615
  Static projects have no database to talk to.
10075
10616
 
@@ -10077,10 +10618,10 @@ To add a backend to this project, run ${pc38.cyan("vela bless")}.`
10077
10618
  );
10078
10619
  p61.log.message();
10079
10620
  p61.cancel("Operation failed.");
10080
- process37.exit(1);
10621
+ process38.exit(1);
10081
10622
  }
10082
- if (SELF_CREDENTIALED_COMMANDS.has(path46)) return;
10083
- if (!process37.env.POCKETBASE_SUPERUSER_EMAIL || !process37.env.POCKETBASE_SUPERUSER_PASSWORD) {
10623
+ if (SELF_CREDENTIALED_COMMANDS.has(path47)) return;
10624
+ if (!process38.env.POCKETBASE_SUPERUSER_EMAIL || !process38.env.POCKETBASE_SUPERUSER_PASSWORD) {
10084
10625
  p61.log.error(
10085
10626
  `PocketBase superuser credentials are required.
10086
10627
 
@@ -10091,7 +10632,7 @@ To set up an existing project, run ${pc38.cyan("vela bless")}.`
10091
10632
  );
10092
10633
  p61.log.message();
10093
10634
  p61.cancel("Operation failed.");
10094
- process37.exit(1);
10635
+ process38.exit(1);
10095
10636
  }
10096
10637
  });
10097
10638
  function getCommandPath(cmd) {
@@ -10145,5 +10686,5 @@ for (const command of [
10145
10686
  }
10146
10687
 
10147
10688
  // src/bin.ts
10148
- program.parse(normalizeArgv(process38.argv.slice(2)), { from: "user" });
10689
+ program.parse(normalizeArgv(process39.argv.slice(2)), { from: "user" });
10149
10690
  //# sourceMappingURL=bin.js.map