vela 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin.ts
4
- import process24 from "node:process";
4
+ import process32 from "node:process";
5
5
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "vela",
10
- version: "0.10.2",
10
+ version: "0.10.4",
11
11
  type: "module",
12
12
  description: "A CLI for creating and updating SvelteKit projects",
13
13
  license: "MIT",
@@ -41,7 +41,7 @@ var package_default = {
41
41
  dependencies: {
42
42
  "@clack/prompts": "^1.7.0",
43
43
  "@faker-js/faker": "^10.6.0",
44
- "@velastack/patterns": "^0.1.0",
44
+ "@velastack/patterns": "^0.1.2",
45
45
  "@velastack/pocketbase-codegen": "^0.1.0",
46
46
  "annotate-json-schema": "^0.1.0",
47
47
  commander: "^13.1.0",
@@ -152,15 +152,15 @@ function isDelegatable(argv2) {
152
152
  return first === void 0 || !NO_DELEGATE_COMMANDS.has(first);
153
153
  }
154
154
  function delegateToLocalCli(opts) {
155
- const env = opts.env ?? process2.env;
156
- if (env[NO_DELEGATE_ENV]) return null;
155
+ const env2 = opts.env ?? process2.env;
156
+ if (env2[NO_DELEGATE_ENV]) return null;
157
157
  const argv2 = opts.argv ?? process2.argv.slice(2);
158
158
  if (!isDelegatable(argv2)) return null;
159
159
  const local = findLocalCli(opts.cwd ?? process2.cwd(), opts.selfPath);
160
160
  if (!local || local.version === opts.selfVersion) return null;
161
161
  const result = spawnSync(process2.execPath, [local.binPath, ...argv2], {
162
162
  stdio: "inherit",
163
- env: { ...env, [NO_DELEGATE_ENV]: "1" }
163
+ env: { ...env2, [NO_DELEGATE_ENV]: "1" }
164
164
  });
165
165
  if (result.error) return null;
166
166
  if (result.signal) return 1;
@@ -168,11 +168,12 @@ function delegateToLocalCli(opts) {
168
168
  }
169
169
 
170
170
  // src/program.ts
171
- import process23 from "node:process";
172
- import * as p29 from "@clack/prompts";
173
- import { Command as Command73 } from "commander";
174
- import dotenv from "dotenv";
175
- import pc7 from "picocolors";
171
+ import process31 from "node:process";
172
+ import * as p42 from "@clack/prompts";
173
+ import { Command as Command87 } from "commander";
174
+ import nodePath from "node:path";
175
+ import dotenv2 from "dotenv";
176
+ import pc21 from "picocolors";
176
177
 
177
178
  // src/lib/help.ts
178
179
  import pc from "picocolors";
@@ -349,8 +350,8 @@ function mergePackageJson(user, template) {
349
350
  }
350
351
  return { merged, added, conflicts, replaced };
351
352
  }
352
- function readPackageJson(path36) {
353
- return JSON.parse(fs2.readFileSync(path36, "utf8"));
353
+ function readPackageJson(path44) {
354
+ return JSON.parse(fs2.readFileSync(path44, "utf8"));
354
355
  }
355
356
  var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
356
357
  var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
@@ -363,12 +364,12 @@ function fillTemplatePlaceholders(raw, values) {
363
364
  function escapeSingleQuoted(value) {
364
365
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
365
366
  }
366
- function readTemplatePackageJson(path36, values) {
367
- const raw = fillTemplatePlaceholders(fs2.readFileSync(path36, "utf8"), values);
367
+ function readTemplatePackageJson(path44, values) {
368
+ const raw = fillTemplatePlaceholders(fs2.readFileSync(path44, "utf8"), values);
368
369
  return JSON.parse(raw);
369
370
  }
370
- function writePackageJson(path36, pkg) {
371
- fs2.writeFileSync(path36, JSON.stringify(pkg, null, " ") + "\n");
371
+ function writePackageJson(path44, pkg) {
372
+ fs2.writeFileSync(path44, JSON.stringify(pkg, null, " ") + "\n");
372
373
  }
373
374
  function toValidPackageName(name) {
374
375
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
@@ -630,13 +631,14 @@ import fs6 from "node:fs";
630
631
  import net from "node:net";
631
632
  import path5 from "node:path";
632
633
  import process5 from "node:process";
634
+ import { createRequire } from "node:module";
633
635
  import { spawn } from "node:child_process";
634
636
  import PocketBase from "pocketbase";
635
637
  import { detect as detect2 } from "package-manager-detector";
636
638
  import { resolveCommand } from "package-manager-detector/commands";
637
639
  import { x } from "tinyexec";
638
640
  function findFreePort(host = "localhost") {
639
- return new Promise((resolve, reject) => {
641
+ return new Promise((resolve2, reject) => {
640
642
  const server = net.createServer();
641
643
  server.unref();
642
644
  server.once("error", reject);
@@ -645,13 +647,13 @@ function findFreePort(host = "localhost") {
645
647
  server.close((err) => {
646
648
  if (err) reject(err);
647
649
  else if (!addr) reject(new Error("Failed to obtain a free port"));
648
- else resolve(addr.port);
650
+ else resolve2(addr.port);
649
651
  });
650
652
  });
651
653
  });
652
654
  }
653
655
  function waitForReadyOrExit(proc, url, maxAttempts = 40) {
654
- return new Promise((resolve, reject) => {
656
+ return new Promise((resolve2, reject) => {
655
657
  let settled = false;
656
658
  const onExit = (code, signal) => {
657
659
  if (settled) return;
@@ -671,7 +673,7 @@ function waitForReadyOrExit(proc, url, maxAttempts = 40) {
671
673
  if (settled) return;
672
674
  settled = true;
673
675
  proc.removeListener("exit", onExit);
674
- resolve();
676
+ resolve2();
675
677
  return;
676
678
  }
677
679
  } catch {
@@ -715,8 +717,8 @@ async function startPocketbaseServe(opts) {
715
717
  } catch (err) {
716
718
  lastErr = err;
717
719
  if (proc.exitCode === null && proc.signalCode === null) {
718
- await new Promise((resolve) => {
719
- proc.once("exit", () => resolve());
720
+ await new Promise((resolve2) => {
721
+ proc.once("exit", () => resolve2());
720
722
  proc.kill();
721
723
  });
722
724
  }
@@ -724,10 +726,10 @@ async function startPocketbaseServe(opts) {
724
726
  }
725
727
  throw lastErr instanceof Error ? lastErr : new Error("Failed to start PocketBase");
726
728
  }
727
- async function authWithRetries(pb, email3, password8, attempts = 3) {
729
+ async function authWithRetries(pb, email3, password10, attempts = 3) {
728
730
  for (let attempt = 1; attempt <= attempts; attempt++) {
729
731
  try {
730
- await pb.collection("_superusers").authWithPassword(email3, password8);
732
+ await pb.collection("_superusers").authWithPassword(email3, password10);
731
733
  return;
732
734
  } catch (e) {
733
735
  if (attempt === attempts) {
@@ -759,14 +761,14 @@ async function withPocketbase(cwd, fn, creds) {
759
761
  const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
760
762
  const host = "localhost";
761
763
  const email3 = creds?.email ?? process5.env.POCKETBASE_SUPERUSER_EMAIL;
762
- const password8 = creds?.password ?? process5.env.POCKETBASE_SUPERUSER_PASSWORD;
764
+ const password10 = creds?.password ?? process5.env.POCKETBASE_SUPERUSER_PASSWORD;
763
765
  if (!fs6.existsSync(dir)) {
764
766
  throw new Error("PocketBase data directory does not exist");
765
767
  }
766
768
  const metadata = getPocketbaseMetadata(cwd);
767
769
  if (metadata?.pocketbaseUrl) {
768
770
  const pb = new PocketBase(metadata.pocketbaseUrl);
769
- await authWithRetries(pb, email3, password8);
771
+ await authWithRetries(pb, email3, password10);
770
772
  await fn(pb);
771
773
  return;
772
774
  }
@@ -778,13 +780,13 @@ async function withPocketbase(cwd, fn, creds) {
778
780
  });
779
781
  try {
780
782
  const pb = new PocketBase(url);
781
- await authWithRetries(pb, email3, password8);
783
+ await authWithRetries(pb, email3, password10);
782
784
  await fn(pb);
783
785
  } finally {
784
786
  proc.kill();
785
787
  }
786
788
  }
787
- async function createSuperuser(cwd, email3, password8) {
789
+ async function createSuperuser(cwd, email3, password10) {
788
790
  const dir = path5.join(cwd, DATA_DIR);
789
791
  const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
790
792
  fs6.mkdirSync(dir, { recursive: true });
@@ -800,7 +802,7 @@ async function createSuperuser(cwd, email3, password8) {
800
802
  "superuser",
801
803
  "create",
802
804
  email3,
803
- password8
805
+ password10
804
806
  ],
805
807
  "pipe"
806
808
  );
@@ -809,7 +811,7 @@ async function launchPocketbase(cwd, {
809
811
  dir,
810
812
  migrationsDir,
811
813
  email: email3,
812
- password: password8
814
+ password: password10
813
815
  }) {
814
816
  const host = "localhost";
815
817
  fs6.mkdirSync(dir, { recursive: true });
@@ -825,7 +827,7 @@ async function launchPocketbase(cwd, {
825
827
  "superuser",
826
828
  "create",
827
829
  email3,
828
- password8
830
+ password10
829
831
  ],
830
832
  "pipe"
831
833
  );
@@ -845,6 +847,33 @@ async function launchPocketbase(cwd, {
845
847
  url
846
848
  };
847
849
  }
850
+ function pocketbaseVersion() {
851
+ const require2 = createRequire(import.meta.url);
852
+ const pkg = require2("pocketbase-server/package.json");
853
+ return pkg.version;
854
+ }
855
+ async function ensureSuperuser(cwd) {
856
+ const email3 = process5.env.POCKETBASE_SUPERUSER_EMAIL;
857
+ const password10 = process5.env.POCKETBASE_SUPERUSER_PASSWORD;
858
+ if (!email3 || !password10) return;
859
+ const dir = path5.join(cwd, DATA_DIR);
860
+ if (!fs6.existsSync(dir)) return;
861
+ const { getBinaryPath } = await import("pocketbase-server");
862
+ await x(
863
+ getBinaryPath(),
864
+ [
865
+ "--dir",
866
+ dir,
867
+ "--migrationsDir",
868
+ path5.join(cwd, MIGRATIONS_DIR),
869
+ "superuser",
870
+ "upsert",
871
+ email3,
872
+ password10
873
+ ],
874
+ { nodeOptions: { cwd, stdio: "ignore" } }
875
+ );
876
+ }
848
877
 
849
878
  // src/lib/env.ts
850
879
  import fs7 from "node:fs";
@@ -869,6 +898,35 @@ function writeEnvFile(cwd, vars, comments = []) {
869
898
  for (const [key, value] of Object.entries(vars)) content = addEnvVar(content, key, value);
870
899
  fs7.writeFileSync(envPath, content);
871
900
  }
901
+ function upsertEnvVar(content, key, value) {
902
+ const line = `${key}=${quoteEnvValue(value)}`;
903
+ const pattern = keyPattern(key);
904
+ let replaced = false;
905
+ const lines = content.split("\n").map((existing) => {
906
+ if (replaced || !pattern.test(existing)) return existing;
907
+ replaced = true;
908
+ return existing.trimStart().startsWith("export ") ? `export ${line}` : line;
909
+ });
910
+ return replaced ? lines.join("\n") : appendLine(content, line);
911
+ }
912
+ function removeEnvVar(content, key) {
913
+ const pattern = keyPattern(key);
914
+ const lines = content.split("\n");
915
+ const kept = lines.filter((line) => !pattern.test(line));
916
+ if (kept.length === lines.length) return content;
917
+ return kept.join("\n");
918
+ }
919
+ function keyPattern(key) {
920
+ return new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=`);
921
+ }
922
+ function escapeRegExp(value) {
923
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
924
+ }
925
+ function quoteEnvValue(value) {
926
+ if (/^[A-Za-z0-9_./:@+-]*$/.test(value)) return value;
927
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n");
928
+ return `"${escaped}"`;
929
+ }
872
930
 
873
931
  // src/lib/config-merge.ts
874
932
  import fs9 from "node:fs";
@@ -1128,18 +1186,18 @@ function isVanillaRoutes(cwd) {
1128
1186
 
1129
1187
  // src/lib/result-report.ts
1130
1188
  import * as p3 from "@clack/prompts";
1131
- function reportResult(report) {
1189
+ function reportResult(report4) {
1132
1190
  const sections = [
1133
- ["Files created", report.filesCreated],
1134
- ["Files modified", report.filesModified],
1135
- ["Files deleted", report.filesDeleted],
1136
- ["UI components added", report.componentsAdded],
1137
- ["Packages installed", report.packagesInstalled],
1138
- ["Collections added", report.collectionsAdded],
1139
- ["Records loaded", report.recordsLoaded],
1140
- ["Records cleared", report.recordsCleared]
1191
+ ["Files created", report4.filesCreated],
1192
+ ["Files modified", report4.filesModified],
1193
+ ["Files deleted", report4.filesDeleted],
1194
+ ["UI components added", report4.componentsAdded],
1195
+ ["Packages installed", report4.packagesInstalled],
1196
+ ["Collections added", report4.collectionsAdded],
1197
+ ["Records loaded", report4.recordsLoaded],
1198
+ ["Records cleared", report4.recordsCleared]
1141
1199
  ];
1142
- for (const extra of report.sections ?? []) {
1200
+ for (const extra of report4.sections ?? []) {
1143
1201
  sections.push([extra.label, extra.items]);
1144
1202
  }
1145
1203
  const body = [];
@@ -1150,14 +1208,14 @@ function reportResult(report) {
1150
1208
  for (const item of items) body.push(`- ${item}`);
1151
1209
  }
1152
1210
  if (body.length > 0) {
1153
- p3.log.success(`${report.summary}
1211
+ p3.log.success(`${report4.summary}
1154
1212
 
1155
1213
  ${body.join("\n")}`);
1156
1214
  } else {
1157
- p3.log.success(report.summary);
1215
+ p3.log.success(report4.summary);
1158
1216
  }
1159
- if (report.nextSteps && report.nextSteps.length > 0) {
1160
- p3.note(report.nextSteps.map((s) => `- ${s}`).join("\n"), "Next steps", {
1217
+ if (report4.nextSteps && report4.nextSteps.length > 0) {
1218
+ p3.note(report4.nextSteps.map((s) => `- ${s}`).join("\n"), "Next steps", {
1161
1219
  format: (line) => line
1162
1220
  });
1163
1221
  }
@@ -1247,7 +1305,7 @@ async function blessProject(cwdArg, options) {
1247
1305
  const projectPath = cwdArg ? resolveProjectPath(cwdArg) : (await getWorkspace()).workspaceRootDir;
1248
1306
  assertNotAlreadyBlessed(projectPath);
1249
1307
  const templateDir = findProjectTemplate(options.template ?? DEFAULT_TEMPLATE).dir;
1250
- const { email: email3, password: password8 } = await p4.group(
1308
+ const { email: email3, password: password10 } = await p4.group(
1251
1309
  {
1252
1310
  email: () => {
1253
1311
  if (options.email) return Promise.resolve(options.email);
@@ -1288,12 +1346,12 @@ async function blessProject(cwdArg, options) {
1288
1346
  }
1289
1347
  }
1290
1348
  p4.log.step("Initializing PocketBase...");
1291
- await createSuperuser(projectPath, email3, password8);
1349
+ await createSuperuser(projectPath, email3, password10);
1292
1350
  writeEnvFile(
1293
1351
  projectPath,
1294
1352
  {
1295
1353
  POCKETBASE_SUPERUSER_EMAIL: email3,
1296
- POCKETBASE_SUPERUSER_PASSWORD: password8
1354
+ POCKETBASE_SUPERUSER_PASSWORD: password10
1297
1355
  },
1298
1356
  ["PocketBase superuser credentials \u2014 used by `vela` commands"]
1299
1357
  );
@@ -1618,9 +1676,9 @@ async function createProject(cwdArg, options) {
1618
1676
  }
1619
1677
  }
1620
1678
  if (credentials) {
1621
- const { email: email3, password: password8 } = credentials;
1679
+ const { email: email3, password: password10 } = credentials;
1622
1680
  p5.log.step("Initializing PocketBase...");
1623
- await createSuperuser(projectPath, email3, password8);
1681
+ await createSuperuser(projectPath, email3, password10);
1624
1682
  await withPocketbase(
1625
1683
  projectPath,
1626
1684
  async (pb) => {
@@ -1628,13 +1686,13 @@ async function createProject(cwdArg, options) {
1628
1686
  meta: { appName: name, appURL: "http://localhost:5173" }
1629
1687
  });
1630
1688
  },
1631
- { email: email3, password: password8 }
1689
+ { email: email3, password: password10 }
1632
1690
  );
1633
1691
  writeEnvFile(
1634
1692
  projectPath,
1635
1693
  {
1636
1694
  POCKETBASE_SUPERUSER_EMAIL: email3,
1637
- POCKETBASE_SUPERUSER_PASSWORD: password8
1695
+ POCKETBASE_SUPERUSER_PASSWORD: password10
1638
1696
  },
1639
1697
  ["PocketBase superuser credentials \u2014 used by `vela` commands"]
1640
1698
  );
@@ -1688,13 +1746,13 @@ import { bySlug } from "@velastack/patterns";
1688
1746
  function toRelative(root, filePath) {
1689
1747
  return path13.isAbsolute(filePath) ? path13.relative(root, filePath) : filePath;
1690
1748
  }
1691
- async function runPattern(slug, argv2, input, report) {
1692
- const pattern = bySlug[slug];
1749
+ async function runPattern(slug2, argv2, input, report4) {
1750
+ const pattern = bySlug[slug2];
1693
1751
  if (!pattern) {
1694
- throw new Error(`Unknown pattern: ${slug}`);
1752
+ throw new Error(`Unknown pattern: ${slug2}`);
1695
1753
  }
1696
1754
  const { workspaceRootDir, features } = await getWorkspace();
1697
- const log18 = p6.taskLog({ title: report.task.title });
1755
+ const log31 = p6.taskLog({ title: report4.task.title });
1698
1756
  let result;
1699
1757
  try {
1700
1758
  result = await pattern.generate({
@@ -1714,28 +1772,28 @@ async function runPattern(slug, argv2, input, report) {
1714
1772
  });
1715
1773
  return collections2;
1716
1774
  },
1717
- logger: { info: (message) => log18.message(message) }
1775
+ logger: { info: (message) => log31.message(message) }
1718
1776
  });
1719
- log18.success(report.task.success);
1777
+ log31.success(report4.task.success);
1720
1778
  } catch (e) {
1721
- log18.error(report.task.error);
1779
+ log31.error(report4.task.error);
1722
1780
  throw e;
1723
1781
  }
1724
1782
  const rel = (f) => toRelative(workspaceRootDir, f);
1725
1783
  const totalChanges = result.creates.length + result.modifies.length + result.deletes.length + result.components.length + result.packages.length + result.collections.length;
1726
1784
  if (totalChanges === 0) {
1727
- p6.log.info(`${pattern.title ?? slug} produced no changes.`);
1785
+ p6.log.info(`${pattern.title ?? slug2} produced no changes.`);
1728
1786
  return;
1729
1787
  }
1730
1788
  reportResult({
1731
- summary: report.summary ?? `Applied ${pattern.title ?? slug}.`,
1789
+ summary: report4.summary ?? `Applied ${pattern.title ?? slug2}.`,
1732
1790
  filesCreated: result.creates.map((f) => rel(f.path)),
1733
1791
  filesModified: result.modifies.map((f) => rel(f.path)),
1734
1792
  filesDeleted: result.deletes.map((f) => rel(f.path)),
1735
1793
  componentsAdded: result.components,
1736
1794
  packagesInstalled: result.packages,
1737
1795
  collectionsAdded: result.collections.map((c) => c.name),
1738
- nextSteps: report.nextSteps
1796
+ nextSteps: report4.nextSteps
1739
1797
  });
1740
1798
  }
1741
1799
 
@@ -1800,7 +1858,14 @@ function readProjectConfig(workspaceRootDir) {
1800
1858
  function writeProjectConfig(workspaceRootDir, config) {
1801
1859
  const file = projectConfigPath(workspaceRootDir);
1802
1860
  fs15.mkdirSync(path15.dirname(file), { recursive: true });
1803
- fs15.writeFileSync(file, JSON.stringify(config, null, 2) + "\n");
1861
+ let existing = {};
1862
+ if (fs15.existsSync(file)) {
1863
+ try {
1864
+ existing = JSON.parse(fs15.readFileSync(file, "utf8"));
1865
+ } catch {
1866
+ }
1867
+ }
1868
+ fs15.writeFileSync(file, JSON.stringify({ ...existing, ...config }, null, 2) + "\n");
1804
1869
  }
1805
1870
 
1806
1871
  // src/lib/ai-client.ts
@@ -2117,7 +2182,7 @@ var form = new Command4("form").description("generate a form from a model").argu
2117
2182
  argv2 = [model, ...fields];
2118
2183
  modelName = model;
2119
2184
  }
2120
- const slug = options.remote ? "generate-form-remote" : "generate-form";
2185
+ const slug2 = options.remote ? "generate-form-remote" : "generate-form";
2121
2186
  const nextSteps = [
2122
2187
  "Edit the form fields and validation in the generated +page.svelte.",
2123
2188
  "Run `vela dev` to preview the form in the browser."
@@ -2128,7 +2193,7 @@ var form = new Command4("form").description("generate a form from a model").argu
2128
2193
  );
2129
2194
  }
2130
2195
  await runPattern(
2131
- slug,
2196
+ slug2,
2132
2197
  argv2,
2133
2198
  { route: options.route },
2134
2199
  {
@@ -2253,7 +2318,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2253
2318
  argv2 = [model, ...fields];
2254
2319
  modelName = model;
2255
2320
  }
2256
- const slug = options.remote ? "generate-scaffold-remote" : "generate-scaffold";
2321
+ const slug2 = options.remote ? "generate-scaffold-remote" : "generate-scaffold";
2257
2322
  const nextSteps = [
2258
2323
  `Run \`vela fixtures generate\` to create 10 ${modelName} records for development.`,
2259
2324
  "Run `vela dev` and visit the generated route to use the scaffold.",
@@ -2265,7 +2330,7 @@ var scaffold = new Command7("scaffold").description("generate a full CRUD scaffo
2265
2330
  );
2266
2331
  }
2267
2332
  await runPattern(
2268
- slug,
2333
+ slug2,
2269
2334
  argv2,
2270
2335
  { route: options.route },
2271
2336
  {
@@ -2560,7 +2625,7 @@ async function ensureKey(envVar, message) {
2560
2625
  async function promptPassword(message) {
2561
2626
  const value = await p14.password({
2562
2627
  message,
2563
- validate: (v4) => v4 && v4.length ? void 0 : "Required"
2628
+ validate: (v9) => v9 && v9.length ? void 0 : "Required"
2564
2629
  });
2565
2630
  if (p14.isCancel(value)) {
2566
2631
  p14.cancel("Operation cancelled.");
@@ -2607,24 +2672,24 @@ var s3 = new Command18("s3").description("enable S3 file storage").configureHelp
2607
2672
  endpoint: () => p15.text({
2608
2673
  message: "S3 endpoint URL",
2609
2674
  initialValue: "https://s3.amazonaws.com",
2610
- validate: (v4) => v4 ? void 0 : "Endpoint is required"
2675
+ validate: (v9) => v9 ? void 0 : "Endpoint is required"
2611
2676
  }),
2612
2677
  accessKey: () => p15.text({
2613
2678
  message: "S3 access key",
2614
- validate: (v4) => v4 ? void 0 : "Access key is required"
2679
+ validate: (v9) => v9 ? void 0 : "Access key is required"
2615
2680
  }),
2616
2681
  secret: () => p15.password({
2617
2682
  message: "S3 secret key",
2618
- validate: (v4) => v4 ? void 0 : "Secret key is required"
2683
+ validate: (v9) => v9 ? void 0 : "Secret key is required"
2619
2684
  }),
2620
2685
  bucket: () => p15.text({
2621
2686
  message: "S3 bucket name",
2622
- validate: (v4) => v4 ? void 0 : "Bucket name is required"
2687
+ validate: (v9) => v9 ? void 0 : "Bucket name is required"
2623
2688
  }),
2624
2689
  region: () => p15.text({
2625
2690
  message: "S3 region",
2626
2691
  initialValue: "us-east-1",
2627
- validate: (v4) => v4 ? void 0 : "Region is required"
2692
+ validate: (v9) => v9 ? void 0 : "Region is required"
2628
2693
  })
2629
2694
  },
2630
2695
  {
@@ -2678,14 +2743,14 @@ var smtp = new Command19("smtp").description("configure SMTP for transactional e
2678
2743
  host: () => p16.text({
2679
2744
  message: "SMTP host",
2680
2745
  placeholder: "smtp.example.com",
2681
- validate: (v4) => v4 ? void 0 : "Host is required"
2746
+ validate: (v9) => v9 ? void 0 : "Host is required"
2682
2747
  }),
2683
2748
  port: () => p16.text({
2684
2749
  message: "SMTP port",
2685
2750
  initialValue: "587",
2686
- validate: (v4) => {
2687
- if (!v4) return "Port is required";
2688
- const n = Number(v4);
2751
+ validate: (v9) => {
2752
+ if (!v9) return "Port is required";
2753
+ const n = Number(v9);
2689
2754
  if (!Number.isInteger(n) || n <= 0 || n > 65535) return "Invalid port";
2690
2755
  return void 0;
2691
2756
  }
@@ -3046,7 +3111,7 @@ var smtp2 = new Command32("smtp").description("disable SMTP configuration").conf
3046
3111
  var disable = new Command33("disable").description("disable features").configureHelp(helpConfig).addCommand(auth2).addCommand(api2).addCommand(apiKeys2).addCommand(backend2).addCommand(contentNegotiation2).addCommand(i18n2).addCommand(teams2).addCommand(payments2).addCommand(s32).addCommand(smtp2);
3047
3112
 
3048
3113
  // src/commands/destroy.ts
3049
- import { Command as Command38 } from "commander";
3114
+ import { Command as Command39 } from "commander";
3050
3115
 
3051
3116
  // src/commands/destroy/form.ts
3052
3117
  import { Command as Command34 } from "commander";
@@ -3054,7 +3119,7 @@ import { Command as Command34 } from "commander";
3054
3119
  // src/commands/destroy/_shared.ts
3055
3120
  import process12 from "node:process";
3056
3121
  import * as p18 from "@clack/prompts";
3057
- async function runDestroy(slug, model, confirmMessage, report, flags) {
3122
+ async function runDestroy(slug2, model, confirmMessage, report4, flags) {
3058
3123
  if (!flags.yes) {
3059
3124
  const ok = await p18.confirm({ message: confirmMessage, initialValue: false });
3060
3125
  if (p18.isCancel(ok) || !ok) {
@@ -3062,7 +3127,7 @@ async function runDestroy(slug, model, confirmMessage, report, flags) {
3062
3127
  process12.exit(0);
3063
3128
  }
3064
3129
  }
3065
- await runPattern(slug, [model], { destructive: true, route: flags.route }, report);
3130
+ await runPattern(slug2, [model], { destructive: true, route: flags.route }, report4);
3066
3131
  }
3067
3132
 
3068
3133
  // src/commands/destroy/form.ts
@@ -3161,198 +3226,1111 @@ var scaffold2 = new Command37("scaffold").description("destroy a scaffold genera
3161
3226
  )
3162
3227
  );
3163
3228
 
3164
- // src/commands/destroy.ts
3165
- var destroy = new Command38("destroy").description("destroy scaffolding").configureHelp(helpConfig).addCommand(form2).addCommand(schema2).addCommand(resource2).addCommand(scaffold2);
3166
-
3167
- // src/commands/ui.ts
3168
- import { Command as Command41 } from "commander";
3229
+ // src/commands/destroy/deployment.ts
3230
+ import process15 from "node:process";
3231
+ import { Command as Command38 } from "commander";
3232
+ import * as p20 from "@clack/prompts";
3233
+ import pc6 from "picocolors";
3169
3234
 
3170
- // src/commands/ui/add.ts
3171
- import { Command as Command39 } from "commander";
3172
- import { exec as exec2 } from "tinyexec";
3173
- import { detect as detect4, resolveCommand as resolveCommand4 } from "package-manager-detector";
3174
- var add = new Command39("add").description("add ui components").argument("<components...>", "the components to add").configureHelp(helpConfig).action(
3175
- (components) => runCommand(async () => {
3176
- const { workspaceRootDir } = await getWorkspace();
3177
- const packageManager = (await detect4({ cwd: workspaceRootDir }))?.name ?? "npm";
3178
- const resolved = resolveCommand4(packageManager, "execute", [
3179
- "shadcn-svelte",
3180
- "add",
3181
- ...components
3182
- ]);
3183
- if (!resolved) {
3184
- throw new Error(`Unable to resolve execute command for ${packageManager}`);
3185
- }
3186
- const args = [...resolved.args];
3187
- if (packageManager === "npm") args.unshift("--yes");
3188
- try {
3189
- await exec2(resolved.command, args, {
3190
- nodeOptions: { cwd: workspaceRootDir, stdio: "inherit" },
3191
- throwOnError: true
3192
- });
3193
- } catch (error) {
3194
- const typed = error;
3195
- throw new Error(
3196
- `Failed to execute '${resolved.command} ${args.join(" ")}': ${typed.message}`,
3197
- { cause: typed.output }
3198
- );
3199
- }
3200
- reportResult({
3201
- summary: `Added ${components.length} UI component(s).`,
3202
- componentsAdded: components,
3203
- nextSteps: [
3204
- `Import a component with: import { Button } from '$lib/components/ui/button';`,
3205
- "Tweak styling in src/lib/components/ui/<component>/*.svelte.",
3206
- "Run `vela ui base <color>` to change the palette (slate, gray, zinc, stone, neutral)."
3207
- ]
3208
- });
3209
- }, "Failed to add UI components.")
3210
- );
3235
+ // src/lib/server-command.ts
3236
+ import process14 from "node:process";
3237
+ import * as v5 from "valibot";
3238
+ import * as p19 from "@clack/prompts";
3239
+ import pc5 from "picocolors";
3211
3240
 
3212
- // src/commands/ui/base.ts
3241
+ // src/lib/deploy-config.ts
3213
3242
  import fs17 from "node:fs";
3214
3243
  import path17 from "node:path";
3215
- import { Command as Command40, InvalidArgumentError } from "commander";
3216
- var VALID_COLORS = ["slate", "gray", "zinc", "stone", "neutral"];
3217
- function findThemeFile(color) {
3218
- const candidate = path17.join(templatesDir(), "ui", "css", `${color}.css`);
3219
- if (!fs17.existsSync(candidate)) {
3220
- throw new Error(`Theme file not found for color: ${color}`);
3244
+ import crypto from "node:crypto";
3245
+ import { pathToFileURL } from "node:url";
3246
+ var CONFIG_BASENAMES = [
3247
+ "velastack.config.ts",
3248
+ "velastack.config.js",
3249
+ "velastack.config.mjs",
3250
+ "velastack.config.json"
3251
+ ];
3252
+ function findConfigFile(workspaceRootDir) {
3253
+ for (const name of CONFIG_BASENAMES) {
3254
+ const file = path17.join(workspaceRootDir, name);
3255
+ if (fs17.existsSync(file)) return file;
3221
3256
  }
3222
- return candidate;
3257
+ return null;
3223
3258
  }
3224
- function extractSelectors(css) {
3225
- const rootMatch = css.match(/:root\s*{[^}]*}/);
3226
- const darkMatch = css.match(/\.dark\s*{[^}]*}/);
3227
- if (!rootMatch || !darkMatch) {
3228
- throw new Error("Invalid theme file: missing :root or .dark selector");
3259
+ async function loadDeployConfig(workspaceRootDir) {
3260
+ const file = findConfigFile(workspaceRootDir);
3261
+ if (!file) return {};
3262
+ if (file.endsWith(".json")) {
3263
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
3264
+ }
3265
+ const url = file.endsWith(".ts") ? await transpileToTemp(file) : pathToFileURL(file).href;
3266
+ try {
3267
+ const mod = await import(url);
3268
+ const config = mod.default;
3269
+ if (!config || typeof config !== "object") {
3270
+ throw new Error(`${path17.basename(file)} must export a config object as its default export.`);
3271
+ }
3272
+ return config;
3273
+ } finally {
3274
+ if (url !== pathToFileURL(file).href) fs17.rmSync(new URL(url), { force: true });
3229
3275
  }
3230
- return { root: rootMatch[0], dark: darkMatch[0] };
3231
3276
  }
3232
- function applyTheme(appCss, selectors) {
3233
- let out = appCss;
3234
- out = out.match(/:root\s*{[^}]*}/) ? out.replace(/:root\s*{[^}]*}/, selectors.root) : `${selectors.root}
3235
-
3236
- ${out}`;
3237
- out = out.match(/\.dark\s*{[^}]*}/) ? out.replace(/\.dark\s*{[^}]*}/, selectors.dark) : `${out}
3238
-
3239
- ${selectors.dark}`;
3240
- return out;
3277
+ async function transpileToTemp(file) {
3278
+ const { ts } = await import("ts-morph");
3279
+ const source = fs17.readFileSync(file, "utf8");
3280
+ const { outputText } = ts.transpileModule(source, {
3281
+ compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 }
3282
+ });
3283
+ const temp = path17.join(
3284
+ path17.dirname(file),
3285
+ `.velastack.config.${crypto.randomBytes(4).toString("hex")}.mjs`
3286
+ );
3287
+ fs17.writeFileSync(temp, outputText);
3288
+ return pathToFileURL(temp).href;
3241
3289
  }
3242
- var base = new Command40("base").description("change the base color").argument("<color>", "base color to use", (value) => {
3243
- if (!VALID_COLORS.includes(value)) {
3244
- throw new InvalidArgumentError(`Valid colors are: ${VALID_COLORS.join(", ")}`);
3290
+ function projectFilePath(workspaceRootDir) {
3291
+ return path17.join(workspaceRootDir, ".vela", "project.json");
3292
+ }
3293
+ function readProjectFile(workspaceRootDir) {
3294
+ const file = projectFilePath(workspaceRootDir);
3295
+ if (!fs17.existsSync(file)) return {};
3296
+ try {
3297
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
3298
+ } catch {
3299
+ return {};
3300
+ }
3301
+ }
3302
+ function writeProjectFile(workspaceRootDir, data) {
3303
+ const file = projectFilePath(workspaceRootDir);
3304
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
3305
+ fs17.writeFileSync(file, JSON.stringify(data, null, 2) + "\n");
3306
+ }
3307
+ function resolveAppIdentity(workspaceRootDir, config = {}) {
3308
+ const project = readProjectFile(workspaceRootDir);
3309
+ const name = config.project ?? project.projectName ?? defaultProjectName(workspaceRootDir);
3310
+ if (project.appId) return { appId: project.appId, name };
3311
+ const appId = project.projectId ?? `${slug(name)}-${crypto.randomBytes(4).toString("hex")}`;
3312
+ writeProjectFile(workspaceRootDir, {
3313
+ ...project,
3314
+ appId,
3315
+ projectName: project.projectName ?? name
3316
+ });
3317
+ return { appId, name };
3318
+ }
3319
+ function readAppIdentity(workspaceRootDir, config = {}) {
3320
+ const project = readProjectFile(workspaceRootDir);
3321
+ const appId = project.appId ?? project.projectId;
3322
+ if (!appId) return null;
3323
+ return {
3324
+ appId,
3325
+ name: config.project ?? project.projectName ?? defaultProjectName(workspaceRootDir)
3326
+ };
3327
+ }
3328
+ function defaultProjectName(workspaceRootDir) {
3329
+ try {
3330
+ const pkg = readPackageJson(path17.join(workspaceRootDir, "package.json"));
3331
+ if (typeof pkg.name === "string" && pkg.name.trim()) return pkg.name.trim();
3332
+ } catch {
3245
3333
  }
3246
- return value;
3247
- }).configureHelp(helpConfig).action(
3248
- (color) => runCommand(async () => {
3249
- const { workspaceRootDir } = await getWorkspace();
3250
- const appCssPath = path17.join(workspaceRootDir, "src", "app.css");
3251
- if (!fs17.existsSync(appCssPath)) {
3252
- throw new Error(`Could not find ${path17.relative(workspaceRootDir, appCssPath)}`);
3253
- }
3254
- const themeContent = fs17.readFileSync(findThemeFile(color), "utf8");
3255
- const selectors = extractSelectors(themeContent);
3256
- const appCss = fs17.readFileSync(appCssPath, "utf8");
3257
- fs17.writeFileSync(appCssPath, applyTheme(appCss, selectors));
3258
- reportResult({
3259
- summary: `Set base color to ${color}.`,
3260
- filesModified: [path17.relative(workspaceRootDir, appCssPath)],
3261
- nextSteps: [
3262
- "Run your dev server to preview the new palette.",
3263
- "Tweak individual CSS variables in src/app.css if you want to customize the theme further."
3264
- ]
3265
- });
3266
- }, "Failed to change base color.")
3267
- );
3334
+ return path17.basename(workspaceRootDir);
3335
+ }
3336
+ function slug(value) {
3337
+ return value.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "app";
3338
+ }
3339
+ function readBinding(workspaceRootDir, envTag) {
3340
+ const project = readProjectFile(workspaceRootDir);
3341
+ const bound = project.targets?.[envTag];
3342
+ if (bound?.server) return bound;
3343
+ const legacy = project.deployments?.[envTag];
3344
+ if (legacy?.target) return { server: legacy.target, domain: legacy.domain };
3345
+ return null;
3346
+ }
3347
+ function readBindings(workspaceRootDir) {
3348
+ const project = readProjectFile(workspaceRootDir);
3349
+ const bindings = {};
3350
+ for (const [envTag, legacy] of Object.entries(project.deployments ?? {})) {
3351
+ if (legacy?.target) bindings[envTag] = { server: legacy.target, domain: legacy.domain };
3352
+ }
3353
+ for (const [envTag, bound] of Object.entries(project.targets ?? {})) {
3354
+ if (bound?.server) bindings[envTag] = bound;
3355
+ }
3356
+ return bindings;
3357
+ }
3358
+ function writeBinding(workspaceRootDir, envTag, binding) {
3359
+ const project = readProjectFile(workspaceRootDir);
3360
+ writeProjectFile(workspaceRootDir, {
3361
+ ...project,
3362
+ targets: { ...project.targets, [envTag]: binding }
3363
+ });
3364
+ }
3268
3365
 
3269
- // src/commands/ui.ts
3270
- var ui = new Command41("ui").description("generate ui components").configureHelp(helpConfig).addCommand(add).addCommand(base);
3366
+ // src/lib/instance.ts
3367
+ var PROD_ENV = "prod";
3368
+ var MAX_SEGMENT = 48;
3369
+ function normalizeEnvTag(tag) {
3370
+ const raw = (tag ?? PROD_ENV).trim();
3371
+ if (!raw) return PROD_ENV;
3372
+ const normalized = raw.toLowerCase().replace(/\//g, "--").replace(/[^a-z0-9-]+/g, "-").replace(/-{3,}/g, "--").replace(/^-+|-+$/g, "");
3373
+ if (!normalized) throw new Error(`Invalid environment tag: ${tag}`);
3374
+ if (normalized === "local") {
3375
+ throw new Error("`local` is not a deployable environment \u2014 it is the copy on this machine.");
3376
+ }
3377
+ return normalized === "production" ? PROD_ENV : normalized;
3378
+ }
3379
+ function branchToEnvTag(branch) {
3380
+ const slug2 = branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SEGMENT);
3381
+ return `preview--${slug2}`;
3382
+ }
3383
+ function instanceId(appId, envTag = PROD_ENV) {
3384
+ const env2 = normalizeEnvTag(envTag);
3385
+ return env2 === PROD_ENV ? appId : `${appId}--${env2}`;
3386
+ }
3387
+ function isProd(envTag) {
3388
+ return normalizeEnvTag(envTag) === PROD_ENV;
3389
+ }
3390
+ function releaseId(date = /* @__PURE__ */ new Date()) {
3391
+ return date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
3392
+ }
3271
3393
 
3272
- // src/commands/legal.ts
3273
- import { Command as Command44 } from "commander";
3394
+ // src/lib/ssh-options.ts
3395
+ import * as v4 from "valibot";
3396
+ var SSH_OPTION_SCHEMA = {
3397
+ identity: v4.optional(v4.string()),
3398
+ sshPort: v4.optional(v4.string()),
3399
+ acceptHostKeys: v4.optional(v4.boolean())
3400
+ };
3401
+ function addSshOptions(command) {
3402
+ return command.option("-i, --identity <file>", "SSH private key to authenticate with").option("--ssh-port <port>", "SSH port").option("--accept-host-keys", "trust an unknown host key on first connect (CI)");
3403
+ }
3404
+ function sshOptionsFrom(options) {
3405
+ return {
3406
+ identityFile: options.identity,
3407
+ port: options.sshPort ? Number(options.sshPort) : void 0,
3408
+ acceptNewHostKeys: options.acceptHostKeys
3409
+ };
3410
+ }
3274
3411
 
3275
- // src/commands/legal/terms.ts
3412
+ // src/lib/ssh.ts
3276
3413
  import fs18 from "node:fs";
3414
+ import os2 from "node:os";
3277
3415
  import path18 from "node:path";
3278
- import { Command as Command42 } from "commander";
3279
- import * as p20 from "@clack/prompts";
3280
-
3281
- // src/commands/legal/shared.ts
3416
+ import crypto2 from "node:crypto";
3282
3417
  import process13 from "node:process";
3283
- import * as p19 from "@clack/prompts";
3284
- var sharedFields = {
3285
- websiteUrl: () => p19.text({
3286
- message: "What is your website URL?",
3287
- placeholder: "http://www.mysite.com",
3288
- validate: (value) => {
3289
- if (!value || !value.startsWith("http")) {
3290
- return "Please enter a valid URL starting with http or https";
3291
- }
3418
+ import { spawn as spawn2 } from "node:child_process";
3419
+ var RemoteCommandError = class extends Error {
3420
+ exitCode;
3421
+ stdout;
3422
+ stderr;
3423
+ constructor(message, result) {
3424
+ super(message);
3425
+ this.name = "RemoteCommandError";
3426
+ this.exitCode = result.exitCode;
3427
+ this.stdout = result.stdout;
3428
+ this.stderr = result.stderr;
3429
+ }
3430
+ };
3431
+ var SshSession = class {
3432
+ target;
3433
+ elevation = "none";
3434
+ options;
3435
+ controlPath = null;
3436
+ constructor(target, options = {}) {
3437
+ this.target = target;
3438
+ this.options = options;
3439
+ }
3440
+ sshArgs() {
3441
+ const args = [];
3442
+ if (this.controlPath) args.push("-o", `ControlPath=${this.controlPath}`);
3443
+ if (this.options.identityFile) {
3444
+ args.push("-i", this.options.identityFile, "-o", "IdentitiesOnly=yes");
3292
3445
  }
3293
- }),
3294
- websiteName: () => p19.text({
3295
- message: "What is your website name?",
3296
- placeholder: "My Site",
3297
- validate: (value) => {
3298
- if (!value) {
3299
- return "Please enter a website name";
3300
- }
3446
+ if (this.options.port) args.push("-p", String(this.options.port));
3447
+ if (this.options.acceptNewHostKeys) args.push("-o", "StrictHostKeyChecking=accept-new");
3448
+ args.push("-o", "BatchMode=yes");
3449
+ return args;
3450
+ }
3451
+ /** The `ssh` invocation rsync should use, as a single shell word list. */
3452
+ rsyncShell() {
3453
+ const parts = ["ssh", ...this.sshArgs()];
3454
+ return parts.map(shellQuote).join(" ");
3455
+ }
3456
+ async open() {
3457
+ if (this.controlPath) return;
3458
+ const dir = path18.join(os2.tmpdir(), "vela-ssh");
3459
+ fs18.mkdirSync(dir, { recursive: true, mode: 448 });
3460
+ const socket = path18.join(dir, `${crypto2.randomBytes(6).toString("hex")}.sock`);
3461
+ this.controlPath = socket;
3462
+ const args = [
3463
+ ...this.sshArgs(),
3464
+ "-o",
3465
+ "ControlMaster=yes",
3466
+ "-o",
3467
+ "ControlPersist=60",
3468
+ "-N",
3469
+ "-f",
3470
+ this.target
3471
+ ];
3472
+ const result = await spawnCapture("ssh", args);
3473
+ if (result.exitCode !== 0) {
3474
+ this.controlPath = null;
3475
+ throw new Error(
3476
+ `Could not connect to ${this.target} over SSH.
3477
+
3478
+ ${result.stderr.trim() || result.stdout.trim()}`
3479
+ );
3301
3480
  }
3302
- }),
3303
- entityType: () => p19.select({
3304
- message: "Entity type",
3305
- options: [
3306
- {
3307
- value: "business",
3308
- label: "I'm a Business",
3309
- hint: "e.g. Corporation, Limited Liability Company, Non-profit, Partnership, Sole Proprietor"
3310
- },
3311
- { value: "individual", label: "I'm an Individual" }
3312
- ]
3313
- }),
3314
- businessName: ({ results }) => results?.entityType === "business" ? p19.text({
3315
- message: "What is the name of the business?",
3316
- placeholder: "My Company LLC",
3317
- validate: (value) => {
3318
- if (!value) {
3319
- return "Please enter a business name";
3320
- }
3481
+ }
3482
+ async close() {
3483
+ if (!this.controlPath) return;
3484
+ const socket = this.controlPath;
3485
+ this.controlPath = null;
3486
+ await spawnCapture("ssh", ["-o", `ControlPath=${socket}`, "-O", "exit", this.target]);
3487
+ }
3488
+ /**
3489
+ * Run a bash program on the server. The program is piped over stdin, so it can
3490
+ * safely embed anything — quoting, newlines, base64 blobs and `args` arrive
3491
+ * as ordinary positional parameters.
3492
+ */
3493
+ async script(source, opts = {}) {
3494
+ const shell = this.elevation === "sudo" ? "sudo -n bash -s" : "bash -s";
3495
+ const remote = [shell, "--", ...(opts.args ?? []).map(shellQuote)].join(" ");
3496
+ const args = [...this.sshArgs(), this.target, remote];
3497
+ const result = await spawnCapture("ssh", args, {
3498
+ stdin: `set -euo pipefail
3499
+ ${source}`,
3500
+ stream: opts.stream
3501
+ });
3502
+ if (opts.check !== false && result.exitCode !== 0) {
3503
+ throw new RemoteCommandError(remoteFailure(this.target, result), result);
3321
3504
  }
3322
- }) : void 0,
3323
- businessAddress: ({ results }) => results?.entityType === "business" ? p19.text({
3324
- message: "What is the address of the business?",
3325
- placeholder: "1 Cupertino, CA 95014",
3326
- validate: (value) => {
3327
- if (!value) {
3328
- return "Please enter a business address";
3329
- }
3505
+ return result;
3506
+ }
3507
+ /** Attach the terminal to a remote command — used by `vela logs -f`. */
3508
+ async interactive(command) {
3509
+ const args = [...this.sshArgs(), "-t", this.target, command.map(shellQuote).join(" ")];
3510
+ return await new Promise((resolve2) => {
3511
+ const child = spawn2("ssh", args, { stdio: "inherit" });
3512
+ child.on("close", (code) => resolve2(code ?? 1));
3513
+ });
3514
+ }
3515
+ /**
3516
+ * Write a file on the server atomically, with an explicit mode.
3517
+ *
3518
+ * The payload rides inside the piped script as base64, so it never lands in
3519
+ * argv (visible in `ps`) or in a shell history, and binary-unsafe characters
3520
+ * are not an issue.
3521
+ */
3522
+ async writeFile(remotePath, content, mode = "0644") {
3523
+ const payload = Buffer.from(content, "utf8").toString("base64");
3524
+ await this.script(
3525
+ `dest="$1"; mode="$2"
3526
+ mkdir -p "$(dirname "$dest")"
3527
+ tmp=$(mktemp "$(dirname "$dest")/.vela.XXXXXX")
3528
+ printf %s ${payload} | base64 -d > "$tmp"
3529
+ chmod "$mode" "$tmp"
3530
+ mv -f "$tmp" "$dest"`,
3531
+ { args: [remotePath, mode] }
3532
+ );
3533
+ }
3534
+ /**
3535
+ * Tunnel a local port to a port on the server, through the connection that is
3536
+ * already open. Used to point a local build at the target's database.
3537
+ */
3538
+ async forwardLocalPort(localPort, remoteHost, remotePort) {
3539
+ if (!this.controlPath) throw new Error("Cannot forward a port without an open session.");
3540
+ const spec = `${localPort}:${remoteHost}:${remotePort}`;
3541
+ const result = await spawnCapture("ssh", [
3542
+ ...this.sshArgs(),
3543
+ "-O",
3544
+ "forward",
3545
+ "-L",
3546
+ spec,
3547
+ this.target
3548
+ ]);
3549
+ if (result.exitCode !== 0) {
3550
+ throw new Error(
3551
+ `Could not forward port ${spec} to ${this.target}.
3552
+
3553
+ ${result.stderr.trim()}`
3554
+ );
3330
3555
  }
3331
- }) : void 0,
3332
- country: () => p19.text({
3333
- message: "Enter the country",
3334
- validate: (value) => {
3335
- if (!value) {
3336
- return "Please enter a country";
3337
- }
3556
+ }
3557
+ async cancelForward(localPort, remoteHost, remotePort) {
3558
+ if (!this.controlPath) return;
3559
+ await spawnCapture("ssh", [
3560
+ ...this.sshArgs(),
3561
+ "-O",
3562
+ "cancel",
3563
+ "-L",
3564
+ `${localPort}:${remoteHost}:${remotePort}`,
3565
+ this.target
3566
+ ]);
3567
+ }
3568
+ async readFile(remotePath) {
3569
+ const result = await this.script(`cat "$1" 2>/dev/null || exit 44`, {
3570
+ args: [remotePath],
3571
+ check: false
3572
+ });
3573
+ if (result.exitCode === 44) return null;
3574
+ if (result.exitCode !== 0)
3575
+ throw new RemoteCommandError(remoteFailure(this.target, result), result);
3576
+ return result.stdout;
3577
+ }
3578
+ /** rsync a local directory (contents) into a remote directory. */
3579
+ async uploadDir(localDir, remoteDir, extraArgs = []) {
3580
+ const src = localDir.endsWith("/") ? localDir : `${localDir}/`;
3581
+ const args = [
3582
+ "-az",
3583
+ "--delete",
3584
+ "-e",
3585
+ this.rsyncShell(),
3586
+ ...this.rsyncPath(),
3587
+ ...extraArgs,
3588
+ src,
3589
+ `${this.target}:${remoteDir}`
3590
+ ];
3591
+ const result = await spawnCapture("rsync", args);
3592
+ if (result.exitCode !== 0) {
3593
+ throw new Error(`rsync to ${this.target}:${remoteDir} failed.
3594
+
3595
+ ${result.stderr.trim()}`);
3338
3596
  }
3339
- }),
3340
- state: () => p19.text({
3341
- message: "Enter the state",
3342
- validate: (value) => {
3343
- if (!value) {
3344
- return "Please enter a state";
3345
- }
3597
+ }
3598
+ rsyncPath() {
3599
+ return this.elevation === "sudo" ? ["--rsync-path=sudo -n rsync"] : [];
3600
+ }
3601
+ /** rsync one or more local paths to a remote directory. */
3602
+ async upload(localPaths, remoteDir, extraArgs = []) {
3603
+ const args = [
3604
+ "-az",
3605
+ "-e",
3606
+ this.rsyncShell(),
3607
+ ...this.rsyncPath(),
3608
+ ...extraArgs,
3609
+ ...localPaths,
3610
+ `${this.target}:${remoteDir}`
3611
+ ];
3612
+ const result = await spawnCapture("rsync", args);
3613
+ if (result.exitCode !== 0) {
3614
+ throw new Error(`rsync to ${this.target}:${remoteDir} failed.
3615
+
3616
+ ${result.stderr.trim()}`);
3346
3617
  }
3347
- })
3348
- };
3349
- var onCancel = () => {
3350
- p19.cancel("Operation cancelled.");
3351
- process13.exit(0);
3618
+ }
3619
+ /**
3620
+ * Decide once whether remote commands need `sudo`, and fail with something
3621
+ * actionable if they do but cannot get it.
3622
+ */
3623
+ async detectElevation() {
3624
+ const uid = await this.script("id -u", { check: false });
3625
+ if (uid.stdout.trim() === "0") {
3626
+ this.elevation = "none";
3627
+ return this.elevation;
3628
+ }
3629
+ const sudo = await this.script("sudo -n true", { check: false });
3630
+ if (sudo.exitCode !== 0) {
3631
+ throw new Error(
3632
+ `${this.target} connects as a non-root user without passwordless sudo.
3633
+
3634
+ Use an SSH target that logs in as root, or give the user NOPASSWD sudo.`
3635
+ );
3636
+ }
3637
+ this.elevation = "sudo";
3638
+ return this.elevation;
3639
+ }
3352
3640
  };
3353
- async function contactMethods(type) {
3354
- const selection = await p19.multiselect({
3355
- message: `How can users contact you for any questions regarding your ${type === "privacy" ? "Privacy Policy" : "Terms & Conditions"}? Check all that apply`,
3641
+ function remoteFailure(target, result) {
3642
+ const detail = (result.stderr.trim() || result.stdout.trim()).split("\n").slice(-12).join("\n");
3643
+ return `Remote command failed on ${target} (exit ${result.exitCode}).
3644
+
3645
+ ${detail}`;
3646
+ }
3647
+ async function withSsh(target, options, fn) {
3648
+ const session = new SshSession(target, options);
3649
+ await session.open();
3650
+ try {
3651
+ return await fn(session);
3652
+ } finally {
3653
+ await session.close();
3654
+ }
3655
+ }
3656
+ function shellQuote(value) {
3657
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
3658
+ return `'${value.replaceAll("'", `'\\''`)}'`;
3659
+ }
3660
+ function spawnCapture(command, args, opts = {}) {
3661
+ return new Promise((resolve2, reject) => {
3662
+ const stdio = [opts.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"];
3663
+ const child = spawn2(command, args, {
3664
+ stdio,
3665
+ cwd: opts.cwd,
3666
+ env: opts.env ? { ...process13.env, ...opts.env } : void 0
3667
+ });
3668
+ let stdout = "";
3669
+ let stderr = "";
3670
+ child.stdout?.on("data", (chunk) => {
3671
+ stdout += chunk;
3672
+ if (opts.streamStdout) process13.stdout.write(chunk);
3673
+ });
3674
+ child.stderr?.on("data", (chunk) => {
3675
+ stderr += chunk;
3676
+ if (opts.stream) process13.stderr.write(chunk);
3677
+ });
3678
+ child.on("error", reject);
3679
+ child.on("close", (code) => resolve2({ stdout, stderr, exitCode: code ?? 1 }));
3680
+ if (opts.stdin !== void 0) {
3681
+ child.stdin.end(opts.stdin);
3682
+ }
3683
+ });
3684
+ }
3685
+
3686
+ // src/lib/remote.ts
3687
+ import path19 from "node:path";
3688
+ var VELA_ROOT = "/var/lib/vela";
3689
+ var VELA_ETC = "/etc/vela";
3690
+ var SCRIPTS_DIR = `${VELA_ROOT}/scripts`;
3691
+ var PROVISIONED_MARKER = `${VELA_ETC}/provisioned`;
3692
+ function serverTemplatesDir() {
3693
+ return path19.join(templatesDir(), "server");
3694
+ }
3695
+ async function syncServerScripts(session) {
3696
+ await session.script(`mkdir -p "$1" && chmod 0755 "$1"`, { args: [SCRIPTS_DIR] });
3697
+ await session.uploadDir(serverTemplatesDir(), SCRIPTS_DIR, ["--chmod=D755,F755"]);
3698
+ await session.script(`chown -R root:root "$1"`, { args: [SCRIPTS_DIR] });
3699
+ }
3700
+ async function runServerScript(session, name, opts = {}) {
3701
+ const result = await session.script(`script="$1"; shift; exec "$script" "$@"`, {
3702
+ args: [`${SCRIPTS_DIR}/${name}`, ...opts.args ?? []],
3703
+ stream: opts.stream
3704
+ });
3705
+ return parseResult(result);
3706
+ }
3707
+ function parseResult(result) {
3708
+ const line = result.stdout.split("\n").reverse().find((l) => l.startsWith("VELA_RESULT "));
3709
+ if (!line) return null;
3710
+ try {
3711
+ return JSON.parse(line.slice("VELA_RESULT ".length));
3712
+ } catch {
3713
+ return null;
3714
+ }
3715
+ }
3716
+ async function readServerInfo(session) {
3717
+ const raw = await session.readFile(PROVISIONED_MARKER);
3718
+ if (!raw) return null;
3719
+ try {
3720
+ return JSON.parse(raw);
3721
+ } catch {
3722
+ return null;
3723
+ }
3724
+ }
3725
+ async function requireProvisioned(session) {
3726
+ const info = await readServerInfo(session);
3727
+ if (!info) {
3728
+ throw new Error(
3729
+ `${session.target} has not been provisioned for vela yet.
3730
+
3731
+ Run \`vela provision ${session.target}\` first.`
3732
+ );
3733
+ }
3734
+ return info;
3735
+ }
3736
+ async function readInstanceStates(session, instance) {
3737
+ const result = await runServerScript(session, "status.sh", {
3738
+ args: instance ? [instance] : []
3739
+ });
3740
+ return Array.isArray(result) ? result : [];
3741
+ }
3742
+ var remotePaths = {
3743
+ app: (instance) => `${VELA_ROOT}/apps/${instance}`,
3744
+ releases: (instance) => `${VELA_ROOT}/apps/${instance}/releases`,
3745
+ release: (instance, release) => `${VELA_ROOT}/apps/${instance}/releases/${release}`,
3746
+ env: (instance) => `${VELA_ETC}/apps/${instance}/env`,
3747
+ runtimeEnv: (instance) => `${VELA_ETC}/apps/${instance}/runtime.env`,
3748
+ caddy: (instance) => `${VELA_ETC}/caddy/${instance}.caddy`,
3749
+ webUnit: (instance) => `vela-web@${instance}.service`,
3750
+ pbUnit: (instance) => `vela-pb@${instance}.service`
3751
+ };
3752
+
3753
+ // src/lib/remote-env.ts
3754
+ import fs19 from "node:fs";
3755
+ import dotenv from "dotenv";
3756
+ var KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
3757
+ function isValidKey(key) {
3758
+ return KEY_PATTERN.test(key);
3759
+ }
3760
+ function parseEnv(content) {
3761
+ const result = {};
3762
+ for (const line of content.split("\n")) {
3763
+ const trimmed = line.trim();
3764
+ if (!trimmed || trimmed.startsWith("#")) continue;
3765
+ const eq = trimmed.indexOf("=");
3766
+ if (eq < 1) continue;
3767
+ const key = trimmed.slice(0, eq).trim();
3768
+ if (!isValidKey(key)) continue;
3769
+ result[key] = unquote(trimmed.slice(eq + 1).trim());
3770
+ }
3771
+ return result;
3772
+ }
3773
+ function unquote(raw) {
3774
+ if (!raw.startsWith('"')) return raw;
3775
+ const body = raw.endsWith('"') && raw.length > 1 ? raw.slice(1, -1) : raw.slice(1);
3776
+ let out = "";
3777
+ for (let i = 0; i < body.length; i++) {
3778
+ if (body[i] !== "\\" || i === body.length - 1) {
3779
+ out += body[i];
3780
+ continue;
3781
+ }
3782
+ const next = body[++i];
3783
+ if (next === "n") out += "\n";
3784
+ else if (next === "t") out += " ";
3785
+ else out += next;
3786
+ }
3787
+ return out;
3788
+ }
3789
+ function serializeEnv(env2) {
3790
+ const keys = Object.keys(env2).sort();
3791
+ const lines = [
3792
+ "# Managed by `vela env`. Values are read by systemd at service start.",
3793
+ ...keys.map((key) => `${key}=${quote(env2[key])}`)
3794
+ ];
3795
+ return lines.join("\n") + "\n";
3796
+ }
3797
+ function quote(value) {
3798
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll(" ", "\\t");
3799
+ return `"${escaped}"`;
3800
+ }
3801
+ function readLocalEnvFile(file) {
3802
+ const parsed = dotenv.parse(fs19.readFileSync(file));
3803
+ const result = {};
3804
+ for (const [key, value] of Object.entries(parsed)) {
3805
+ if (isValidKey(key)) result[key] = value;
3806
+ }
3807
+ return result;
3808
+ }
3809
+ async function readRemoteEnv(session, instance) {
3810
+ const content = await session.readFile(remotePaths.env(instance));
3811
+ return content ? parseEnv(content) : {};
3812
+ }
3813
+ async function writeRemoteEnv(session, instance, env2) {
3814
+ const file = remotePaths.env(instance);
3815
+ await session.writeFile(file, serializeEnv(env2), "0600");
3816
+ await session.script(`chown root:root "$1" && chmod 0600 "$1"`, { args: [file] });
3817
+ }
3818
+ var SUPERUSER_KEYS = [
3819
+ "POCKETBASE_SUPERUSER_EMAIL",
3820
+ "POCKETBASE_SUPERUSER_PASSWORD"
3821
+ ];
3822
+ function touchesSuperuser(keys) {
3823
+ for (const key of keys) {
3824
+ if (SUPERUSER_KEYS.includes(key)) return true;
3825
+ }
3826
+ return false;
3827
+ }
3828
+ async function restartInstance(session, instance) {
3829
+ const result = await session.script(
3830
+ `instance="$1"
3831
+ enabled=()
3832
+ for unit in "vela-pb@$instance.service" "vela-web@$instance.service"; do
3833
+ if systemctl is-enabled "$unit" >/dev/null 2>&1; then enabled+=("$unit"); fi
3834
+ done
3835
+ if [ \${#enabled[@]} -eq 0 ]; then echo NOT_DEPLOYED; exit 0; fi
3836
+ systemctl restart "\${enabled[@]}"`,
3837
+ { args: [instance], check: false }
3838
+ );
3839
+ if (result.stdout.includes("NOT_DEPLOYED")) return { deployed: false, restarted: false };
3840
+ if (result.exitCode !== 0) {
3841
+ return { deployed: true, restarted: false, error: result.stderr.trim() };
3842
+ }
3843
+ return { deployed: true, restarted: true };
3844
+ }
3845
+
3846
+ // src/lib/target.ts
3847
+ var LOCAL_TARGET = "local";
3848
+ var PRODUCTION_TARGET = "production";
3849
+ var TargetError = class extends Error {
3850
+ };
3851
+ var SERVER_SHAPED = /@|^\d{1,3}(?:\.\d{1,3}){3}$|\.[a-z]{2,}$/i;
3852
+ var PREVIEW = "preview";
3853
+ var TARGET_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3854
+ function parseTarget(raw, fallback) {
3855
+ const value = raw?.trim();
3856
+ if (!value) {
3857
+ return fallback === "local" ? { kind: "local" } : production();
3858
+ }
3859
+ if (SERVER_SHAPED.test(value)) {
3860
+ throw new TargetError(
3861
+ `${value} looks like a server, and ${bold("-t")} now selects a target rather than a machine.
3862
+
3863
+ Targets are ${bold("local")}, ${bold("production")}, or a name you choose such as ${bold("staging")}.
3864
+ To point a target at a server, pass ${bold("--server")} on \`vela deploy\`.`
3865
+ );
3866
+ }
3867
+ const lower = value.toLowerCase();
3868
+ if (lower === LOCAL_TARGET) return { kind: "local" };
3869
+ if (lower === PREVIEW || lower.startsWith(`${PREVIEW}:`)) {
3870
+ const branch = lower.slice(PREVIEW.length + 1) || void 0;
3871
+ return {
3872
+ kind: "preview",
3873
+ branch,
3874
+ // Held for when previews are built, so the grammar and the instance
3875
+ // naming cannot drift apart in the meantime.
3876
+ envTag: branch ? branchToEnvTag(branch) : PREVIEW
3877
+ };
3878
+ }
3879
+ if (value.includes(":")) {
3880
+ throw new TargetError(
3881
+ `${value} is not a target. Only ${bold("preview")} takes a \`:branch\` suffix.`
3882
+ );
3883
+ }
3884
+ if (!TARGET_NAME.test(lower)) {
3885
+ throw new TargetError(
3886
+ `${value} is not a target name.
3887
+
3888
+ Use lowercase letters, digits and single dashes, such as ${bold("staging")}.`
3889
+ );
3890
+ }
3891
+ return { kind: "remote", name: lower, envTag: normalizeEnvTag(lower) };
3892
+ }
3893
+ function production() {
3894
+ return { kind: "remote", name: PRODUCTION_TARGET, envTag: PROD_ENV };
3895
+ }
3896
+ function describeTarget(target) {
3897
+ switch (target.kind) {
3898
+ case "local":
3899
+ return LOCAL_TARGET;
3900
+ case "preview":
3901
+ return target.branch ? `${PREVIEW}:${target.branch}` : PREVIEW;
3902
+ case "remote":
3903
+ return target.name;
3904
+ }
3905
+ }
3906
+ function bold(text16) {
3907
+ return `\`${text16}\``;
3908
+ }
3909
+
3910
+ // src/lib/server-command.ts
3911
+ var SERVER_OPTIONS_SCHEMA = {
3912
+ ...SSH_OPTION_SCHEMA,
3913
+ target: v5.optional(v5.string()),
3914
+ server: v5.optional(v5.string())
3915
+ };
3916
+ var OptionsSchema = v5.object(SERVER_OPTIONS_SCHEMA);
3917
+ function addTargetOptions(command, fallback) {
3918
+ 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");
3919
+ }
3920
+ async function withTarget(raw, handlers, run = {}) {
3921
+ const options = parseOptions(OptionsSchema, raw);
3922
+ const target = parseTarget(options.target, "production");
3923
+ const label = run.label ? `vela ${run.label}` : "this command";
3924
+ if (target.kind === "preview") {
3925
+ throw new Error(
3926
+ `Preview targets are not supported yet.
3927
+
3928
+ \`${describeTarget(target)}\` parses, but nothing deploys it: previews need wildcard
3929
+ DNS and certificates that \`vela provision\` does not set up yet.`
3930
+ );
3931
+ }
3932
+ const { workspaceRootDir } = await getWorkspace();
3933
+ const config = await loadDeployConfig(workspaceRootDir);
3934
+ const app = resolveAppIdentity(workspaceRootDir, {
3935
+ ...config,
3936
+ project: run.project ?? config.project
3937
+ });
3938
+ const base2 = {
3939
+ workspaceRootDir,
3940
+ appName: app.name,
3941
+ appId: app.appId,
3942
+ config
3943
+ };
3944
+ if (target.kind === "local") {
3945
+ if (!handlers.local) {
3946
+ throw new Error(
3947
+ `${label} has no local target.` + (run.localHint ? `
3948
+
3949
+ ${run.localHint}` : "")
3950
+ );
3951
+ }
3952
+ await handlers.local({
3953
+ ...base2,
3954
+ kind: "local",
3955
+ envFile: envFilePath(workspaceRootDir)
3956
+ });
3957
+ return;
3958
+ }
3959
+ if (!handlers.remote) {
3960
+ throw new Error(`${label} only acts on ${pc5.cyan("local")}.`);
3961
+ }
3962
+ const binding = await ensureBinding(workspaceRootDir, target, {
3963
+ server: options.server,
3964
+ askDomain: run.askDomain
3965
+ });
3966
+ await withSsh(binding.server, sshOptionsFrom(options), async (session) => {
3967
+ await session.detectElevation();
3968
+ await requireProvisioned(session);
3969
+ await syncServerScripts(session);
3970
+ await handlers.remote({
3971
+ ...base2,
3972
+ kind: "remote",
3973
+ session,
3974
+ instance: instanceId(app.appId, target.envTag),
3975
+ envTag: target.envTag,
3976
+ targetName: target.name,
3977
+ server: binding.server,
3978
+ binding
3979
+ });
3980
+ });
3981
+ }
3982
+ async function ensureBinding(workspaceRootDir, target, request = {}) {
3983
+ const existing = readBinding(workspaceRootDir, target.envTag);
3984
+ const moving = Boolean(request.server && existing && existing.server !== request.server);
3985
+ let server = request.server ?? existing?.server;
3986
+ if (!server) {
3987
+ server = await promptServer(target);
3988
+ }
3989
+ let domain = request.domain ?? existing?.domain;
3990
+ if (!domain && request.askDomain && !request.server) {
3991
+ domain = await promptDomain(target);
3992
+ }
3993
+ const binding = domain ? { server, domain } : { server };
3994
+ if (!existing || existing.server !== server || existing.domain !== domain) {
3995
+ writeBinding(workspaceRootDir, target.envTag, binding);
3996
+ }
3997
+ if (moving) {
3998
+ p19.log.warn(
3999
+ `${pc5.cyan(target.name)} now points at ${pc5.cyan(server)}.
4000
+
4001
+ Whatever is running on ${existing.server} is left there, and this project can no
4002
+ longer reach it. Remove it there first if that was not intended.`
4003
+ );
4004
+ }
4005
+ return binding;
4006
+ }
4007
+ async function promptServer(target) {
4008
+ if (!process14.stdout.isTTY || process14.env.CI) {
4009
+ throw new Error(
4010
+ `${target.name} is not bound to a server yet, and there is no terminal to ask on.
4011
+
4012
+ Pass ${pc5.cyan("--server user@host")}, or run the command once from your own machine
4013
+ and commit ${pc5.cyan(".vela/project.json")}.`
4014
+ );
4015
+ }
4016
+ p19.log.info(`${pc5.cyan(target.name)} is not bound to a server yet.`);
4017
+ const value = await p19.text({
4018
+ message: `Which server should ${target.name} run on?`,
4019
+ placeholder: "root@203.0.113.10",
4020
+ validate: (input) => input?.trim() ? void 0 : "An ssh_config alias, or user@host"
4021
+ });
4022
+ if (p19.isCancel(value)) {
4023
+ p19.cancel("Operation cancelled.");
4024
+ process14.exit(0);
4025
+ }
4026
+ return value.trim();
4027
+ }
4028
+ async function promptDomain(target) {
4029
+ if (!process14.stdout.isTTY || process14.env.CI) return void 0;
4030
+ const value = await p19.text({
4031
+ message: `Which hostname should ${target.name} be served on?`,
4032
+ placeholder: "example.com \u2014 leave blank to decide later"
4033
+ });
4034
+ if (p19.isCancel(value)) {
4035
+ p19.cancel("Operation cancelled.");
4036
+ process14.exit(0);
4037
+ }
4038
+ return value.trim() || void 0;
4039
+ }
4040
+ async function applyRestart(ctx) {
4041
+ const outcome = await restartInstance(ctx.session, ctx.instance);
4042
+ if (!outcome.deployed) return;
4043
+ if (outcome.restarted) {
4044
+ p19.log.success("App restarted");
4045
+ return;
4046
+ }
4047
+ p19.log.error(
4048
+ `App restart failed.
4049
+
4050
+ The new value is stored and will be used the next time the app starts.
4051
+ ${pc5.dim(outcome.error ?? "")}`
4052
+ );
4053
+ }
4054
+ async function applyEnvRestart(ctx, changed) {
4055
+ if (touchesSuperuser(changed)) {
4056
+ p19.log.warn(
4057
+ `PocketBase superuser credentials changed.
4058
+
4059
+ The database still holds the old ones, so the app has not been restarted.
4060
+ Run ${pc5.cyan("vela deploy")} to push them into the database and restart.`
4061
+ );
4062
+ return;
4063
+ }
4064
+ await applyRestart(ctx);
4065
+ }
4066
+ async function withServerSession(raw, fn) {
4067
+ const options = parseOptions(OptionsSchema, raw);
4068
+ let server = options.server;
4069
+ if (!server) {
4070
+ const { workspaceRootDir } = await getWorkspace();
4071
+ const target = parseTarget(options.target, "production");
4072
+ if (target.kind !== "remote") {
4073
+ throw new Error(`${describeTarget(target)} does not run on a server.`);
4074
+ }
4075
+ server = (await ensureBinding(workspaceRootDir, target, { server: options.server })).server;
4076
+ }
4077
+ await withSsh(server, sshOptionsFrom(options), async (session) => {
4078
+ await session.detectElevation();
4079
+ await requireProvisioned(session);
4080
+ await syncServerScripts(session);
4081
+ await fn(session);
4082
+ });
4083
+ }
4084
+ function envFilePath(workspaceRootDir) {
4085
+ return `${workspaceRootDir}/.env`;
4086
+ }
4087
+
4088
+ // src/commands/destroy/deployment.ts
4089
+ var deployment = addTargetOptions(
4090
+ new Command38("deployment").description("remove a deployed environment from its server").configureHelp(helpConfig),
4091
+ "production"
4092
+ ).option("--purge", "also delete the database and uploaded files").option("-y, --yes", "skip the confirmation prompt").action(
4093
+ (raw) => runCommand(async () => {
4094
+ const options = raw;
4095
+ await withTarget(
4096
+ raw,
4097
+ {
4098
+ remote: async (ctx) => {
4099
+ if (!options.yes) {
4100
+ await confirm5(ctx.appName, ctx.targetName, ctx.envTag, options.purge === true);
4101
+ }
4102
+ const result = await runServerScript(ctx.session, "destroy.sh", {
4103
+ args: [ctx.instance, ...options.purge ? ["--purge"] : []],
4104
+ stream: true
4105
+ });
4106
+ p20.log.success(
4107
+ `Removed ${pc6.cyan(`${ctx.appName} (${ctx.targetName})`)} from ${ctx.server}.` + (result?.purged ? "" : `
4108
+
4109
+ The database and uploads are still in the instance's shared directory. Pass ${pc6.cyan("--purge")} to delete them.`)
4110
+ );
4111
+ }
4112
+ },
4113
+ {
4114
+ label: "destroy deployment",
4115
+ localHint: "There is nothing deployed locally to remove."
4116
+ }
4117
+ );
4118
+ }, "Failed to remove the deployment.")
4119
+ );
4120
+ async function confirm5(appName, targetName, envTag, purge) {
4121
+ if (isProd(envTag) || purge) {
4122
+ const answer = await p20.text({
4123
+ message: `This removes ${pc6.cyan(`${appName} (${targetName})`)}${purge ? " and its database" : ""}. Type the app name to confirm`,
4124
+ validate: (value) => value === appName ? void 0 : `Type ${appName} to confirm`
4125
+ });
4126
+ if (p20.isCancel(answer)) {
4127
+ p20.cancel("Operation cancelled.");
4128
+ process15.exit(0);
4129
+ }
4130
+ return;
4131
+ }
4132
+ const ok = await p20.confirm({
4133
+ message: `Remove ${appName} (${targetName})?`,
4134
+ initialValue: false
4135
+ });
4136
+ if (p20.isCancel(ok) || !ok) {
4137
+ p20.cancel("Operation cancelled.");
4138
+ process15.exit(0);
4139
+ }
4140
+ }
4141
+
4142
+ // src/commands/destroy.ts
4143
+ var destroy = new Command39("destroy").description("destroy scaffolding, or a deployment").configureHelp(helpConfig).addCommand(form2).addCommand(schema2).addCommand(resource2).addCommand(scaffold2).addCommand(deployment);
4144
+
4145
+ // src/commands/ui.ts
4146
+ import { Command as Command42 } from "commander";
4147
+
4148
+ // src/commands/ui/add.ts
4149
+ import { Command as Command40 } from "commander";
4150
+ import { exec as exec2 } from "tinyexec";
4151
+ import { detect as detect4, resolveCommand as resolveCommand4 } from "package-manager-detector";
4152
+ var add = new Command40("add").description("add ui components").argument("<components...>", "the components to add").configureHelp(helpConfig).action(
4153
+ (components) => runCommand(async () => {
4154
+ const { workspaceRootDir } = await getWorkspace();
4155
+ const packageManager = (await detect4({ cwd: workspaceRootDir }))?.name ?? "npm";
4156
+ const resolved = resolveCommand4(packageManager, "execute", [
4157
+ "shadcn-svelte",
4158
+ "add",
4159
+ ...components
4160
+ ]);
4161
+ if (!resolved) {
4162
+ throw new Error(`Unable to resolve execute command for ${packageManager}`);
4163
+ }
4164
+ const args = [...resolved.args];
4165
+ if (packageManager === "npm") args.unshift("--yes");
4166
+ try {
4167
+ await exec2(resolved.command, args, {
4168
+ nodeOptions: { cwd: workspaceRootDir, stdio: "inherit" },
4169
+ throwOnError: true
4170
+ });
4171
+ } catch (error) {
4172
+ const typed = error;
4173
+ throw new Error(
4174
+ `Failed to execute '${resolved.command} ${args.join(" ")}': ${typed.message}`,
4175
+ { cause: typed.output }
4176
+ );
4177
+ }
4178
+ reportResult({
4179
+ summary: `Added ${components.length} UI component(s).`,
4180
+ componentsAdded: components,
4181
+ nextSteps: [
4182
+ `Import a component with: import { Button } from '$lib/components/ui/button';`,
4183
+ "Tweak styling in src/lib/components/ui/<component>/*.svelte.",
4184
+ "Run `vela ui base <color>` to change the palette (slate, gray, zinc, stone, neutral)."
4185
+ ]
4186
+ });
4187
+ }, "Failed to add UI components.")
4188
+ );
4189
+
4190
+ // src/commands/ui/base.ts
4191
+ import fs20 from "node:fs";
4192
+ import path20 from "node:path";
4193
+ import { Command as Command41, InvalidArgumentError } from "commander";
4194
+ var VALID_COLORS = ["slate", "gray", "zinc", "stone", "neutral"];
4195
+ function findThemeFile(color) {
4196
+ const candidate = path20.join(templatesDir(), "ui", "css", `${color}.css`);
4197
+ if (!fs20.existsSync(candidate)) {
4198
+ throw new Error(`Theme file not found for color: ${color}`);
4199
+ }
4200
+ return candidate;
4201
+ }
4202
+ function extractSelectors(css) {
4203
+ const rootMatch = css.match(/:root\s*{[^}]*}/);
4204
+ const darkMatch = css.match(/\.dark\s*{[^}]*}/);
4205
+ if (!rootMatch || !darkMatch) {
4206
+ throw new Error("Invalid theme file: missing :root or .dark selector");
4207
+ }
4208
+ return { root: rootMatch[0], dark: darkMatch[0] };
4209
+ }
4210
+ function applyTheme(appCss, selectors) {
4211
+ let out = appCss;
4212
+ out = out.match(/:root\s*{[^}]*}/) ? out.replace(/:root\s*{[^}]*}/, selectors.root) : `${selectors.root}
4213
+
4214
+ ${out}`;
4215
+ out = out.match(/\.dark\s*{[^}]*}/) ? out.replace(/\.dark\s*{[^}]*}/, selectors.dark) : `${out}
4216
+
4217
+ ${selectors.dark}`;
4218
+ return out;
4219
+ }
4220
+ var base = new Command41("base").description("change the base color").argument("<color>", "base color to use", (value) => {
4221
+ if (!VALID_COLORS.includes(value)) {
4222
+ throw new InvalidArgumentError(`Valid colors are: ${VALID_COLORS.join(", ")}`);
4223
+ }
4224
+ return value;
4225
+ }).configureHelp(helpConfig).action(
4226
+ (color) => runCommand(async () => {
4227
+ const { workspaceRootDir } = await getWorkspace();
4228
+ const appCssPath = path20.join(workspaceRootDir, "src", "app.css");
4229
+ if (!fs20.existsSync(appCssPath)) {
4230
+ throw new Error(`Could not find ${path20.relative(workspaceRootDir, appCssPath)}`);
4231
+ }
4232
+ const themeContent = fs20.readFileSync(findThemeFile(color), "utf8");
4233
+ const selectors = extractSelectors(themeContent);
4234
+ const appCss = fs20.readFileSync(appCssPath, "utf8");
4235
+ fs20.writeFileSync(appCssPath, applyTheme(appCss, selectors));
4236
+ reportResult({
4237
+ summary: `Set base color to ${color}.`,
4238
+ filesModified: [path20.relative(workspaceRootDir, appCssPath)],
4239
+ nextSteps: [
4240
+ "Run your dev server to preview the new palette.",
4241
+ "Tweak individual CSS variables in src/app.css if you want to customize the theme further."
4242
+ ]
4243
+ });
4244
+ }, "Failed to change base color.")
4245
+ );
4246
+
4247
+ // src/commands/ui.ts
4248
+ var ui = new Command42("ui").description("generate ui components").configureHelp(helpConfig).addCommand(add).addCommand(base);
4249
+
4250
+ // src/commands/legal.ts
4251
+ import { Command as Command45 } from "commander";
4252
+
4253
+ // src/commands/legal/terms.ts
4254
+ import fs21 from "node:fs";
4255
+ import path21 from "node:path";
4256
+ import { Command as Command43 } from "commander";
4257
+ import * as p22 from "@clack/prompts";
4258
+
4259
+ // src/commands/legal/shared.ts
4260
+ import process16 from "node:process";
4261
+ import * as p21 from "@clack/prompts";
4262
+ var sharedFields = {
4263
+ websiteUrl: () => p21.text({
4264
+ message: "What is your website URL?",
4265
+ placeholder: "http://www.mysite.com",
4266
+ validate: (value) => {
4267
+ if (!value || !value.startsWith("http")) {
4268
+ return "Please enter a valid URL starting with http or https";
4269
+ }
4270
+ }
4271
+ }),
4272
+ websiteName: () => p21.text({
4273
+ message: "What is your website name?",
4274
+ placeholder: "My Site",
4275
+ validate: (value) => {
4276
+ if (!value) {
4277
+ return "Please enter a website name";
4278
+ }
4279
+ }
4280
+ }),
4281
+ entityType: () => p21.select({
4282
+ message: "Entity type",
4283
+ options: [
4284
+ {
4285
+ value: "business",
4286
+ label: "I'm a Business",
4287
+ hint: "e.g. Corporation, Limited Liability Company, Non-profit, Partnership, Sole Proprietor"
4288
+ },
4289
+ { value: "individual", label: "I'm an Individual" }
4290
+ ]
4291
+ }),
4292
+ businessName: ({ results }) => results?.entityType === "business" ? p21.text({
4293
+ message: "What is the name of the business?",
4294
+ placeholder: "My Company LLC",
4295
+ validate: (value) => {
4296
+ if (!value) {
4297
+ return "Please enter a business name";
4298
+ }
4299
+ }
4300
+ }) : void 0,
4301
+ businessAddress: ({ results }) => results?.entityType === "business" ? p21.text({
4302
+ message: "What is the address of the business?",
4303
+ placeholder: "1 Cupertino, CA 95014",
4304
+ validate: (value) => {
4305
+ if (!value) {
4306
+ return "Please enter a business address";
4307
+ }
4308
+ }
4309
+ }) : void 0,
4310
+ country: () => p21.text({
4311
+ message: "Enter the country",
4312
+ validate: (value) => {
4313
+ if (!value) {
4314
+ return "Please enter a country";
4315
+ }
4316
+ }
4317
+ }),
4318
+ state: () => p21.text({
4319
+ message: "Enter the state",
4320
+ validate: (value) => {
4321
+ if (!value) {
4322
+ return "Please enter a state";
4323
+ }
4324
+ }
4325
+ })
4326
+ };
4327
+ var onCancel = () => {
4328
+ p21.cancel("Operation cancelled.");
4329
+ process16.exit(0);
4330
+ };
4331
+ async function contactMethods(type) {
4332
+ const selection = await p21.multiselect({
4333
+ message: `How can users contact you for any questions regarding your ${type === "privacy" ? "Privacy Policy" : "Terms & Conditions"}? Check all that apply`,
3356
4334
  options: [
3357
4335
  { value: "email", label: "By email" },
3358
4336
  { value: "page", label: "By visiting a page on our website" },
@@ -3360,11 +4338,11 @@ async function contactMethods(type) {
3360
4338
  { value: "mail", label: "By sending post mail" }
3361
4339
  ]
3362
4340
  });
3363
- if (p19.isCancel(selection)) onCancel();
4341
+ if (p21.isCancel(selection)) onCancel();
3364
4342
  const contact = selection;
3365
4343
  const details = {};
3366
4344
  if (contact.includes("email")) {
3367
- const email3 = await p19.text({
4345
+ const email3 = await p21.text({
3368
4346
  message: "What's the email?",
3369
4347
  placeholder: "office@mycompany.com",
3370
4348
  validate: (value) => {
@@ -3373,11 +4351,11 @@ async function contactMethods(type) {
3373
4351
  }
3374
4352
  }
3375
4353
  });
3376
- if (p19.isCancel(email3)) onCancel();
4354
+ if (p21.isCancel(email3)) onCancel();
3377
4355
  details.email = email3;
3378
4356
  }
3379
4357
  if (contact.includes("page")) {
3380
- const page = await p19.text({
4358
+ const page = await p21.text({
3381
4359
  message: "What's the link?",
3382
4360
  placeholder: "http://www.mycompany.com/contact",
3383
4361
  validate: (value) => {
@@ -3386,11 +4364,11 @@ async function contactMethods(type) {
3386
4364
  }
3387
4365
  }
3388
4366
  });
3389
- if (p19.isCancel(page)) onCancel();
4367
+ if (p21.isCancel(page)) onCancel();
3390
4368
  details.page = page;
3391
4369
  }
3392
4370
  if (contact.includes("phone")) {
3393
- const phone = await p19.text({
4371
+ const phone = await p21.text({
3394
4372
  message: "What's the phone number?",
3395
4373
  placeholder: "408.996.1010",
3396
4374
  validate: (value) => {
@@ -3399,11 +4377,11 @@ async function contactMethods(type) {
3399
4377
  }
3400
4378
  }
3401
4379
  });
3402
- if (p19.isCancel(phone)) onCancel();
4380
+ if (p21.isCancel(phone)) onCancel();
3403
4381
  details.phone = phone;
3404
4382
  }
3405
4383
  if (contact.includes("mail")) {
3406
- const address = await p19.text({
4384
+ const address = await p21.text({
3407
4385
  message: "What's the address?",
3408
4386
  placeholder: "767 Fifth Avenue New York, NY 10153, United States",
3409
4387
  validate: (value) => {
@@ -3412,7 +4390,7 @@ async function contactMethods(type) {
3412
4390
  }
3413
4391
  }
3414
4392
  });
3415
- if (p19.isCancel(address)) onCancel();
4393
+ if (p21.isCancel(address)) onCancel();
3416
4394
  details.address = address;
3417
4395
  }
3418
4396
  return { methods: contact, details };
@@ -3808,7 +4786,7 @@ var generateTermsHtml = (answers) => {
3808
4786
  };
3809
4787
  async function termsAction() {
3810
4788
  const { workspaceRootDir, publicRoutesDir } = await getWorkspace();
3811
- const core = await p20.group(
4789
+ const core = await p22.group(
3812
4790
  {
3813
4791
  websiteUrl: sharedFields.websiteUrl,
3814
4792
  websiteName: sharedFields.websiteName,
@@ -3820,25 +4798,25 @@ async function termsAction() {
3820
4798
  },
3821
4799
  { onCancel }
3822
4800
  );
3823
- const accounts = await p20.select({
4801
+ const accounts = await p22.select({
3824
4802
  message: "Can users create accounts?",
3825
4803
  options: [
3826
4804
  { value: "yes", label: "Yes, users can create accounts" },
3827
4805
  { value: "no", label: "No" }
3828
4806
  ]
3829
4807
  });
3830
- if (p20.isCancel(accounts)) onCancel();
3831
- const userContent = await p20.select({
4808
+ if (p22.isCancel(accounts)) onCancel();
4809
+ const userContent = await p22.select({
3832
4810
  message: "Can users create and/or upload content (ie. text, images)?",
3833
4811
  options: [
3834
4812
  { value: "yes", label: "Yes, users can create and/or upload content" },
3835
4813
  { value: "no", label: "No" }
3836
4814
  ]
3837
4815
  });
3838
- if (p20.isCancel(userContent)) onCancel();
4816
+ if (p22.isCancel(userContent)) onCancel();
3839
4817
  let infringementEmail;
3840
4818
  if (userContent === "yes") {
3841
- const email3 = await p20.text({
4819
+ const email3 = await p22.text({
3842
4820
  message: "What's the email address where you will receive infringements notices?",
3843
4821
  placeholder: "dmca@website.com",
3844
4822
  validate: (value) => {
@@ -3847,10 +4825,10 @@ async function termsAction() {
3847
4825
  }
3848
4826
  }
3849
4827
  });
3850
- if (p20.isCancel(email3)) onCancel();
4828
+ if (p22.isCancel(email3)) onCancel();
3851
4829
  infringementEmail = email3;
3852
4830
  }
3853
- const canBuyGoods = await p20.select({
4831
+ const canBuyGoods = await p22.select({
3854
4832
  message: "Can users buy goods (products, items)?",
3855
4833
  options: [
3856
4834
  {
@@ -3860,28 +4838,28 @@ async function termsAction() {
3860
4838
  { value: "no", label: "No" }
3861
4839
  ]
3862
4840
  });
3863
- if (p20.isCancel(canBuyGoods)) onCancel();
3864
- const subscriptions2 = await p20.select({
4841
+ if (p22.isCancel(canBuyGoods)) onCancel();
4842
+ const subscriptions2 = await p22.select({
3865
4843
  message: "Do you offer subscription plans?",
3866
4844
  options: [
3867
4845
  { value: "yes", label: "Yes, we offer subscription plans" },
3868
4846
  { value: "no", label: "No" }
3869
4847
  ]
3870
4848
  });
3871
- if (p20.isCancel(subscriptions2)) onCancel();
4849
+ if (p22.isCancel(subscriptions2)) onCancel();
3872
4850
  let freeTrial;
3873
4851
  if (subscriptions2 === "yes") {
3874
- const ft = await p20.select({
4852
+ const ft = await p22.select({
3875
4853
  message: "Do you offer a free trial?",
3876
4854
  options: [
3877
4855
  { value: "yes", label: "Yes" },
3878
4856
  { value: "no", label: "No" }
3879
4857
  ]
3880
4858
  });
3881
- if (p20.isCancel(ft)) onCancel();
4859
+ if (p22.isCancel(ft)) onCancel();
3882
4860
  freeTrial = ft;
3883
4861
  }
3884
- const exclusiveContent = await p20.select({
4862
+ const exclusiveContent = await p22.select({
3885
4863
  message: "Do you want to make it clear that your own content & trademarks are your exclusive property?",
3886
4864
  options: [
3887
4865
  {
@@ -3891,24 +4869,24 @@ async function termsAction() {
3891
4869
  { value: "no", label: "No" }
3892
4870
  ]
3893
4871
  });
3894
- if (p20.isCancel(exclusiveContent)) onCancel();
3895
- const feedbackReuse = await p20.select({
4872
+ if (p22.isCancel(exclusiveContent)) onCancel();
4873
+ const feedbackReuse = await p22.select({
3896
4874
  message: "If users provide you feedback & suggestions, do you want to use this feedback without compensation or credits given?",
3897
4875
  options: [
3898
4876
  { value: "yes", label: "Yes, we may implement any feedback or suggestions we receive" },
3899
4877
  { value: "no", label: "No" }
3900
4878
  ]
3901
4879
  });
3902
- if (p20.isCancel(feedbackReuse)) onCancel();
3903
- const promotions = await p20.select({
4880
+ if (p22.isCancel(feedbackReuse)) onCancel();
4881
+ const promotions = await p22.select({
3904
4882
  message: "Do you plan to offer promotions, contests, sweepstakes?",
3905
4883
  options: [
3906
4884
  { value: "yes", label: "Yes, we may offer promotions, contests, sweepstakes" },
3907
4885
  { value: "no", label: "No" }
3908
4886
  ]
3909
4887
  });
3910
- if (p20.isCancel(promotions)) onCancel();
3911
- const mobileAppRaw = await p20.multiselect({
4888
+ if (p22.isCancel(promotions)) onCancel();
4889
+ const mobileAppRaw = await p22.multiselect({
3912
4890
  message: "Is the Service distributed through any mobile app stores? Check all that apply",
3913
4891
  options: [
3914
4892
  { value: "apple", label: "Apple App Store" },
@@ -3916,7 +4894,7 @@ async function termsAction() {
3916
4894
  ],
3917
4895
  required: false
3918
4896
  });
3919
- if (p20.isCancel(mobileAppRaw)) onCancel();
4897
+ if (p22.isCancel(mobileAppRaw)) onCancel();
3920
4898
  const mobileApp = mobileAppRaw ?? [];
3921
4899
  const contact = await contactMethods("terms");
3922
4900
  const html = generateTermsHtml({
@@ -3933,22 +4911,22 @@ async function termsAction() {
3933
4911
  mobileApp,
3934
4912
  contact
3935
4913
  });
3936
- const termsPage = path18.join(
4914
+ const termsPage = path21.join(
3937
4915
  workspaceRootDir,
3938
4916
  publicRoutesDir,
3939
4917
  LEGAL_DIR,
3940
4918
  "terms",
3941
4919
  "+page.svelte"
3942
4920
  );
3943
- const termsPageTs = path18.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
3944
- fs18.mkdirSync(path18.dirname(termsPage), { recursive: true });
3945
- fs18.writeFileSync(termsPage, html);
3946
- fs18.writeFileSync(
4921
+ const termsPageTs = path21.join(workspaceRootDir, publicRoutesDir, LEGAL_DIR, "terms", "+page.ts");
4922
+ fs21.mkdirSync(path21.dirname(termsPage), { recursive: true });
4923
+ fs21.writeFileSync(termsPage, html);
4924
+ fs21.writeFileSync(
3947
4925
  termsPageTs,
3948
4926
  pageMetaTagsLoader("Terms of Service", `Terms of Service for ${core.websiteName}`)
3949
4927
  );
3950
- const relativeTermsPage = path18.relative(workspaceRootDir, termsPage);
3951
- const relativeTermsPageTs = path18.relative(workspaceRootDir, termsPageTs);
4928
+ const relativeTermsPage = path21.relative(workspaceRootDir, termsPage);
4929
+ const relativeTermsPageTs = path21.relative(workspaceRootDir, termsPageTs);
3952
4930
  reportResult({
3953
4931
  summary: "Generated placeholder terms and conditions.",
3954
4932
  filesCreated: [relativeTermsPage, relativeTermsPageTs],
@@ -3959,13 +4937,13 @@ async function termsAction() {
3959
4937
  ]
3960
4938
  });
3961
4939
  }
3962
- var terms = new Command42("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
4940
+ var terms = new Command43("terms").description("generate placeholder terms and conditions").configureHelp(helpConfig).action(() => runCommand(termsAction, "Failed to generate terms and conditions."));
3963
4941
 
3964
4942
  // src/commands/legal/privacy.ts
3965
- import fs19 from "node:fs";
3966
- import path19 from "node:path";
3967
- import { Command as Command43 } from "commander";
3968
- import * as p21 from "@clack/prompts";
4943
+ import fs22 from "node:fs";
4944
+ import path22 from "node:path";
4945
+ import { Command as Command44 } from "commander";
4946
+ import * as p23 from "@clack/prompts";
3969
4947
  var mapLabels = {
3970
4948
  personalInfo: {
3971
4949
  email: "Email address",
@@ -4052,9 +5030,9 @@ var compute2 = (a) => {
4052
5030
  });
4053
5031
  const piSelections = a.personalInfo ?? [];
4054
5032
  const piLabels = piSelections.map(
4055
- (v4) => mapLabels.personalInfo[v4] ?? v4
5033
+ (v9) => mapLabels.personalInfo[v9] ?? v9
4056
5034
  );
4057
- const lookup = (map, vs) => (vs ?? []).map((v4) => map[v4] ?? v4);
5035
+ const lookup = (map, vs) => (vs ?? []).map((v9) => map[v9] ?? v9);
4058
5036
  const companyName = a.core?.entityType === "business" ? a.core?.businessName ?? a.core?.websiteName : a.core?.websiteName ?? "Our Company";
4059
5037
  const companyAddress = a.core?.entityType === "business" ? a.core?.businessAddress ?? "" : "";
4060
5038
  const websiteName = a.core?.websiteName ?? "our website";
@@ -4248,7 +5226,7 @@ var sectionShare = (a, c) => {
4248
5226
  };
4249
5227
  var sectionSaleSharing = (a, c) => {
4250
5228
  const sells = a.showAds === "yes" || a.remarketing === "yes";
4251
- const intro = sells ? `<p>For purposes of certain U.S. state privacy laws (including the California Consumer Privacy Act, as amended by the California Privacy Rights Act, and the Virginia, Colorado, Connecticut, Utah, Texas, and Oregon consumer privacy laws), our use of cookies, pixels, and similar advertising and analytics technologies \u2014 including for cross-context behavioral advertising and targeted advertising \u2014 may be considered a "sale" or "share" of personal information. The categories of personal information involved typically include online identifiers, IP addresses, internet or other electronic network activity information, and inferences derived from this information.</p>` : '<p>We do not "sell" your personal information for monetary consideration. We do not "share" your personal information for cross-context behavioral advertising as those terms are defined under the California Privacy Rights Act ("CPRA") or comparable U.S. state privacy laws. We do not knowingly sell or share personal information of consumers under 16 years of age.</p>';
5229
+ const intro3 = sells ? `<p>For purposes of certain U.S. state privacy laws (including the California Consumer Privacy Act, as amended by the California Privacy Rights Act, and the Virginia, Colorado, Connecticut, Utah, Texas, and Oregon consumer privacy laws), our use of cookies, pixels, and similar advertising and analytics technologies \u2014 including for cross-context behavioral advertising and targeted advertising \u2014 may be considered a "sale" or "share" of personal information. The categories of personal information involved typically include online identifiers, IP addresses, internet or other electronic network activity information, and inferences derived from this information.</p>` : '<p>We do not "sell" your personal information for monetary consideration. We do not "share" your personal information for cross-context behavioral advertising as those terms are defined under the California Privacy Rights Act ("CPRA") or comparable U.S. state privacy laws. We do not knowingly sell or share personal information of consumers under 16 years of age.</p>';
4252
5230
  let recipients = "";
4253
5231
  if (sells) {
4254
5232
  const partners = [...c.adsPlatforms, ...c.remarketingPlatforms];
@@ -4260,7 +5238,7 @@ var sectionSaleSharing = (a, c) => {
4260
5238
  const optOut = `<p><strong>Your right to opt out.</strong> You have the right to opt out of the sale or sharing of your personal information at any time. To exercise this right, you may use the controls described in the "Tracking Technologies and Cookies" section, change the privacy settings in your browser or mobile device, contact us using the methods listed in the "Contact Us" section, or send an opt-out preference signal \u2014 we honor the Global Privacy Control ("GPC") signal as a request to opt out of sale and sharing for the browser or device on which the signal is detected.</p>`;
4261
5239
  return titledSection(
4262
5240
  "Sale or Sharing of Personal Information; Targeted Advertising",
4263
- `${intro}${recipients}${optOut}`
5241
+ `${intro3}${recipients}${optOut}`
4264
5242
  );
4265
5243
  };
4266
5244
  var sectionCookies = (a, c) => {
@@ -4299,8 +5277,8 @@ var sectionAutomated = () => titledSection(
4299
5277
  "<p>We do not use your Personal Information to make decisions based solely on automated processing \u2014 including profiling \u2014 that produce legal effects concerning you or similarly significantly affect you, except as may be necessary for entering into or performing a contract with you, where authorized by applicable law, or where you have given your explicit consent. If we ever do, we will provide meaningful information about the logic involved and the significance and envisaged consequences of such processing, and you may request human review of any such decision.</p>"
4300
5278
  );
4301
5279
  var sectionRights = (a, c) => {
4302
- const intro = '<p>This section describes your privacy rights under applicable laws and how to exercise them. Even if a particular section does not apply to you, you may always contact us using the methods in the "Contact Us" section to ask about your privacy choices.</p>';
4303
- let body = intro;
5280
+ const intro3 = '<p>This section describes your privacy rights under applicable laws and how to exercise them. Even if a particular section does not apply to you, you may always contact us using the methods in the "Contact Us" section to ask about your privacy choices.</p>';
5281
+ let body = intro3;
4304
5282
  if (a.usStates === "yes") {
4305
5283
  body += subSection("U.S. State Privacy Rights", usStateRightsBlock(c.companyName));
4306
5284
  }
@@ -4383,12 +5361,12 @@ var generatePrivacyHtml = (answers) => {
4383
5361
  return `<section data-role="content">${sections.join("")}</section>`;
4384
5362
  };
4385
5363
  async function promptWithCustom(message, options, customMessage) {
4386
- const selection = await p21.multiselect({ message, options });
4387
- if (p21.isCancel(selection)) onCancel();
5364
+ const selection = await p23.multiselect({ message, options });
5365
+ if (p23.isCancel(selection)) onCancel();
4388
5366
  const values = selection;
4389
5367
  if (values.includes("custom")) {
4390
- const custom = await p21.text({ message: customMessage });
4391
- if (p21.isCancel(custom)) onCancel();
5368
+ const custom = await p23.text({ message: customMessage });
5369
+ if (p23.isCancel(custom)) onCancel();
4392
5370
  const idx = values.indexOf("custom");
4393
5371
  if (idx !== -1) values.splice(idx, 1);
4394
5372
  if (custom) values.push(String(custom));
@@ -4397,7 +5375,7 @@ async function promptWithCustom(message, options, customMessage) {
4397
5375
  }
4398
5376
  async function privacyAction() {
4399
5377
  const { workspaceRootDir, publicRoutesDir } = await getWorkspace();
4400
- const core = await p21.group(
5378
+ const core = await p23.group(
4401
5379
  {
4402
5380
  websiteUrl: sharedFields.websiteUrl,
4403
5381
  websiteName: sharedFields.websiteName,
@@ -4409,7 +5387,7 @@ async function privacyAction() {
4409
5387
  },
4410
5388
  { onCancel }
4411
5389
  );
4412
- const personalInfo = await p21.multiselect({
5390
+ const personalInfo = await p23.multiselect({
4413
5391
  message: "What kind of personal information do you collect from users? Check all that apply",
4414
5392
  options: [
4415
5393
  { value: "email", label: "Email address" },
@@ -4424,16 +5402,16 @@ async function privacyAction() {
4424
5402
  ],
4425
5403
  required: false
4426
5404
  });
4427
- if (p21.isCancel(personalInfo)) onCancel();
5405
+ if (p23.isCancel(personalInfo)) onCancel();
4428
5406
  const contact = await contactMethods("privacy");
4429
- const tracking = await p21.select({
5407
+ const tracking = await p23.select({
4430
5408
  message: "Do you use tracking and/or analytics tools, such as Google Analytics?",
4431
5409
  options: [
4432
5410
  { value: "yes", label: "Yes, we use Google Analytics or other related tools" },
4433
5411
  { value: "no", label: "No" }
4434
5412
  ]
4435
5413
  });
4436
- if (p21.isCancel(tracking)) onCancel();
5414
+ if (p23.isCancel(tracking)) onCancel();
4437
5415
  let trackingTools;
4438
5416
  if (tracking === "yes") {
4439
5417
  trackingTools = await promptWithCustom(
@@ -4452,7 +5430,7 @@ async function privacyAction() {
4452
5430
  "Enter your custom tracking/analytics tool name"
4453
5431
  );
4454
5432
  }
4455
- const sendEmails = await p21.select({
5433
+ const sendEmails = await p23.select({
4456
5434
  message: "Do you send emails to users?",
4457
5435
  options: [
4458
5436
  {
@@ -4462,7 +5440,7 @@ async function privacyAction() {
4462
5440
  { value: "no", label: "No" }
4463
5441
  ]
4464
5442
  });
4465
- if (p21.isCancel(sendEmails)) onCancel();
5443
+ if (p23.isCancel(sendEmails)) onCancel();
4466
5444
  let emailPlatforms;
4467
5445
  if (sendEmails === "yes") {
4468
5446
  emailPlatforms = await promptWithCustom(
@@ -4477,14 +5455,14 @@ async function privacyAction() {
4477
5455
  "Enter your custom email platform"
4478
5456
  );
4479
5457
  }
4480
- const showAds = await p21.select({
5458
+ const showAds = await p23.select({
4481
5459
  message: "Do you show ads?",
4482
5460
  options: [
4483
5461
  { value: "yes", label: "Yes, we show ads" },
4484
5462
  { value: "no", label: "No" }
4485
5463
  ]
4486
5464
  });
4487
- if (p21.isCancel(showAds)) onCancel();
5465
+ if (p23.isCancel(showAds)) onCancel();
4488
5466
  let adsPlatforms;
4489
5467
  if (showAds === "yes") {
4490
5468
  adsPlatforms = await promptWithCustom(
@@ -4507,7 +5485,7 @@ async function privacyAction() {
4507
5485
  "Enter your custom ads platform"
4508
5486
  );
4509
5487
  }
4510
- const canPay = await p21.select({
5488
+ const canPay = await p23.select({
4511
5489
  message: "Can users pay for products or services?",
4512
5490
  options: [
4513
5491
  { value: "yes", label: "Yes, users can pay for our products/services" },
@@ -4517,7 +5495,7 @@ async function privacyAction() {
4517
5495
  }
4518
5496
  ]
4519
5497
  });
4520
- if (p21.isCancel(canPay)) onCancel();
5498
+ if (p23.isCancel(canPay)) onCancel();
4521
5499
  let paymentProcessors;
4522
5500
  if (canPay === "yes") {
4523
5501
  paymentProcessors = await promptWithCustom(
@@ -4548,14 +5526,14 @@ async function privacyAction() {
4548
5526
  "Enter your custom payment processor/method"
4549
5527
  );
4550
5528
  }
4551
- const remarketing = await p21.select({
5529
+ const remarketing = await p23.select({
4552
5530
  message: "Do you use remarketing services for marketing & advertising purposes?",
4553
5531
  options: [
4554
5532
  { value: "yes", label: "Yes, we use remarketing services to advertise our business" },
4555
5533
  { value: "no", label: "No" }
4556
5534
  ]
4557
5535
  });
4558
- if (p21.isCancel(remarketing)) onCancel();
5536
+ if (p23.isCancel(remarketing)) onCancel();
4559
5537
  let remarketingPlatforms;
4560
5538
  if (remarketing === "yes") {
4561
5539
  remarketingPlatforms = await promptWithCustom(
@@ -4574,7 +5552,7 @@ async function privacyAction() {
4574
5552
  "Enter your custom remarketing platform"
4575
5553
  );
4576
5554
  }
4577
- const providersRaw = await p21.multiselect({
5555
+ const providersRaw = await p23.multiselect({
4578
5556
  message: "Select if you use any of the following providers",
4579
5557
  options: [
4580
5558
  { value: "recaptcha", label: "Invisible reCAPTCHA" },
@@ -4585,16 +5563,16 @@ async function privacyAction() {
4585
5563
  ],
4586
5564
  required: false
4587
5565
  });
4588
- if (p21.isCancel(providersRaw)) onCancel();
5566
+ if (p23.isCancel(providersRaw)) onCancel();
4589
5567
  const providers = providersRaw;
4590
5568
  if (providers.includes("custom")) {
4591
- const custom = await p21.text({ message: "Enter your custom provider" });
4592
- if (p21.isCancel(custom)) onCancel();
5569
+ const custom = await p23.text({ message: "Enter your custom provider" });
5570
+ if (p23.isCancel(custom)) onCancel();
4593
5571
  const idx = providers.indexOf("custom");
4594
5572
  if (idx !== -1) providers.splice(idx, 1);
4595
5573
  if (custom) providers.push(String(custom));
4596
5574
  }
4597
- const usStates = await p21.select({
5575
+ const usStates = await p23.select({
4598
5576
  message: "Include U.S. state privacy rights (CCPA/CPRA, VCDPA, CPA, CTDPA, UCPA, TX, OR, etc.)?",
4599
5577
  options: [
4600
5578
  {
@@ -4605,55 +5583,55 @@ async function privacyAction() {
4605
5583
  ],
4606
5584
  initialValue: "yes"
4607
5585
  });
4608
- if (p21.isCancel(usStates)) onCancel();
4609
- const gdpr = await p21.select({
5586
+ if (p23.isCancel(usStates)) onCancel();
5587
+ const gdpr = await p23.select({
4610
5588
  message: "Do you want your Privacy Policy to include GDPR / UK GDPR wording?",
4611
5589
  options: [
4612
5590
  { value: "yes", label: "Yes. Include GDPR rights for EEA, UK, and Swiss residents" },
4613
5591
  { value: "no", label: "No" }
4614
5592
  ]
4615
5593
  });
4616
- if (p21.isCancel(gdpr)) onCancel();
5594
+ if (p23.isCancel(gdpr)) onCancel();
4617
5595
  let facebookFanPage = "no";
4618
5596
  const facebookDetails = { name: "", url: "" };
4619
5597
  if (gdpr === "yes") {
4620
- const fan = await p21.select({
5598
+ const fan = await p23.select({
4621
5599
  message: "Do you have a Facebook Fan Page?",
4622
5600
  options: [
4623
5601
  { value: "yes", label: "Yes, we have a Facebook Fan Page" },
4624
5602
  { value: "no", label: "No" }
4625
5603
  ]
4626
5604
  });
4627
- if (p21.isCancel(fan)) onCancel();
5605
+ if (p23.isCancel(fan)) onCancel();
4628
5606
  facebookFanPage = fan;
4629
5607
  if (facebookFanPage === "yes") {
4630
- const name = await p21.text({
5608
+ const name = await p23.text({
4631
5609
  message: "What is the name of the Facebook Fan Page?",
4632
5610
  placeholder: "My Facebook Page"
4633
5611
  });
4634
- if (p21.isCancel(name)) onCancel();
5612
+ if (p23.isCancel(name)) onCancel();
4635
5613
  facebookDetails.name = name;
4636
- const url = await p21.text({
5614
+ const url = await p23.text({
4637
5615
  message: "What is the URL of the Facebook Fan Page?",
4638
5616
  placeholder: "https://facebook.com/my-facebook-page"
4639
5617
  });
4640
- if (p21.isCancel(url)) onCancel();
5618
+ if (p23.isCancel(url)) onCancel();
4641
5619
  facebookDetails.url = url;
4642
5620
  }
4643
5621
  }
4644
- const kids = await p21.select({
5622
+ const kids = await p23.select({
4645
5623
  message: "Do you collect information from kids under the age of 13?",
4646
5624
  options: [
4647
5625
  { value: "yes", label: "Yes. We collect information from children under the age of 13" },
4648
5626
  { value: "no", label: "No" }
4649
5627
  ]
4650
5628
  });
4651
- if (p21.isCancel(kids)) onCancel();
4652
- const retention = await p21.text({
5629
+ if (p23.isCancel(kids)) onCancel();
5630
+ const retention = await p23.text({
4653
5631
  message: "How long do you retain personal information? (leave blank for default wording)",
4654
5632
  placeholder: "e.g. 12 months after account closure"
4655
5633
  });
4656
- if (p21.isCancel(retention)) onCancel();
5634
+ if (p23.isCancel(retention)) onCancel();
4657
5635
  const html = generatePrivacyHtml({
4658
5636
  core,
4659
5637
  personalInfo: personalInfo ?? [],
@@ -4676,28 +5654,28 @@ async function privacyAction() {
4676
5654
  kids,
4677
5655
  retention
4678
5656
  });
4679
- const privacyPage = path19.join(
5657
+ const privacyPage = path22.join(
4680
5658
  workspaceRootDir,
4681
5659
  publicRoutesDir,
4682
5660
  LEGAL_DIR,
4683
5661
  "privacy",
4684
5662
  "+page.svelte"
4685
5663
  );
4686
- const privacyPageTs = path19.join(
5664
+ const privacyPageTs = path22.join(
4687
5665
  workspaceRootDir,
4688
5666
  publicRoutesDir,
4689
5667
  LEGAL_DIR,
4690
5668
  "privacy",
4691
5669
  "+page.ts"
4692
5670
  );
4693
- fs19.mkdirSync(path19.dirname(privacyPage), { recursive: true });
4694
- fs19.writeFileSync(privacyPage, html);
4695
- fs19.writeFileSync(
5671
+ fs22.mkdirSync(path22.dirname(privacyPage), { recursive: true });
5672
+ fs22.writeFileSync(privacyPage, html);
5673
+ fs22.writeFileSync(
4696
5674
  privacyPageTs,
4697
5675
  pageMetaTagsLoader("Privacy Policy", `Privacy Policy for ${core.websiteName}`)
4698
5676
  );
4699
- const relativePrivacyPage = path19.relative(workspaceRootDir, privacyPage);
4700
- const relativePrivacyPageTs = path19.relative(workspaceRootDir, privacyPageTs);
5677
+ const relativePrivacyPage = path22.relative(workspaceRootDir, privacyPage);
5678
+ const relativePrivacyPageTs = path22.relative(workspaceRootDir, privacyPageTs);
4701
5679
  reportResult({
4702
5680
  summary: "Generated placeholder privacy policy.",
4703
5681
  filesCreated: [relativePrivacyPage, relativePrivacyPageTs],
@@ -4708,23 +5686,23 @@ async function privacyAction() {
4708
5686
  ]
4709
5687
  });
4710
5688
  }
4711
- var privacy = new Command43("privacy").description("generate placeholder privacy policy").configureHelp(helpConfig).action(() => runCommand(privacyAction, "Failed to generate privacy policy."));
5689
+ var privacy = new Command44("privacy").description("generate placeholder privacy policy").configureHelp(helpConfig).action(() => runCommand(privacyAction, "Failed to generate privacy policy."));
4712
5690
 
4713
5691
  // src/commands/legal.ts
4714
- var legal = new Command44("legal").description("generate placeholder legal documents").configureHelp(helpConfig).addCommand(terms).addCommand(privacy);
5692
+ var legal = new Command45("legal").description("generate placeholder legal documents").configureHelp(helpConfig).addCommand(terms).addCommand(privacy);
4715
5693
 
4716
5694
  // src/commands/fixtures.ts
4717
- import { Command as Command50 } from "commander";
5695
+ import { Command as Command51 } from "commander";
4718
5696
 
4719
5697
  // src/commands/fixtures/load.ts
4720
- import fs20 from "node:fs";
4721
- import path20 from "node:path";
4722
- import { Command as Command45 } from "commander";
5698
+ import fs23 from "node:fs";
5699
+ import path23 from "node:path";
5700
+ import { Command as Command46 } from "commander";
4723
5701
  function getFixtureFiles(cwd) {
4724
- const fixturesDir = path20.join(cwd, "data", "fixtures");
4725
- if (!fs20.existsSync(fixturesDir)) return [];
4726
- return fs20.readdirSync(fixturesDir).filter((file) => file.endsWith(".json")).map((file) => path20.join(fixturesDir, file)).sort((a, b) => a.localeCompare(b)).map((fixturePath) => {
4727
- const file = path20.basename(fixturePath);
5702
+ const fixturesDir = path23.join(cwd, "data", "fixtures");
5703
+ if (!fs23.existsSync(fixturesDir)) return [];
5704
+ return fs23.readdirSync(fixturesDir).filter((file) => file.endsWith(".json")).map((file) => path23.join(fixturesDir, file)).sort((a, b) => a.localeCompare(b)).map((fixturePath) => {
5705
+ const file = path23.basename(fixturePath);
4728
5706
  const nameWithoutExt = file.replace(/\.json$/i, "");
4729
5707
  const collectionName = nameWithoutExt.replace(/^\d+[-_]?/, "");
4730
5708
  return { collectionName, fixturePath };
@@ -4745,15 +5723,15 @@ async function loadFixtures(pb, workspaceRootDir) {
4745
5723
  `${collectionName} collection already has fixture records. Run \`vela fixtures reset\` to clear and reload, or \`vela fixtures clear\` first.`
4746
5724
  );
4747
5725
  }
4748
- const items = JSON.parse(fs20.readFileSync(fixturePath, "utf8"));
5726
+ const items = JSON.parse(fs23.readFileSync(fixturePath, "utf8"));
4749
5727
  for (const fixture of items) {
4750
5728
  await pb.collection(collectionName).create(fixture);
4751
5729
  }
4752
- loaded.push(`${path20.relative(workspaceRootDir, fixturePath)} (${items.length} records)`);
5730
+ loaded.push(`${path23.relative(workspaceRootDir, fixturePath)} (${items.length} records)`);
4753
5731
  }
4754
5732
  return loaded;
4755
5733
  }
4756
- var load = new Command45("load").description("load fixtures into the database").configureHelp(helpConfig).action(
5734
+ var load = new Command46("load").description("load fixtures into the database").configureHelp(helpConfig).action(
4757
5735
  () => runCommand(async () => {
4758
5736
  const { workspaceRootDir } = await getWorkspace();
4759
5737
  let loaded = [];
@@ -4779,8 +5757,8 @@ var load = new Command45("load").description("load fixtures into the database").
4779
5757
  );
4780
5758
 
4781
5759
  // src/commands/fixtures/clear.ts
4782
- import path21 from "node:path";
4783
- import { Command as Command46 } from "commander";
5760
+ import path24 from "node:path";
5761
+ import { Command as Command47 } from "commander";
4784
5762
 
4785
5763
  // src/lib/collections.ts
4786
5764
  function dependencyOrder(collections2, startingCollectionId) {
@@ -4842,12 +5820,12 @@ async function clearFixtures(pb, workspaceRootDir) {
4842
5820
  await pb.collection(collectionName).delete(id);
4843
5821
  }
4844
5822
  const fixturePath = fixturePathByName.get(collectionName);
4845
- const label = fixturePath ? path21.relative(workspaceRootDir, fixturePath) : collectionName;
5823
+ const label = fixturePath ? path24.relative(workspaceRootDir, fixturePath) : collectionName;
4846
5824
  cleared.push(`${label} (${records.length} records)`);
4847
5825
  }
4848
5826
  return cleared;
4849
5827
  }
4850
- var clear = new Command46("clear").description("clear loaded fixtures").configureHelp(helpConfig).action(
5828
+ var clear = new Command47("clear").description("clear loaded fixtures").configureHelp(helpConfig).action(
4851
5829
  () => runCommand(async () => {
4852
5830
  const { workspaceRootDir } = await getWorkspace();
4853
5831
  let cleared = [];
@@ -4876,8 +5854,8 @@ var clear = new Command46("clear").description("clear loaded fixtures").configur
4876
5854
  );
4877
5855
 
4878
5856
  // src/commands/fixtures/reset.ts
4879
- import { Command as Command47 } from "commander";
4880
- var reset = new Command47("reset").description("clear and reload fixtures").configureHelp(helpConfig).action(
5857
+ import { Command as Command48 } from "commander";
5858
+ var reset = new Command48("reset").description("clear and reload fixtures").configureHelp(helpConfig).action(
4881
5859
  () => runCommand(async () => {
4882
5860
  const { workspaceRootDir } = await getWorkspace();
4883
5861
  let cleared = [];
@@ -4907,10 +5885,10 @@ var reset = new Command47("reset").description("clear and reload fixtures").conf
4907
5885
  );
4908
5886
 
4909
5887
  // src/commands/fixtures/generate.ts
4910
- import fs21 from "node:fs";
4911
- import path22 from "node:path";
4912
- import { Command as Command48, InvalidArgumentError as InvalidArgumentError2 } from "commander";
4913
- import * as p22 from "@clack/prompts";
5888
+ import fs24 from "node:fs";
5889
+ import path25 from "node:path";
5890
+ import { Command as Command49, InvalidArgumentError as InvalidArgumentError2 } from "commander";
5891
+ import * as p24 from "@clack/prompts";
4914
5892
  import { annotate } from "annotate-json-schema";
4915
5893
  import { createGenerator } from "json-schema-faker";
4916
5894
  import { faker } from "@faker-js/faker";
@@ -4957,10 +5935,10 @@ async function loadCollections(pb) {
4957
5935
  }));
4958
5936
  }
4959
5937
  async function generateFixtureFiles(pb, workspaceRootDir, opts) {
4960
- const fixturesDir = path22.join(workspaceRootDir, "data", "fixtures");
4961
- fs21.mkdirSync(fixturesDir, { recursive: true });
4962
- for (const file of fs21.readdirSync(fixturesDir)) {
4963
- if (file.endsWith(".json")) fs21.unlinkSync(path22.join(fixturesDir, file));
5938
+ const fixturesDir = path25.join(workspaceRootDir, "data", "fixtures");
5939
+ fs24.mkdirSync(fixturesDir, { recursive: true });
5940
+ for (const file of fs24.readdirSync(fixturesDir)) {
5941
+ if (file.endsWith(".json")) fs24.unlinkSync(path25.join(fixturesDir, file));
4964
5942
  }
4965
5943
  if (opts.seed !== void 0) faker.seed(opts.seed);
4966
5944
  const generator = createGenerator({
@@ -5024,13 +6002,13 @@ async function generateFixtureFiles(pb, workspaceRootDir, opts) {
5024
6002
  items.push(record);
5025
6003
  }
5026
6004
  const filename = `${padZeros(fileIndex, 2)}-${collection.name}.json`;
5027
- fs21.writeFileSync(path22.join(fixturesDir, filename), JSON.stringify(items, null, 2));
5028
- writtenFiles.push(`${path22.join("data", "fixtures", filename)} (${items.length} records)`);
6005
+ fs24.writeFileSync(path25.join(fixturesDir, filename), JSON.stringify(items, null, 2));
6006
+ writtenFiles.push(`${path25.join("data", "fixtures", filename)} (${items.length} records)`);
5029
6007
  fileIndex++;
5030
6008
  }
5031
6009
  return { writtenFiles, warnings };
5032
6010
  }
5033
- var generate2 = new Command48("generate").description("generate fixture data").option("-c, --count <count>", "number of records per collection", parseCount, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed).option("-f, --force", "overwrite existing fixture files and clear loaded fixtures").configureHelp(helpConfig).action(
6011
+ var generate2 = new Command49("generate").description("generate fixture data").option("-c, --count <count>", "number of records per collection", parseCount, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed).option("-f, --force", "overwrite existing fixture files and clear loaded fixtures").configureHelp(helpConfig).action(
5034
6012
  (opts) => runCommand(async () => {
5035
6013
  const { workspaceRootDir } = await getWorkspace();
5036
6014
  const existing = getFixtureFiles(workspaceRootDir);
@@ -5059,7 +6037,7 @@ var generate2 = new Command48("generate").description("generate fixture data").o
5059
6037
  seed: opts.seed
5060
6038
  });
5061
6039
  });
5062
- for (const warning of result.warnings) p22.log.warn(warning);
6040
+ for (const warning of result.warnings) p24.log.warn(warning);
5063
6041
  if (result.writtenFiles.length === 0) {
5064
6042
  reportResult({
5065
6043
  summary: "No eligible collections found to generate fixtures for.",
@@ -5083,8 +6061,8 @@ var generate2 = new Command48("generate").description("generate fixture data").o
5083
6061
  );
5084
6062
 
5085
6063
  // src/commands/fixtures/regen.ts
5086
- import { Command as Command49, InvalidArgumentError as InvalidArgumentError3 } from "commander";
5087
- import * as p23 from "@clack/prompts";
6064
+ import { Command as Command50, InvalidArgumentError as InvalidArgumentError3 } from "commander";
6065
+ import * as p25 from "@clack/prompts";
5088
6066
  function parseCount2(value) {
5089
6067
  const n = parseInt(value, 10);
5090
6068
  if (!Number.isFinite(n) || n <= 0 || n > 999) {
@@ -5099,7 +6077,7 @@ function parseSeed2(value) {
5099
6077
  }
5100
6078
  return n;
5101
6079
  }
5102
- var regen = new Command49("regen").description("clear the database, regenerate fixture files, and reload them").option("-c, --count <count>", "number of records per collection", parseCount2, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed2).configureHelp(helpConfig).action(
6080
+ var regen = new Command50("regen").description("clear the database, regenerate fixture files, and reload them").option("-c, --count <count>", "number of records per collection", parseCount2, 10).option("-s, --seed <seed>", "seed for deterministic output", parseSeed2).configureHelp(helpConfig).action(
5103
6081
  (opts) => runCommand(async () => {
5104
6082
  const { workspaceRootDir } = await getWorkspace();
5105
6083
  let cleared = [];
@@ -5113,7 +6091,7 @@ var regen = new Command49("regen").description("clear the database, regenerate f
5113
6091
  });
5114
6092
  loaded = await loadFixtures(pb, workspaceRootDir);
5115
6093
  });
5116
- for (const warning of result.warnings) p23.log.warn(warning);
6094
+ for (const warning of result.warnings) p25.log.warn(warning);
5117
6095
  reportResult({
5118
6096
  summary: `Regenerated fixtures (${loaded.length} collection(s) reloaded).`,
5119
6097
  filesCreated: result.writtenFiles,
@@ -5128,26 +6106,26 @@ var regen = new Command49("regen").description("clear the database, regenerate f
5128
6106
  );
5129
6107
 
5130
6108
  // src/commands/fixtures.ts
5131
- var fixtures = new Command50("fixtures").description("manage fixture data").configureHelp(helpConfig).addCommand(generate2).addCommand(load).addCommand(clear).addCommand(reset).addCommand(regen);
6109
+ var fixtures = new Command51("fixtures").description("manage fixture data").configureHelp(helpConfig).addCommand(generate2).addCommand(load).addCommand(clear).addCommand(reset).addCommand(regen);
5132
6110
 
5133
6111
  // src/commands/seeds.ts
5134
- import { Command as Command54 } from "commander";
6112
+ import { Command as Command55 } from "commander";
5135
6113
 
5136
6114
  // src/commands/seeds/load.ts
5137
- import fs22 from "node:fs";
5138
- import path23 from "node:path";
5139
- import { Command as Command51 } from "commander";
6115
+ import fs25 from "node:fs";
6116
+ import path26 from "node:path";
6117
+ import { Command as Command52 } from "commander";
5140
6118
  function getSeedFiles(cwd) {
5141
- const seedsDir = path23.join(cwd, "data", "seeds");
5142
- if (!fs22.existsSync(seedsDir)) return [];
5143
- return fs22.readdirSync(seedsDir).filter((file) => file.endsWith(".json")).map((file) => path23.join(seedsDir, file)).sort((a, b) => a.localeCompare(b)).map((seedPath) => {
5144
- const file = path23.basename(seedPath);
6119
+ const seedsDir = path26.join(cwd, "data", "seeds");
6120
+ if (!fs25.existsSync(seedsDir)) return [];
6121
+ return fs25.readdirSync(seedsDir).filter((file) => file.endsWith(".json")).map((file) => path26.join(seedsDir, file)).sort((a, b) => a.localeCompare(b)).map((seedPath) => {
6122
+ const file = path26.basename(seedPath);
5145
6123
  const nameWithoutExt = file.replace(/\.json$/i, "");
5146
6124
  const collectionName = nameWithoutExt.replace(/^\d+[-_]?/, "");
5147
6125
  return { collectionName, seedPath };
5148
6126
  });
5149
6127
  }
5150
- var load2 = new Command51("load").description("load seeds into the database").option("-f, --force", "load even if target collections already have records").configureHelp(helpConfig).action(
6128
+ var load2 = new Command52("load").description("load seeds into the database").option("-f, --force", "load even if target collections already have records").configureHelp(helpConfig).action(
5151
6129
  (opts) => runCommand(async () => {
5152
6130
  const { workspaceRootDir } = await getWorkspace();
5153
6131
  const seedFiles = getSeedFiles(workspaceRootDir);
@@ -5171,11 +6149,11 @@ var load2 = new Command51("load").description("load seeds into the database").op
5171
6149
  }
5172
6150
  }
5173
6151
  for (const { collectionName, seedPath } of seedFiles) {
5174
- const seeds2 = JSON.parse(fs22.readFileSync(seedPath, "utf8"));
6152
+ const seeds2 = JSON.parse(fs25.readFileSync(seedPath, "utf8"));
5175
6153
  for (const seed of seeds2) {
5176
6154
  await pb.collection(collectionName).create(seed);
5177
6155
  }
5178
- loaded.push(`${path23.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
6156
+ loaded.push(`${path26.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
5179
6157
  }
5180
6158
  });
5181
6159
  reportResult({
@@ -5190,9 +6168,9 @@ var load2 = new Command51("load").description("load seeds into the database").op
5190
6168
  );
5191
6169
 
5192
6170
  // src/commands/seeds/save.ts
5193
- import fs23 from "node:fs";
5194
- import path24 from "node:path";
5195
- import { Command as Command52 } from "commander";
6171
+ import fs26 from "node:fs";
6172
+ import path27 from "node:path";
6173
+ import { Command as Command53 } from "commander";
5196
6174
  var padZeros2 = (num, length) => num.toString().padStart(length, "0");
5197
6175
  function filterSystemFields(record, systemFieldNames) {
5198
6176
  const out = {};
@@ -5203,18 +6181,18 @@ function filterSystemFields(record, systemFieldNames) {
5203
6181
  }
5204
6182
  return out;
5205
6183
  }
5206
- var save = new Command52("save").description("save the current data as seeds").option("-f, --force", "overwrite existing seed files").configureHelp(helpConfig).action(
6184
+ var save = new Command53("save").description("save the current data as seeds").option("-f, --force", "overwrite existing seed files").configureHelp(helpConfig).action(
5207
6185
  (opts) => runCommand(async () => {
5208
6186
  const { workspaceRootDir } = await getWorkspace();
5209
- const seedsPath = path24.join(workspaceRootDir, "data", "seeds");
6187
+ const seedsPath = path27.join(workspaceRootDir, "data", "seeds");
5210
6188
  const existing = getSeedFiles(workspaceRootDir);
5211
6189
  if (existing.length > 0 && !opts.force) {
5212
6190
  throw new Error("Existing seed files found in data/seeds. Pass --force to overwrite.");
5213
6191
  }
5214
- fs23.mkdirSync(seedsPath, { recursive: true });
6192
+ fs26.mkdirSync(seedsPath, { recursive: true });
5215
6193
  if (opts.force) {
5216
- for (const file of fs23.readdirSync(seedsPath)) {
5217
- if (file.endsWith(".json")) fs23.unlinkSync(path24.join(seedsPath, file));
6194
+ for (const file of fs26.readdirSync(seedsPath)) {
6195
+ if (file.endsWith(".json")) fs26.unlinkSync(path27.join(seedsPath, file));
5218
6196
  }
5219
6197
  }
5220
6198
  const saved = [];
@@ -5243,13 +6221,13 @@ var save = new Command52("save").description("save the current data as seeds").o
5243
6221
  const filtered = records.map(
5244
6222
  (r) => filterSystemFields(r, systemFieldNames)
5245
6223
  );
5246
- const relativeSeedPath = path24.join(
6224
+ const relativeSeedPath = path27.join(
5247
6225
  "data",
5248
6226
  "seeds",
5249
6227
  `${padZeros2(count, 2)}-${collectionName}.json`
5250
6228
  );
5251
- const seedPath = path24.join(workspaceRootDir, relativeSeedPath);
5252
- fs23.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
6229
+ const seedPath = path27.join(workspaceRootDir, relativeSeedPath);
6230
+ fs26.writeFileSync(seedPath, JSON.stringify(filtered, null, 2));
5253
6231
  saved.push(`${relativeSeedPath} (${filtered.length} records)`);
5254
6232
  count++;
5255
6233
  }
@@ -5275,21 +6253,21 @@ var save = new Command52("save").description("save the current data as seeds").o
5275
6253
  );
5276
6254
 
5277
6255
  // src/commands/seeds/clear.ts
5278
- import fs24 from "node:fs";
5279
- import path25 from "node:path";
5280
- import { Command as Command53 } from "commander";
6256
+ import fs27 from "node:fs";
6257
+ import path28 from "node:path";
6258
+ import { Command as Command54 } from "commander";
5281
6259
  async function clearSeeds(pb, workspaceRootDir, seedFiles) {
5282
6260
  const cleared = [];
5283
6261
  for (const { collectionName, seedPath } of seedFiles) {
5284
- const seeds2 = JSON.parse(fs24.readFileSync(seedPath, "utf8"));
6262
+ const seeds2 = JSON.parse(fs27.readFileSync(seedPath, "utf8"));
5285
6263
  for (const { id } of seeds2) {
5286
6264
  await pb.collection(collectionName).delete(id);
5287
6265
  }
5288
- cleared.push(`${path25.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
6266
+ cleared.push(`${path28.relative(workspaceRootDir, seedPath)} (${seeds2.length} records)`);
5289
6267
  }
5290
6268
  return cleared;
5291
6269
  }
5292
- var clear2 = new Command53("clear").description("clear seeded records").configureHelp(helpConfig).action(
6270
+ var clear2 = new Command54("clear").description("clear seeded records").configureHelp(helpConfig).action(
5293
6271
  () => runCommand(async () => {
5294
6272
  const { workspaceRootDir } = await getWorkspace();
5295
6273
  const seedFiles = getSeedFiles(workspaceRootDir);
@@ -5320,23 +6298,23 @@ var clear2 = new Command53("clear").description("clear seeded records").configur
5320
6298
  );
5321
6299
 
5322
6300
  // src/commands/seeds.ts
5323
- var seeds = new Command54("seeds").description("manage seed data").configureHelp(helpConfig).addCommand(load2).addCommand(save).addCommand(clear2);
6301
+ var seeds = new Command55("seeds").description("manage seed data").configureHelp(helpConfig).addCommand(load2).addCommand(save).addCommand(clear2);
5324
6302
 
5325
6303
  // src/commands/signup.ts
5326
- import { Command as Command56 } from "commander";
5327
- import * as p25 from "@clack/prompts";
6304
+ import { Command as Command57 } from "commander";
6305
+ import * as p27 from "@clack/prompts";
5328
6306
  import makeFetchCookie2 from "fetch-cookie";
5329
6307
 
5330
6308
  // src/commands/login.ts
5331
- import os2 from "node:os";
5332
- import { Command as Command55 } from "commander";
5333
- import * as p24 from "@clack/prompts";
6309
+ import os3 from "node:os";
6310
+ import { Command as Command56 } from "commander";
6311
+ import * as p26 from "@clack/prompts";
5334
6312
  import makeFetchCookie from "fetch-cookie";
5335
- var login = new Command55("login").description("login to velastack.dev").configureHelp(helpConfig).action(
6313
+ var login = new Command56("login").description("login to velastack.dev").configureHelp(helpConfig).action(
5336
6314
  () => runCommand(async () => {
5337
- const { email: email3, password: password8 } = await p24.group({
5338
- email: () => p24.text({ message: "Email" }),
5339
- password: () => p24.password({ message: "Password" })
6315
+ const { email: email3, password: password10 } = await p26.group({
6316
+ email: () => p26.text({ message: "Email" }),
6317
+ password: () => p26.password({ message: "Password" })
5340
6318
  });
5341
6319
  const fetchCookie = makeFetchCookie(fetch);
5342
6320
  const loginRes = await fetchCookie(`${API_URL}/login`, {
@@ -5348,7 +6326,7 @@ var login = new Command55("login").description("login to velastack.dev").configu
5348
6326
  body: new URLSearchParams({
5349
6327
  type: "password",
5350
6328
  email: email3,
5351
- password: password8
6329
+ password: password10
5352
6330
  }).toString()
5353
6331
  });
5354
6332
  if (!loginRes.headers.get("Set-Cookie")) {
@@ -5358,11 +6336,11 @@ var login = new Command55("login").description("login to velastack.dev").configu
5358
6336
  }
5359
6337
  const apiKey = await issueApiKey(fetchCookie);
5360
6338
  writeConfig({ apiKey });
5361
- p24.log.success("Logged in to velastack.dev");
6339
+ p26.log.success("Logged in to velastack.dev");
5362
6340
  }, "Failed to login.")
5363
6341
  );
5364
6342
  async function issueApiKey(fetchCookie) {
5365
- const label = `CLI - ${os2.hostname()}`;
6343
+ const label = `CLI - ${os3.hostname()}`;
5366
6344
  await fetchCookie(`${API_URL}/api-keys/new`, {
5367
6345
  method: "POST",
5368
6346
  headers: {
@@ -5399,14 +6377,14 @@ function extractApiKey(cookie) {
5399
6377
  }
5400
6378
 
5401
6379
  // src/commands/signup.ts
5402
- var signup = new Command56("signup").description("signup to velastack.dev").configureHelp(helpConfig).action(
6380
+ var signup = new Command57("signup").description("signup to velastack.dev").configureHelp(helpConfig).action(
5403
6381
  () => runCommand(async () => {
5404
- const { email: email3, password: password8, passwordConfirm } = await p25.group({
5405
- email: () => p25.text({ message: "Email" }),
5406
- password: () => p25.password({ message: "Password" }),
5407
- passwordConfirm: () => p25.password({ message: "Confirm password" })
6382
+ const { email: email3, password: password10, passwordConfirm } = await p27.group({
6383
+ email: () => p27.text({ message: "Email" }),
6384
+ password: () => p27.password({ message: "Password" }),
6385
+ passwordConfirm: () => p27.password({ message: "Confirm password" })
5408
6386
  });
5409
- if (password8 !== passwordConfirm) {
6387
+ if (password10 !== passwordConfirm) {
5410
6388
  throw new Error("Passwords do not match.");
5411
6389
  }
5412
6390
  const fetchCookie = makeFetchCookie2(fetch);
@@ -5419,7 +6397,7 @@ var signup = new Command56("signup").description("signup to velastack.dev").conf
5419
6397
  body: new URLSearchParams({
5420
6398
  type: "password",
5421
6399
  email: email3,
5422
- password: password8,
6400
+ password: password10,
5423
6401
  passwordConfirm
5424
6402
  }).toString()
5425
6403
  });
@@ -5428,33 +6406,33 @@ var signup = new Command56("signup").description("signup to velastack.dev").conf
5428
6406
  }
5429
6407
  const apiKey = await issueApiKey(fetchCookie);
5430
6408
  writeConfig({ apiKey });
5431
- p25.log.success("Signed up to velastack.dev");
5432
- p25.log.info("Check your email for a confirmation link.");
6409
+ p27.log.success("Signed up to velastack.dev");
6410
+ p27.log.info("Check your email for a confirmation link.");
5433
6411
  }, "Failed to signup.")
5434
6412
  );
5435
6413
 
5436
6414
  // src/commands/logout.ts
5437
- import { Command as Command57 } from "commander";
5438
- import * as p26 from "@clack/prompts";
5439
- var logout = new Command57("logout").alias("signout").description("logout from velastack.dev").configureHelp(helpConfig).action(
6415
+ import { Command as Command58 } from "commander";
6416
+ import * as p28 from "@clack/prompts";
6417
+ var logout = new Command58("logout").alias("signout").description("logout from velastack.dev").configureHelp(helpConfig).action(
5440
6418
  () => runCommand(() => {
5441
6419
  if (!readConfig()) {
5442
- p26.log.info("Not logged in");
6420
+ p28.log.info("Not logged in");
5443
6421
  return;
5444
6422
  }
5445
6423
  clearConfig();
5446
- p26.log.success("Logged out of velastack.dev");
6424
+ p28.log.success("Logged out of velastack.dev");
5447
6425
  })
5448
6426
  );
5449
6427
 
5450
6428
  // src/commands/whoami.ts
5451
- import { Command as Command58 } from "commander";
5452
- import * as p27 from "@clack/prompts";
5453
- var whoami = new Command58("whoami").description("show the current user").configureHelp(helpConfig).action(
6429
+ import { Command as Command59 } from "commander";
6430
+ import * as p29 from "@clack/prompts";
6431
+ var whoami = new Command59("whoami").description("show the current user").configureHelp(helpConfig).action(
5454
6432
  () => runCommand(async () => {
5455
6433
  const apiKey = readConfig()?.apiKey;
5456
6434
  if (!apiKey) {
5457
- p27.log.info("Not logged in. Run `vela login` to login.");
6435
+ p29.log.info("Not logged in. Run `vela login` to login.");
5458
6436
  return;
5459
6437
  }
5460
6438
  const res = await fetch(`${API_URL}/api/collections/users/records`, {
@@ -5462,31 +6440,31 @@ var whoami = new Command58("whoami").description("show the current user").config
5462
6440
  });
5463
6441
  const data = await res.json();
5464
6442
  if (!data.items.length) throw new Error("No user found. Run `vela login` to login.");
5465
- p27.log.success(`Logged in as ${data.items[0].email}`);
6443
+ p29.log.success(`Logged in as ${data.items[0].email}`);
5466
6444
  })
5467
6445
  );
5468
6446
 
5469
6447
  // src/commands/migrate.ts
5470
- import { Command as Command64 } from "commander";
6448
+ import { Command as Command65 } from "commander";
5471
6449
 
5472
6450
  // src/commands/migrate/up.ts
5473
- import { Command as Command59 } from "commander";
6451
+ import { Command as Command60 } from "commander";
5474
6452
 
5475
6453
  // src/lib/migrate.ts
5476
- import path26 from "node:path";
5477
- import process14 from "node:process";
6454
+ import path29 from "node:path";
6455
+ import process17 from "node:process";
5478
6456
  import { x as x2 } from "tinyexec";
5479
6457
  async function runPocketbaseMigrate(args) {
5480
- const cwd = process14.cwd();
6458
+ const cwd = process17.cwd();
5481
6459
  const { getBinaryPath } = await import("pocketbase-server");
5482
6460
  const binaryPath = getBinaryPath();
5483
6461
  await x2(
5484
6462
  binaryPath,
5485
6463
  [
5486
6464
  "--dir",
5487
- path26.join(cwd, DATA_DIR),
6465
+ path29.join(cwd, DATA_DIR),
5488
6466
  "--migrationsDir",
5489
- path26.join(cwd, MIGRATIONS_DIR),
6467
+ path29.join(cwd, MIGRATIONS_DIR),
5490
6468
  "migrate",
5491
6469
  ...args
5492
6470
  ],
@@ -5509,10 +6487,10 @@ async function runMigrateUp() {
5509
6487
  ]
5510
6488
  });
5511
6489
  }
5512
- var up = new Command59("up").description("apply all pending migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations."));
6490
+ var up = new Command60("up").description("apply all pending migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations."));
5513
6491
 
5514
6492
  // src/commands/migrate/down.ts
5515
- import { Command as Command60, InvalidArgumentError as InvalidArgumentError4 } from "commander";
6493
+ import { Command as Command61, InvalidArgumentError as InvalidArgumentError4 } from "commander";
5516
6494
  function parseSteps(value) {
5517
6495
  const n = parseInt(value, 10);
5518
6496
  if (!Number.isFinite(n) || n <= 0) {
@@ -5520,7 +6498,7 @@ function parseSteps(value) {
5520
6498
  }
5521
6499
  return n;
5522
6500
  }
5523
- var down = new Command60("down").alias("rollback").description("revert the last N applied migrations").argument("[number]", "how many migrations to revert", parseSteps, 1).configureHelp(helpConfig).action(
6501
+ var down = new Command61("down").alias("rollback").description("revert the last N applied migrations").argument("[number]", "how many migrations to revert", parseSteps, 1).configureHelp(helpConfig).action(
5524
6502
  (n) => runCommand(async () => {
5525
6503
  await runPocketbaseMigrate(["down", String(n)]);
5526
6504
  reportResult({
@@ -5534,20 +6512,20 @@ var down = new Command60("down").alias("rollback").description("revert the last
5534
6512
  );
5535
6513
 
5536
6514
  // src/commands/migrate/create.ts
5537
- import fs25 from "node:fs";
5538
- import path27 from "node:path";
5539
- import process15 from "node:process";
5540
- import { Command as Command61 } from "commander";
5541
- var create2 = new Command61("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
6515
+ import fs28 from "node:fs";
6516
+ import path30 from "node:path";
6517
+ import process18 from "node:process";
6518
+ import { Command as Command62 } from "commander";
6519
+ var create2 = new Command62("create").alias("new").description("create a new blank migration").argument("<name>", "migration name (snake_case)").configureHelp(helpConfig).action(
5542
6520
  (name) => runCommand(async () => {
5543
- const cwd = process15.cwd();
6521
+ const cwd = process18.cwd();
5544
6522
  const before = listMigrationFiles(cwd);
5545
6523
  await runPocketbaseMigrate(["create", name]);
5546
6524
  const after = listMigrationFiles(cwd);
5547
6525
  const added = [...after].filter((f) => !before.has(f));
5548
6526
  reportResult({
5549
6527
  summary: `Created blank migration ${name}.`,
5550
- filesCreated: added.map((f) => path27.join(MIGRATIONS_DIR, f)),
6528
+ filesCreated: added.map((f) => path30.join(MIGRATIONS_DIR, f)),
5551
6529
  nextSteps: [
5552
6530
  `Open the new file in ${MIGRATIONS_DIR}/ and fill in the up/down handlers.`,
5553
6531
  "Run `vela migrate up` to apply the migration once the handlers are written."
@@ -5556,19 +6534,19 @@ var create2 = new Command61("create").alias("new").description("create a new bla
5556
6534
  }, "Failed to create migration.")
5557
6535
  );
5558
6536
  function listMigrationFiles(cwd) {
5559
- const dir = path27.join(cwd, MIGRATIONS_DIR);
5560
- if (!fs25.existsSync(dir)) return /* @__PURE__ */ new Set();
5561
- return new Set(fs25.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
6537
+ const dir = path30.join(cwd, MIGRATIONS_DIR);
6538
+ if (!fs28.existsSync(dir)) return /* @__PURE__ */ new Set();
6539
+ return new Set(fs28.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
5562
6540
  }
5563
6541
 
5564
6542
  // src/commands/migrate/collections.ts
5565
- import fs26 from "node:fs";
5566
- import path28 from "node:path";
5567
- import process16 from "node:process";
5568
- import { Command as Command62 } from "commander";
5569
- var collections = new Command62("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
6543
+ import fs29 from "node:fs";
6544
+ import path31 from "node:path";
6545
+ import process19 from "node:process";
6546
+ import { Command as Command63 } from "commander";
6547
+ var collections = new Command63("collections").alias("snapshot").description("snapshot local collections into a new migration").configureHelp(helpConfig).action(
5570
6548
  () => runCommand(async () => {
5571
- const cwd = process16.cwd();
6549
+ const cwd = process19.cwd();
5572
6550
  const before = listMigrationFiles2(cwd);
5573
6551
  await runPocketbaseMigrate(["collections"]);
5574
6552
  const after = listMigrationFiles2(cwd);
@@ -5581,7 +6559,7 @@ var collections = new Command62("collections").alias("snapshot").description("sn
5581
6559
  }
5582
6560
  reportResult({
5583
6561
  summary: "Snapshotted local collections into a new migration.",
5584
- filesCreated: added.map((f) => path28.join(MIGRATIONS_DIR, f)),
6562
+ filesCreated: added.map((f) => path31.join(MIGRATIONS_DIR, f)),
5585
6563
  nextSteps: [
5586
6564
  `Review the generated snapshot in ${MIGRATIONS_DIR}/.`,
5587
6565
  "Commit the snapshot so teammates pick up the new schema.",
@@ -5591,14 +6569,14 @@ var collections = new Command62("collections").alias("snapshot").description("sn
5591
6569
  }, "Failed to snapshot collections.")
5592
6570
  );
5593
6571
  function listMigrationFiles2(cwd) {
5594
- const dir = path28.join(cwd, MIGRATIONS_DIR);
5595
- if (!fs26.existsSync(dir)) return /* @__PURE__ */ new Set();
5596
- return new Set(fs26.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
6572
+ const dir = path31.join(cwd, MIGRATIONS_DIR);
6573
+ if (!fs29.existsSync(dir)) return /* @__PURE__ */ new Set();
6574
+ return new Set(fs29.readdirSync(dir).filter((f) => /\.[jt]s$/.test(f)));
5597
6575
  }
5598
6576
 
5599
6577
  // src/commands/migrate/history-sync.ts
5600
- import { Command as Command63 } from "commander";
5601
- var historySync = new Command63("history-sync").description("drop _migrations rows whose files no longer exist").configureHelp(helpConfig).action(
6578
+ import { Command as Command64 } from "commander";
6579
+ var historySync = new Command64("history-sync").description("drop _migrations rows whose files no longer exist").configureHelp(helpConfig).action(
5602
6580
  () => runCommand(async () => {
5603
6581
  await runPocketbaseMigrate(["history-sync"]);
5604
6582
  reportResult({
@@ -5609,16 +6587,55 @@ var historySync = new Command63("history-sync").description("drop _migrations ro
5609
6587
  );
5610
6588
 
5611
6589
  // src/commands/migrate.ts
5612
- var migrate = new Command64("migrate").description("manage database migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations.")).addCommand(up).addCommand(down).addCommand(create2).addCommand(collections).addCommand(historySync);
6590
+ var migrate = new Command65("migrate").description("manage database migrations").configureHelp(helpConfig).action(() => runCommand(runMigrateUp, "Failed to run migrations.")).addCommand(up).addCommand(down).addCommand(create2).addCommand(collections).addCommand(historySync);
5613
6591
 
5614
6592
  // src/commands/dev.ts
5615
- import fs27 from "node:fs";
5616
- import path29 from "node:path";
5617
- import process17 from "node:process";
6593
+ import fs30 from "node:fs";
6594
+ import path33 from "node:path";
6595
+ import process21 from "node:process";
5618
6596
  import { performance } from "node:perf_hooks";
5619
- import { Command as Command65, InvalidArgumentError as InvalidArgumentError5 } from "commander";
5620
- import pc5 from "picocolors";
6597
+ import { Command as Command66, InvalidArgumentError as InvalidArgumentError5 } from "commander";
6598
+ import pc8 from "picocolors";
5621
6599
  import PocketBase2 from "pocketbase";
6600
+
6601
+ // src/lib/vite.ts
6602
+ import path32 from "node:path";
6603
+ import process20 from "node:process";
6604
+ import { createRequire as createRequire2 } from "node:module";
6605
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
6606
+ import pc7 from "picocolors";
6607
+ var REQUIRED_VITE_MAJOR = 8;
6608
+ function viteVersionError(version) {
6609
+ const major = Number.parseInt(version, 10);
6610
+ if (!Number.isFinite(major) || major >= REQUIRED_VITE_MAJOR) return null;
6611
+ return [
6612
+ `This project has vite ${version}, but vela needs vite ${REQUIRED_VITE_MAJOR} or newer.`,
6613
+ "",
6614
+ " npm install -D vite@^8 @sveltejs/vite-plugin-svelte@^7",
6615
+ "",
6616
+ "Both move together: @sveltejs/vite-plugin-svelte 7 requires vite 8, and",
6617
+ "version 6 is the last that supports vite 7."
6618
+ ].join("\n");
6619
+ }
6620
+ function resolveProjectVite(cwd) {
6621
+ try {
6622
+ return createRequire2(path32.join(cwd, "package.json")).resolve("vite");
6623
+ } catch {
6624
+ return null;
6625
+ }
6626
+ }
6627
+ async function loadVite(cwd = process20.cwd()) {
6628
+ const entry = resolveProjectVite(cwd);
6629
+ const vite = entry ? await import(pathToFileURL2(entry).href) : await import("vite");
6630
+ const error = viteVersionError(vite.version);
6631
+ if (error) {
6632
+ console.error(`${pc7.redBright("\u2717")} ${error}`);
6633
+ process20.exit(1);
6634
+ }
6635
+ return vite;
6636
+ }
6637
+
6638
+ // src/commands/dev.ts
5622
6639
  function parsePort(value) {
5623
6640
  const port = Number(value);
5624
6641
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
@@ -5626,38 +6643,38 @@ function parsePort(value) {
5626
6643
  }
5627
6644
  return port;
5628
6645
  }
5629
- var dev = new Command65("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) => {
5630
- const cwd = process17.cwd();
6646
+ var dev = new Command66("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) => {
6647
+ const cwd = process21.cwd();
5631
6648
  const startTime = performance.now();
5632
- const { createServer, version } = await import("vite");
5633
- const viteMetadataDir = path29.join(cwd, "node_modules", ".vite");
5634
- const viteMetadataFile = path29.join(viteMetadataDir, "_pocketbase_metadata.json");
6649
+ const { createServer, version } = await loadVite(cwd);
6650
+ const viteMetadataDir = path33.join(cwd, "node_modules", ".vite");
6651
+ const viteMetadataFile = path33.join(viteMetadataDir, "_pocketbase_metadata.json");
5635
6652
  let pbProc;
5636
6653
  const backend3 = hasBackend(cwd);
5637
- const needsStart = backend3 && !process17.env.POCKETBASE_URL;
6654
+ const needsStart = backend3 && !process21.env.POCKETBASE_URL;
5638
6655
  const cleanup = () => {
5639
6656
  if (pbProc?.pid) pbProc.kill();
5640
- if (fs27.existsSync(viteMetadataFile)) fs27.rmSync(viteMetadataFile);
6657
+ if (fs30.existsSync(viteMetadataFile)) fs30.rmSync(viteMetadataFile);
5641
6658
  };
5642
6659
  if (needsStart) {
5643
- const dataDir = path29.join(cwd, DATA_DIR);
6660
+ const dataDir = path33.join(cwd, DATA_DIR);
5644
6661
  const started = await startPocketbaseServe({
5645
6662
  dataDir,
5646
6663
  migrationsDir: MIGRATIONS_DIR,
5647
- hooksDir: path29.join(dataDir, "hooks"),
6664
+ hooksDir: path33.join(dataDir, "hooks"),
5648
6665
  dev: true,
5649
6666
  stdio: "pipe"
5650
6667
  });
5651
6668
  pbProc = started.proc;
5652
- process17.env.POCKETBASE_URL = started.url;
5653
- pbProc.stdout?.pipe(process17.stdout);
5654
- pbProc.stderr?.pipe(process17.stderr);
6669
+ process21.env.POCKETBASE_URL = started.url;
6670
+ pbProc.stdout?.pipe(process21.stdout);
6671
+ pbProc.stderr?.pipe(process21.stderr);
5655
6672
  pbProc.on("error", (err) => console.error("PocketBase error:", err));
5656
6673
  pbProc.on("exit", (code) => console.log(`PocketBase exited with code ${code}`));
5657
- process17.on("exit", cleanup);
5658
- process17.on("SIGINT", () => {
6674
+ process21.on("exit", cleanup);
6675
+ process21.on("SIGINT", () => {
5659
6676
  cleanup();
5660
- process17.exit(0);
6677
+ process21.exit(0);
5661
6678
  });
5662
6679
  }
5663
6680
  const serverOptions = {};
@@ -5675,31 +6692,31 @@ var dev = new Command65("dev").description("start the development server").optio
5675
6692
  if (!backend3) return;
5676
6693
  const { address, port: vitePort } = server.httpServer.address();
5677
6694
  const viteHost = address === "::1" ? "localhost" : address;
5678
- await fs27.promises.mkdir(viteMetadataDir, { recursive: true });
5679
- await fs27.promises.writeFile(
6695
+ await fs30.promises.mkdir(viteMetadataDir, { recursive: true });
6696
+ await fs30.promises.writeFile(
5680
6697
  viteMetadataFile,
5681
6698
  JSON.stringify({
5682
- pocketbaseUrl: process17.env.POCKETBASE_URL,
6699
+ pocketbaseUrl: process21.env.POCKETBASE_URL,
5683
6700
  vitePort,
5684
6701
  viteHost
5685
6702
  })
5686
6703
  );
5687
- const pb = new PocketBase2(process17.env.POCKETBASE_URL);
6704
+ const pb = new PocketBase2(process21.env.POCKETBASE_URL);
5688
6705
  await pb.collection("_superusers").authWithPassword(
5689
- process17.env.POCKETBASE_SUPERUSER_EMAIL,
5690
- process17.env.POCKETBASE_SUPERUSER_PASSWORD
6706
+ process21.env.POCKETBASE_SUPERUSER_EMAIL,
6707
+ process21.env.POCKETBASE_SUPERUSER_PASSWORD
5691
6708
  );
5692
6709
  await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
5693
6710
  await startWatchingTypes(cwd, pb);
5694
6711
  });
5695
6712
  await server.listen();
5696
- const hasExistingLogs = process17.stdout.bytesWritten > 0 || process17.stderr.bytesWritten > 0;
5697
- const startupDurationString = pc5.dim(
5698
- `ready in ${pc5.reset(pc5.bold(Math.ceil(performance.now() - startTime)))} ms`
6713
+ const hasExistingLogs = process21.stdout.bytesWritten > 0 || process21.stderr.bytesWritten > 0;
6714
+ const startupDurationString = pc8.dim(
6715
+ `ready in ${pc8.reset(pc8.bold(Math.ceil(performance.now() - startTime)))} ms`
5699
6716
  );
5700
6717
  server.config.logger.info(
5701
6718
  `
5702
- ${pc5.green(`${pc5.bold("VITE")} v${version}`)} ${startupDurationString}
6719
+ ${pc8.green(`${pc8.bold("VITE")} v${version}`)} ${startupDurationString}
5703
6720
  `,
5704
6721
  { clear: !hasExistingLogs }
5705
6722
  );
@@ -5708,18 +6725,18 @@ var dev = new Command65("dev").description("start the development server").optio
5708
6725
  });
5709
6726
  async function startWatchingTypes(cwd, pb) {
5710
6727
  const { processTypes } = await import("@velastack/pocketbase-codegen");
5711
- const typesDir = path29.resolve(cwd, ".svelte-kit", "types");
5712
- const pocketbaseDir = path29.join(typesDir, "pocketbase");
5713
- const pocketbaseTypes = path29.join(pocketbaseDir, "$types.d.ts");
6728
+ const typesDir = path33.resolve(cwd, ".svelte-kit", "types");
6729
+ const pocketbaseDir = path33.join(typesDir, "pocketbase");
6730
+ const pocketbaseTypes = path33.join(pocketbaseDir, "$types.d.ts");
5714
6731
  const regenerate = () => processTypes(pb, typesDir).catch(() => {
5715
6732
  });
5716
6733
  await regenerate();
5717
6734
  void (async () => {
5718
6735
  for (; ; ) {
5719
6736
  try {
5720
- await fs27.promises.mkdir(pocketbaseDir, { recursive: true });
5721
- for await (const event of fs27.promises.watch(pocketbaseDir)) {
5722
- if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs27.existsSync(pocketbaseTypes)) {
6737
+ await fs30.promises.mkdir(pocketbaseDir, { recursive: true });
6738
+ for await (const event of fs30.promises.watch(pocketbaseDir)) {
6739
+ if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs30.existsSync(pocketbaseTypes)) {
5723
6740
  setTimeout(regenerate, 100);
5724
6741
  }
5725
6742
  }
@@ -5731,34 +6748,35 @@ async function startWatchingTypes(cwd, pb) {
5731
6748
  }
5732
6749
 
5733
6750
  // src/commands/build.ts
5734
- import path30 from "node:path";
5735
- import process18 from "node:process";
5736
- import { Command as Command66 } from "commander";
6751
+ import path34 from "node:path";
6752
+ import process22 from "node:process";
6753
+ import { Command as Command67 } from "commander";
5737
6754
  import { x as x3 } from "tinyexec";
5738
6755
  import { detect as detect5 } from "package-manager-detector";
5739
6756
  import { resolveCommand as resolveCommand5 } from "package-manager-detector/commands";
5740
- var build = new Command66("build").description("build the app").configureHelp(helpConfig).action(async () => {
5741
- process18.env.VITE_BUILD = "true";
5742
- const cwd = process18.cwd();
6757
+ var build = new Command67("build").description("build the app").configureHelp(helpConfig).action(async () => {
6758
+ process22.env.VITE_BUILD = "true";
6759
+ const cwd = process22.cwd();
5743
6760
  let pbProc;
5744
- const needsStart = hasBackend(cwd) && !process18.env.POCKETBASE_URL;
6761
+ const needsStart = hasBackend(cwd) && !process22.env.POCKETBASE_URL;
5745
6762
  const cleanup = () => {
5746
6763
  if (pbProc?.pid) pbProc.kill();
5747
6764
  };
5748
6765
  if (needsStart) {
5749
- const dataDir = path30.join(cwd, DATA_DIR);
6766
+ await ensureSuperuser(cwd);
6767
+ const dataDir = path34.join(cwd, DATA_DIR);
5750
6768
  const started = await startPocketbaseServe({
5751
6769
  dataDir,
5752
6770
  migrationsDir: MIGRATIONS_DIR,
5753
- hooksDir: path30.join(dataDir, "hooks"),
6771
+ hooksDir: path34.join(dataDir, "hooks"),
5754
6772
  dev: true
5755
6773
  });
5756
6774
  pbProc = started.proc;
5757
- process18.env.POCKETBASE_URL = started.url;
5758
- process18.on("exit", cleanup);
5759
- process18.on("SIGINT", () => {
6775
+ process22.env.POCKETBASE_URL = started.url;
6776
+ process22.on("exit", cleanup);
6777
+ process22.on("SIGINT", () => {
5760
6778
  cleanup();
5761
- process18.exit(0);
6779
+ process22.exit(0);
5762
6780
  });
5763
6781
  }
5764
6782
  try {
@@ -5775,73 +6793,495 @@ var build = new Command66("build").description("build the app").configureHelp(he
5775
6793
  }
5776
6794
  });
5777
6795
 
5778
- // src/commands/preview.ts
5779
- import path31 from "node:path";
5780
- import process19 from "node:process";
5781
- import { Command as Command67 } from "commander";
5782
- import { x as x4 } from "tinyexec";
5783
- import { detect as detect6 } from "package-manager-detector";
5784
- import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
5785
- var preview = new Command67("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
5786
- const cwd = process19.cwd();
5787
- let pbProc;
5788
- const needsStart = hasBackend(cwd) && !process19.env.POCKETBASE_URL;
5789
- const cleanup = () => {
5790
- if (pbProc?.pid) pbProc.kill();
6796
+ // src/commands/preview.ts
6797
+ import path35 from "node:path";
6798
+ import process23 from "node:process";
6799
+ import { Command as Command68 } from "commander";
6800
+ import { x as x4 } from "tinyexec";
6801
+ import { detect as detect6 } from "package-manager-detector";
6802
+ import { resolveCommand as resolveCommand6 } from "package-manager-detector/commands";
6803
+ var preview = new Command68("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
6804
+ const cwd = process23.cwd();
6805
+ let pbProc;
6806
+ const needsStart = hasBackend(cwd) && !process23.env.POCKETBASE_URL;
6807
+ const cleanup = () => {
6808
+ if (pbProc?.pid) pbProc.kill();
6809
+ };
6810
+ if (needsStart) {
6811
+ const dataDir = path35.join(cwd, DATA_DIR);
6812
+ const started = await startPocketbaseServe({
6813
+ dataDir,
6814
+ migrationsDir: MIGRATIONS_DIR,
6815
+ hooksDir: path35.join(dataDir, "hooks"),
6816
+ dev: true
6817
+ });
6818
+ pbProc = started.proc;
6819
+ process23.env.POCKETBASE_URL = started.url;
6820
+ process23.on("exit", cleanup);
6821
+ process23.on("SIGINT", () => {
6822
+ cleanup();
6823
+ process23.exit(0);
6824
+ });
6825
+ }
6826
+ try {
6827
+ const pm = (await detect6({ cwd }))?.name ?? "npm";
6828
+ const resolved = resolveCommand6(pm, "execute", ["vite", "preview"]);
6829
+ const args = resolved.args.slice();
6830
+ if (pm === "npm") args.unshift("--yes");
6831
+ await x4(resolved.command, args, {
6832
+ nodeOptions: { cwd, stdio: "inherit" },
6833
+ throwOnError: true
6834
+ });
6835
+ } finally {
6836
+ cleanup();
6837
+ }
6838
+ });
6839
+
6840
+ // src/commands/sync.ts
6841
+ import path36 from "node:path";
6842
+ import { Command as Command69 } from "commander";
6843
+ var sync = new Command69("sync").description("sync types from the database").configureHelp(helpConfig).action(
6844
+ () => runCommand(async () => {
6845
+ const { workspaceRootDir } = await getWorkspace();
6846
+ const typesDir = path36.join(workspaceRootDir, ".svelte-kit", "types");
6847
+ const { processTypes } = await import("@velastack/pocketbase-codegen");
6848
+ await withPocketbase(workspaceRootDir, async (pb) => {
6849
+ await processTypes(pb, typesDir);
6850
+ });
6851
+ console.log("types synced");
6852
+ }, "Failed to sync types.")
6853
+ );
6854
+
6855
+ // src/commands/provision.ts
6856
+ import { Command as Command70 } from "commander";
6857
+ import * as p30 from "@clack/prompts";
6858
+ import pc9 from "picocolors";
6859
+ import * as v6 from "valibot";
6860
+ var OptionsSchema2 = v6.object({
6861
+ ...SSH_OPTION_SCHEMA,
6862
+ pbVersion: v6.optional(v6.string()),
6863
+ nodeMajor: v6.optional(v6.string())
6864
+ });
6865
+ var provision = addSshOptions(
6866
+ new Command70("provision").description("prepare a server to host vela apps").argument("<target>", "SSH target \u2014 an alias from ~/.ssh/config, or user@host").configureHelp(helpConfig)
6867
+ ).option("--pb-version <version>", "PocketBase version to install").option("--node-major <version>", "Node.js major version to install", "22").action(
6868
+ (target, raw) => runCommand(async () => {
6869
+ const options = parseOptions(OptionsSchema2, raw);
6870
+ const pbVersion = options.pbVersion ?? pocketbaseVersion();
6871
+ p30.intro(pc9.bgCyan(pc9.black(" vela provision ")));
6872
+ p30.log.info(`Target ${pc9.cyan(target)}`);
6873
+ await withSsh(target, sshOptionsFrom(options), async (session) => {
6874
+ await session.detectElevation();
6875
+ const existing = await readServerInfo(session);
6876
+ if (existing) {
6877
+ p30.log.info(
6878
+ `Already provisioned by vela ${existing.cliVersion} on ${existing.provisionedAt}. Bringing it up to date.`
6879
+ );
6880
+ }
6881
+ p30.log.step("Uploading server scripts");
6882
+ await syncServerScripts(session);
6883
+ p30.log.step("Running provision");
6884
+ const result = await runServerScript(session, "provision.sh", {
6885
+ args: [
6886
+ "--pb-version",
6887
+ pbVersion,
6888
+ "--node-major",
6889
+ options.nodeMajor ?? "22",
6890
+ "--cli-version",
6891
+ package_default.version
6892
+ ],
6893
+ stream: true
6894
+ });
6895
+ p30.log.success(
6896
+ `${target} is ready.
6897
+
6898
+ Node ${result?.node ?? "installed"}
6899
+ Caddy ${result?.caddy ?? "installed"}
6900
+ PocketBase ${result?.pocketbase ?? pbVersion}`
6901
+ );
6902
+ });
6903
+ p30.outro(`Deploy with ${pc9.cyan(`vela deploy --server ${target}`)}`);
6904
+ }, "Failed to provision.")
6905
+ );
6906
+
6907
+ // src/commands/deploy.ts
6908
+ import path39 from "node:path";
6909
+ import fs33 from "node:fs";
6910
+ import { Command as Command71 } from "commander";
6911
+ import * as p31 from "@clack/prompts";
6912
+ import pc10 from "picocolors";
6913
+ import * as v7 from "valibot";
6914
+
6915
+ // src/lib/pocketbase-settings.ts
6916
+ import fs31 from "node:fs";
6917
+ import path37 from "node:path";
6918
+ import process24 from "node:process";
6919
+ import PocketBase4 from "pocketbase";
6920
+
6921
+ // src/lib/remote-pocketbase.ts
6922
+ import PocketBase3 from "pocketbase";
6923
+ async function openRemoteDatabase(session, instance) {
6924
+ const [state] = await readInstanceStates(session, instance);
6925
+ const pbPort = state?.pbPort;
6926
+ if (!pbPort) {
6927
+ throw new Error(
6928
+ `${instance} has no deployed database yet.
6929
+
6930
+ Run \`vela deploy\` first \u2014 the database is created by the first deploy.`
6931
+ );
6932
+ }
6933
+ const env2 = await readRemoteEnv(session, instance);
6934
+ const email3 = env2.POCKETBASE_SUPERUSER_EMAIL;
6935
+ const password10 = env2.POCKETBASE_SUPERUSER_PASSWORD;
6936
+ if (!email3 || !password10) {
6937
+ throw new Error(
6938
+ `${instance} has no PocketBase superuser credentials in its environment.
6939
+
6940
+ A deploy creates them. Deploy again to repair this instance.`
6941
+ );
6942
+ }
6943
+ const localPort = await findFreePort("127.0.0.1");
6944
+ await session.forwardLocalPort(localPort, "127.0.0.1", pbPort);
6945
+ return {
6946
+ url: `http://127.0.0.1:${localPort}`,
6947
+ email: email3,
6948
+ password: password10,
6949
+ close: () => session.cancelForward(localPort, "127.0.0.1", pbPort)
6950
+ };
6951
+ }
6952
+ async function withRemotePocketbase(session, instance, fn) {
6953
+ const db = await openRemoteDatabase(session, instance);
6954
+ try {
6955
+ const pb = new PocketBase3(db.url);
6956
+ await authWithRetries(pb, db.email, db.password);
6957
+ return await fn(pb);
6958
+ } finally {
6959
+ await db.close();
6960
+ }
6961
+ }
6962
+
6963
+ // src/lib/pocketbase-settings.ts
6964
+ var COPIED_KEYS = ["appName", "senderName", "senderAddress"];
6965
+ async function readLocalMeta(cwd) {
6966
+ const dataDir = path37.join(cwd, DATA_DIR);
6967
+ if (!fs31.existsSync(dataDir)) return null;
6968
+ const email3 = process24.env.POCKETBASE_SUPERUSER_EMAIL;
6969
+ const password10 = process24.env.POCKETBASE_SUPERUSER_PASSWORD;
6970
+ if (!email3 || !password10) return null;
6971
+ let proc;
6972
+ try {
6973
+ const started = await startPocketbaseServe({
6974
+ dataDir,
6975
+ migrationsDir: MIGRATIONS_DIR,
6976
+ hooksDir: path37.join(dataDir, "hooks")
6977
+ });
6978
+ proc = started.proc;
6979
+ const pb = new PocketBase4(started.url);
6980
+ await authWithRetries(pb, email3, password10);
6981
+ const settings = await pb.settings.getAll();
6982
+ return settings.meta ?? null;
6983
+ } catch {
6984
+ return null;
6985
+ } finally {
6986
+ proc?.kill();
6987
+ }
6988
+ }
6989
+ async function seedRemoteMeta(session, instance, local, appURL) {
6990
+ const patch = {};
6991
+ for (const key of COPIED_KEYS) {
6992
+ const value = local[key];
6993
+ if (typeof value === "string" && value.trim()) patch[key] = value;
6994
+ }
6995
+ if (appURL) patch.appURL = appURL;
6996
+ if (Object.keys(patch).length === 0) return [];
6997
+ await withRemotePocketbase(session, instance, async (pb) => {
6998
+ const settings = await pb.settings.getAll();
6999
+ await pb.settings.update({ meta: { ...settings.meta ?? {}, ...patch } });
7000
+ });
7001
+ return Object.keys(patch);
7002
+ }
7003
+
7004
+ // src/lib/artifact.ts
7005
+ import fs32 from "node:fs";
7006
+ import path38 from "node:path";
7007
+ import { detect as detect7 } from "package-manager-detector";
7008
+ import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
7009
+ var DEFAULT_OUTPUT_DIR = "build";
7010
+ var BuildError = class extends Error {
7011
+ };
7012
+ async function runBuild(cwd, buildCommand, env2) {
7013
+ if (buildCommand) {
7014
+ const result2 = await spawnCapture("bash", ["-lc", buildCommand], {
7015
+ cwd,
7016
+ stream: true,
7017
+ streamStdout: true,
7018
+ env: env2
7019
+ });
7020
+ if (result2.exitCode !== 0)
7021
+ throw new BuildError(`\`${buildCommand}\` exited ${result2.exitCode}.`);
7022
+ return;
7023
+ }
7024
+ const pm = (await detect7({ cwd }))?.name ?? "npm";
7025
+ const resolved = resolveCommand7(pm, "run", ["build"]);
7026
+ if (!resolved) throw new BuildError(`Could not work out how to run a build with ${pm}.`);
7027
+ const result = await spawnCapture(resolved.command, resolved.args, {
7028
+ cwd,
7029
+ stream: true,
7030
+ streamStdout: true,
7031
+ env: env2
7032
+ });
7033
+ if (result.exitCode !== 0) {
7034
+ throw new BuildError(
7035
+ `\`${resolved.command} ${resolved.args.join(" ")}\` exited ${result.exitCode}.`
7036
+ );
7037
+ }
7038
+ }
7039
+ function collectArtifact(cwd, config = {}) {
7040
+ const outputDir = config.outputDir ?? DEFAULT_OUTPUT_DIR;
7041
+ const entries = [];
7042
+ const add2 = (rel, remoteDir = "") => {
7043
+ const localPath = path38.join(cwd, rel);
7044
+ if (fs32.existsSync(localPath)) entries.push({ localPath, remoteDir });
7045
+ };
7046
+ const buildPath = path38.join(cwd, outputDir);
7047
+ if (!fs32.existsSync(path38.join(buildPath, "index.js"))) {
7048
+ throw new BuildError(
7049
+ `No ${outputDir}/index.js after the build.
7050
+
7051
+ Deploying to a server needs @sveltejs/adapter-node. Install it and set it as
7052
+ the adapter in your Vite or Svelte config, then build again.`
7053
+ );
7054
+ }
7055
+ entries.push({ localPath: buildPath, remoteDir: "" });
7056
+ add2("package.json");
7057
+ add2("package-lock.json");
7058
+ add2(".npmrc");
7059
+ add2(MIGRATIONS_DIR);
7060
+ const hooks = path38.join(cwd, DATA_DIR, "hooks");
7061
+ if (fs32.existsSync(hooks)) entries.push({ localPath: hooks, remoteDir: "hooks" });
7062
+ for (const extra of config.include ?? []) add2(extra);
7063
+ return entries;
7064
+ }
7065
+ async function gitSha(cwd) {
7066
+ const result = await spawnCapture("git", ["rev-parse", "HEAD"], { cwd });
7067
+ return result.exitCode === 0 ? result.stdout.trim() : "";
7068
+ }
7069
+
7070
+ // src/commands/deploy.ts
7071
+ var OptionsSchema3 = v7.object({
7072
+ ...SSH_OPTION_SCHEMA,
7073
+ env: v7.optional(v7.string()),
7074
+ project: v7.optional(v7.string()),
7075
+ remoteDb: v7.optional(v7.boolean()),
7076
+ domain: v7.optional(v7.string()),
7077
+ healthPath: v7.optional(v7.string()),
7078
+ keep: v7.optional(v7.string()),
7079
+ pbVersion: v7.optional(v7.string()),
7080
+ build: v7.optional(v7.boolean())
7081
+ });
7082
+ var deploy = addTargetOptions(
7083
+ new Command71("deploy").description("deploy the app").configureHelp(helpConfig),
7084
+ "production"
7085
+ ).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").option(
7086
+ "--remote-db",
7087
+ "render the build against the database on the server, over an SSH tunnel \u2014 needed when pages are prerendered from data"
7088
+ ).action(
7089
+ (raw) => runCommand(async () => {
7090
+ const options = parseOptions(OptionsSchema3, raw);
7091
+ const backend3 = hasBackend();
7092
+ const release = releaseId();
7093
+ p31.intro(pc10.bgCyan(pc10.black(" vela deploy ")));
7094
+ await withTarget(
7095
+ raw,
7096
+ {
7097
+ remote: async (ctx) => {
7098
+ const { session, instance, workspaceRootDir, config } = ctx;
7099
+ p31.log.info(
7100
+ `${pc10.cyan(ctx.appName)} ${pc10.dim("\u2192")} ${pc10.cyan(ctx.targetName)} ${pc10.dim(`(${ctx.server})`)}`
7101
+ );
7102
+ const [existing] = await readInstanceStates(session, instance);
7103
+ const domain = options.domain ?? ctx.binding.domain ?? config.deploy?.domain ?? existing?.domain ?? "";
7104
+ const remoteDb = options.remoteDb ?? config.deploy?.buildAgainstRemote ?? false;
7105
+ if (options.build !== false) {
7106
+ let buildEnv = {};
7107
+ let tunnel = null;
7108
+ if (backend3 && remoteDb) {
7109
+ tunnel = await openDatabaseTunnel(session, instance, existing);
7110
+ buildEnv = tunnel.env;
7111
+ p31.log.info(
7112
+ `Building against the ${pc10.cyan(ctx.targetName)} database on ${ctx.server} ${pc10.dim(`(port ${tunnel.pbPort})`)}`
7113
+ );
7114
+ } else if (backend3) {
7115
+ await ensureSuperuser(workspaceRootDir);
7116
+ }
7117
+ p31.log.step("Building");
7118
+ try {
7119
+ await runBuild(workspaceRootDir, config.deploy?.buildCommand, buildEnv);
7120
+ } finally {
7121
+ if (tunnel) await tunnel.close();
7122
+ }
7123
+ }
7124
+ const entries = collectArtifact(workspaceRootDir, config.deploy ?? {});
7125
+ const sha = await gitSha(workspaceRootDir);
7126
+ p31.log.step(`Uploading release ${pc10.dim(release)}`);
7127
+ await uploadRelease(session, instance, release, entries);
7128
+ p31.log.step("Activating");
7129
+ const result = await runServerScript(session, "apply.sh", {
7130
+ args: [
7131
+ instance,
7132
+ release,
7133
+ "--name",
7134
+ ctx.appName,
7135
+ "--app-id",
7136
+ ctx.appId,
7137
+ "--env",
7138
+ ctx.envTag,
7139
+ "--domain",
7140
+ domain,
7141
+ "--health-path",
7142
+ options.healthPath ?? config.deploy?.healthCheckPath ?? "/",
7143
+ "--keep",
7144
+ options.keep ?? String(config.deploy?.keepReleases ?? 5),
7145
+ "--backend",
7146
+ backend3 ? "1" : "0",
7147
+ "--pb-version",
7148
+ options.pbVersion ?? config.deploy?.pocketbaseVersion ?? pocketbaseVersion(),
7149
+ "--git-sha",
7150
+ sha
7151
+ ],
7152
+ stream: true
7153
+ });
7154
+ writeBinding(workspaceRootDir, ctx.envTag, {
7155
+ server: ctx.server,
7156
+ domain: domain || void 0
7157
+ });
7158
+ if (!existing) await reportEmptyEnvironment(session, instance, workspaceRootDir);
7159
+ const url = result?.url ?? "";
7160
+ p31.log.success(
7161
+ `Deployed ${pc10.cyan(ctx.appName)} ${pc10.dim(release)}
7162
+
7163
+ URL ${url}
7164
+ Port ${result?.webPort ?? "?"}${backend3 ? ` (PocketBase ${result?.pbPort ?? "?"})` : ""}`
7165
+ );
7166
+ if (result?.superuserCreated) {
7167
+ await copyLocalBranding(
7168
+ session,
7169
+ instance,
7170
+ workspaceRootDir,
7171
+ domain ? result?.url ?? "" : ""
7172
+ );
7173
+ p31.log.info(
7174
+ `Created the PocketBase superuser this app authenticates as.
7175
+
7176
+ Its credentials are stored in the environment on the server. To use
7177
+ your own instead, ${pc10.cyan("vela env set POCKETBASE_SUPERUSER_PASSWORD")}
7178
+ and deploy again.`
7179
+ );
7180
+ }
7181
+ if (!domain) {
7182
+ p31.log.warn(
7183
+ `No domain configured, so nothing is proxied to this app yet.
7184
+ Redeploy with ${pc10.cyan("--domain example.com")} once DNS points at ${ctx.server}.`
7185
+ );
7186
+ }
7187
+ }
7188
+ },
7189
+ { project: options.project, askDomain: true, label: "deploy" }
7190
+ );
7191
+ p31.outro(`${pc10.cyan("vela status")} to see what is running`);
7192
+ }, "Failed to deploy.")
7193
+ );
7194
+ async function copyLocalBranding(session, instance, workspaceRootDir, appURL) {
7195
+ const local = await readLocalMeta(workspaceRootDir);
7196
+ if (!local) return;
7197
+ try {
7198
+ const copied = await seedRemoteMeta(session, instance, local, appURL);
7199
+ if (copied.length === 0) return;
7200
+ const outcome = await restartInstance(session, instance);
7201
+ if (outcome.deployed && !outcome.restarted) {
7202
+ p31.log.warn(
7203
+ `Copied ${copied.join(", ")}, but the app did not restart to pick them up.
7204
+ ${pc10.dim(outcome.error ?? "")}`
7205
+ );
7206
+ return;
7207
+ }
7208
+ p31.log.success(`Copied ${copied.join(", ")} from this project's database`);
7209
+ } catch (err) {
7210
+ p31.log.warn(
7211
+ `Could not copy this project's PocketBase settings across.
7212
+ Set them in the admin panel instead. ${pc10.dim(String(err))}`
7213
+ );
7214
+ }
7215
+ }
7216
+ async function openDatabaseTunnel(session, instance, state) {
7217
+ const pbPort = state?.pbPort;
7218
+ if (!pbPort) {
7219
+ throw new Error(
7220
+ `--remote-db needs an existing deployment to build against, and ${instance} has not been deployed yet.
7221
+
7222
+ Deploy once without it, then turn it on.`
7223
+ );
7224
+ }
7225
+ const remoteEnv = await readRemoteEnv(session, instance);
7226
+ const email3 = remoteEnv.POCKETBASE_SUPERUSER_EMAIL;
7227
+ const password10 = remoteEnv.POCKETBASE_SUPERUSER_PASSWORD;
7228
+ if (!email3 || !password10) {
7229
+ throw new Error(
7230
+ `--remote-db renders the build as the superuser of ${instance}, and that environment has no credentials for one.
7231
+
7232
+ Set them with \`vela env set POCKETBASE_SUPERUSER_EMAIL\` and \`vela env set POCKETBASE_SUPERUSER_PASSWORD\`.`
7233
+ );
7234
+ }
7235
+ const localPort = await findFreePort("127.0.0.1");
7236
+ await session.forwardLocalPort(localPort, "127.0.0.1", pbPort);
7237
+ return {
7238
+ pbPort,
7239
+ env: {
7240
+ POCKETBASE_URL: `http://127.0.0.1:${localPort}`,
7241
+ POCKETBASE_SUPERUSER_EMAIL: email3,
7242
+ POCKETBASE_SUPERUSER_PASSWORD: password10
7243
+ },
7244
+ close: () => session.cancelForward(localPort, "127.0.0.1", pbPort)
5791
7245
  };
5792
- if (needsStart) {
5793
- const dataDir = path31.join(cwd, DATA_DIR);
5794
- const started = await startPocketbaseServe({
5795
- dataDir,
5796
- migrationsDir: MIGRATIONS_DIR,
5797
- hooksDir: path31.join(dataDir, "hooks"),
5798
- dev: true
5799
- });
5800
- pbProc = started.proc;
5801
- process19.env.POCKETBASE_URL = started.url;
5802
- process19.on("exit", cleanup);
5803
- process19.on("SIGINT", () => {
5804
- cleanup();
5805
- process19.exit(0);
5806
- });
7246
+ }
7247
+ async function uploadRelease(session, instance, release, entries) {
7248
+ const dir = remotePaths.release(instance, release);
7249
+ await session.script(`mkdir -p "$1"`, { args: [dir] });
7250
+ const byTarget = /* @__PURE__ */ new Map();
7251
+ for (const entry of entries) {
7252
+ const target = entry.remoteDir ? `${dir}/${entry.remoteDir}` : dir;
7253
+ const source = entry.remoteDir && isDirectory(entry.localPath) ? `${entry.localPath}/` : entry.localPath;
7254
+ byTarget.set(target, [...byTarget.get(target) ?? [], source]);
5807
7255
  }
7256
+ for (const [target, sources] of byTarget) {
7257
+ if (target !== dir) await session.script(`mkdir -p "$1"`, { args: [target] });
7258
+ await session.upload(sources, target);
7259
+ }
7260
+ }
7261
+ function isDirectory(target) {
5808
7262
  try {
5809
- const pm = (await detect6({ cwd }))?.name ?? "npm";
5810
- const resolved = resolveCommand6(pm, "execute", ["vite", "preview"]);
5811
- const args = resolved.args.slice();
5812
- if (pm === "npm") args.unshift("--yes");
5813
- await x4(resolved.command, args, {
5814
- nodeOptions: { cwd, stdio: "inherit" },
5815
- throwOnError: true
5816
- });
5817
- } finally {
5818
- cleanup();
7263
+ return fs33.statSync(target).isDirectory();
7264
+ } catch {
7265
+ return false;
5819
7266
  }
5820
- });
5821
-
5822
- // src/commands/sync.ts
5823
- import path32 from "node:path";
5824
- import { Command as Command68 } from "commander";
5825
- var sync = new Command68("sync").description("sync types from the database").configureHelp(helpConfig).action(
5826
- () => runCommand(async () => {
5827
- const { workspaceRootDir } = await getWorkspace();
5828
- const typesDir = path32.join(workspaceRootDir, ".svelte-kit", "types");
5829
- const { processTypes } = await import("@velastack/pocketbase-codegen");
5830
- await withPocketbase(workspaceRootDir, async (pb) => {
5831
- await processTypes(pb, typesDir);
5832
- });
5833
- console.log("types synced");
5834
- }, "Failed to sync types.")
5835
- );
7267
+ }
7268
+ async function reportEmptyEnvironment(session, instance, workspaceRootDir) {
7269
+ const remote = await readRemoteEnv(session, instance);
7270
+ if (Object.keys(remote).length > 0) return;
7271
+ if (!fs33.existsSync(path39.join(workspaceRootDir, ".env"))) return;
7272
+ p31.log.warn(
7273
+ `This app has no production environment variables yet.
5836
7274
 
5837
- // src/commands/provision.ts
5838
- var provision = stubCommand("provision", "provision cloud resources");
7275
+ Local ${pc10.cyan(".env")} values are not uploaded by a deploy. Set them with
7276
+ ${pc10.cyan("vela env set KEY")}, or copy a file across with ${pc10.cyan("vela env import .env.production")}.`
7277
+ );
7278
+ }
5839
7279
 
5840
- // src/commands/deploy.ts
5841
- import path33 from "node:path";
5842
- import process20 from "node:process";
5843
- import { Command as Command69 } from "commander";
5844
- import * as p28 from "@clack/prompts";
7280
+ // src/commands/link.ts
7281
+ import path40 from "node:path";
7282
+ import process25 from "node:process";
7283
+ import { Command as Command72 } from "commander";
7284
+ import * as p32 from "@clack/prompts";
5845
7285
 
5846
7286
  // src/lib/velastack-api.ts
5847
7287
  async function apiFetch(apiKey, pathAndQuery, init) {
@@ -5890,14 +7330,14 @@ async function createProject2(apiKey, args) {
5890
7330
  });
5891
7331
  }
5892
7332
 
5893
- // src/commands/deploy.ts
7333
+ // src/commands/link.ts
5894
7334
  var CREATE_NEW = "__new__";
5895
- var deploy = new Command69("deploy").description("deploy the app").configureHelp(helpConfig).action(() => runCommand(linkProject, "Failed to deploy."));
7335
+ var link = new Command72("link").description("link this project to a velastack.dev project").configureHelp(helpConfig).action(() => runCommand(linkProject, "Failed to link the project."));
5896
7336
  async function linkProject() {
5897
7337
  const { workspaceRootDir } = await getWorkspace();
5898
7338
  const existing = readProjectConfig(workspaceRootDir);
5899
7339
  if (existing) {
5900
- p28.log.success(`Linked to ${existing.projectName}.`);
7340
+ p32.log.success(`Linked to ${existing.projectName}.`);
5901
7341
  return;
5902
7342
  }
5903
7343
  const apiKey = requireApiKey();
@@ -5924,10 +7364,10 @@ async function linkProject() {
5924
7364
  projectName = created.name;
5925
7365
  }
5926
7366
  writeProjectConfig(workspaceRootDir, { projectId, teamId, projectName });
5927
- p28.log.success(`Linked to ${projectName}.`);
7367
+ p32.log.success(`Linked to ${projectName}.`);
5928
7368
  }
5929
7369
  async function pickExistingProject(projects) {
5930
- const choice = await p28.select({
7370
+ const choice = await p32.select({
5931
7371
  message: "Select a project",
5932
7372
  options: [
5933
7373
  ...projects.map((pr) => ({
@@ -5937,80 +7377,656 @@ async function pickExistingProject(projects) {
5937
7377
  { value: CREATE_NEW, label: "Create a new project" }
5938
7378
  ]
5939
7379
  });
5940
- if (p28.isCancel(choice)) {
5941
- p28.cancel("Operation cancelled.");
5942
- process20.exit(0);
7380
+ if (p32.isCancel(choice)) {
7381
+ p32.cancel("Operation cancelled.");
7382
+ process25.exit(0);
5943
7383
  }
5944
7384
  return choice;
5945
7385
  }
5946
7386
  async function pickTeam(teams3) {
5947
7387
  if (teams3.length === 1) return teams3[0];
5948
- const choice = await p28.select({
7388
+ const choice = await p32.select({
5949
7389
  message: "Select a team",
5950
7390
  options: teams3.map((team) => ({
5951
7391
  value: team.id,
5952
7392
  label: team.is_personal ? `${team.name} (personal)` : team.name
5953
7393
  }))
5954
7394
  });
5955
- if (p28.isCancel(choice)) {
5956
- p28.cancel("Operation cancelled.");
5957
- process20.exit(0);
7395
+ if (p32.isCancel(choice)) {
7396
+ p32.cancel("Operation cancelled.");
7397
+ process25.exit(0);
5958
7398
  }
5959
7399
  return teams3.find((team) => team.id === choice);
5960
7400
  }
5961
7401
  async function promptProjectName(workspaceRootDir) {
5962
- const defaultValue = defaultProjectName(workspaceRootDir);
5963
- const value = await p28.text({
7402
+ const defaultValue = defaultProjectName2(workspaceRootDir);
7403
+ const value = await p32.text({
5964
7404
  message: "Project name",
5965
7405
  defaultValue,
5966
7406
  initialValue: defaultValue,
5967
7407
  placeholder: defaultValue,
5968
- validate: (v4) => !v4?.trim() ? "Required" : void 0
7408
+ validate: (v9) => !v9?.trim() ? "Required" : void 0
5969
7409
  });
5970
- if (p28.isCancel(value)) {
5971
- p28.cancel("Operation cancelled.");
5972
- process20.exit(0);
7410
+ if (p32.isCancel(value)) {
7411
+ p32.cancel("Operation cancelled.");
7412
+ process25.exit(0);
5973
7413
  }
5974
7414
  return value.trim();
5975
7415
  }
5976
- function defaultProjectName(workspaceRootDir) {
7416
+ function defaultProjectName2(workspaceRootDir) {
5977
7417
  try {
5978
- const pkg = readPackageJson(path33.join(workspaceRootDir, "package.json"));
7418
+ const pkg = readPackageJson(path40.join(workspaceRootDir, "package.json"));
5979
7419
  const name = pkg.name;
5980
7420
  if (typeof name === "string" && name.trim()) return name.trim();
5981
7421
  } catch {
5982
7422
  }
5983
- return path33.basename(workspaceRootDir);
7423
+ return path40.basename(workspaceRootDir);
7424
+ }
7425
+
7426
+ // src/commands/env.ts
7427
+ import { Command as Command77 } from "commander";
7428
+
7429
+ // src/commands/env/list.ts
7430
+ import { Command as Command73 } from "commander";
7431
+ import * as p34 from "@clack/prompts";
7432
+ import pc12 from "picocolors";
7433
+
7434
+ // src/lib/local-env.ts
7435
+ import fs34 from "node:fs";
7436
+ import * as p33 from "@clack/prompts";
7437
+ import pc11 from "picocolors";
7438
+ function readLocalEnv(envFile) {
7439
+ if (!fs34.existsSync(envFile)) return {};
7440
+ return readLocalEnvFile(envFile);
7441
+ }
7442
+ function editLocalEnv(envFile, edit) {
7443
+ const before = fs34.existsSync(envFile) ? fs34.readFileSync(envFile, "utf8") : "";
7444
+ const after = edit(before);
7445
+ if (after !== before) fs34.writeFileSync(envFile, after);
7446
+ }
7447
+ function setLocalEnv(envFile, key, value) {
7448
+ editLocalEnv(envFile, (content) => upsertEnvVar(content, key, value));
7449
+ }
7450
+ function unsetLocalEnv(envFile, key) {
7451
+ editLocalEnv(envFile, (content) => removeEnvVar(content, key));
7452
+ }
7453
+ async function applyLocalEnvChange(ctx, changed) {
7454
+ if (touchesSuperuser(changed)) {
7455
+ const env2 = readLocalEnv(ctx.envFile);
7456
+ const email3 = env2.POCKETBASE_SUPERUSER_EMAIL;
7457
+ const password10 = env2.POCKETBASE_SUPERUSER_PASSWORD;
7458
+ if (email3 && password10) {
7459
+ process.env.POCKETBASE_SUPERUSER_EMAIL = email3;
7460
+ process.env.POCKETBASE_SUPERUSER_PASSWORD = password10;
7461
+ await ensureSuperuser(ctx.workspaceRootDir);
7462
+ p33.log.success("Local superuser updated to match");
7463
+ } else {
7464
+ p33.log.warn(
7465
+ `The local database still has the old superuser.
7466
+ Set both ${pc11.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc11.cyan("POCKETBASE_SUPERUSER_PASSWORD")} to reconcile it.`
7467
+ );
7468
+ }
7469
+ }
7470
+ if (getPocketbaseMetadata(ctx.workspaceRootDir)) {
7471
+ p33.log.info(`Restart ${pc11.cyan("vela dev")} to pick this up.`);
7472
+ }
7473
+ }
7474
+
7475
+ // src/commands/env/list.ts
7476
+ var envList = addTargetOptions(
7477
+ new Command73("list").description("list environment variable names").configureHelp(helpConfig),
7478
+ "local"
7479
+ ).action(
7480
+ (raw) => runCommand(
7481
+ () => withTarget(
7482
+ raw,
7483
+ {
7484
+ local: async (ctx) => {
7485
+ report(Object.keys(readLocalEnv(ctx.envFile)), `${ctx.appName}, local (.env)`);
7486
+ },
7487
+ remote: async (ctx) => {
7488
+ const env2 = await readRemoteEnv(ctx.session, ctx.instance);
7489
+ report(Object.keys(env2), `${ctx.appName}, ${ctx.targetName}`);
7490
+ }
7491
+ },
7492
+ { label: "env list" }
7493
+ ),
7494
+ "Failed to read the environment."
7495
+ )
7496
+ );
7497
+ function report(keys, where) {
7498
+ if (keys.length === 0) {
7499
+ p34.log.info(`No environment variables configured ${pc12.dim(`(${where})`)}.`);
7500
+ return;
7501
+ }
7502
+ p34.log.info(
7503
+ `Environment ${pc12.dim(`(${where})`)}
7504
+
7505
+ ` + keys.sort().map((key) => ` ${key}`).join("\n")
7506
+ );
7507
+ }
7508
+
7509
+ // src/commands/env/set.ts
7510
+ import process26 from "node:process";
7511
+ import { Command as Command74 } from "commander";
7512
+ import * as p35 from "@clack/prompts";
7513
+ import pc13 from "picocolors";
7514
+ var envSet = addTargetOptions(
7515
+ new Command74("set").description("set an environment variable").argument("<key>", "variable name").argument("[value]", "value \u2014 prompted for, without echo, when omitted").configureHelp(helpConfig),
7516
+ "local"
7517
+ ).action(
7518
+ (key, value, raw) => runCommand(
7519
+ () => withTarget(
7520
+ raw,
7521
+ {
7522
+ local: async (ctx) => {
7523
+ const resolved = await resolveValue(key, value);
7524
+ setLocalEnv(ctx.envFile, key, resolved);
7525
+ p35.log.success(`${key} updated ${pc13.dim("(local)")}`);
7526
+ await applyLocalEnvChange(ctx, [key]);
7527
+ },
7528
+ remote: async (ctx) => {
7529
+ const resolved = await resolveValue(key, value);
7530
+ const env2 = await readRemoteEnv(ctx.session, ctx.instance);
7531
+ await writeRemoteEnv(ctx.session, ctx.instance, { ...env2, [key]: resolved });
7532
+ p35.log.success(`${key} updated ${pc13.dim(`(${ctx.targetName})`)}`);
7533
+ await applyEnvRestart(ctx, [key]);
7534
+ }
7535
+ },
7536
+ { label: "env set" }
7537
+ ),
7538
+ "Failed to set the variable."
7539
+ )
7540
+ );
7541
+ async function resolveValue(key, value) {
7542
+ if (!isValidKey(key)) throw new Error(`${key} is not a valid environment variable name.`);
7543
+ return value ?? await promptValue(key);
7544
+ }
7545
+ async function promptValue(key) {
7546
+ const value = await p35.password({
7547
+ message: `Value for ${pc13.cyan(key)}`,
7548
+ validate: (input) => !input?.length ? "Required" : void 0
7549
+ });
7550
+ if (p35.isCancel(value)) {
7551
+ p35.cancel("Operation cancelled.");
7552
+ process26.exit(0);
7553
+ }
7554
+ return value;
7555
+ }
7556
+
7557
+ // src/commands/env/unset.ts
7558
+ import { Command as Command75 } from "commander";
7559
+ import * as p36 from "@clack/prompts";
7560
+ import pc14 from "picocolors";
7561
+ var envUnset = addTargetOptions(
7562
+ new Command75("unset").description("remove an environment variable").argument("<key>", "variable name").configureHelp(helpConfig),
7563
+ "local"
7564
+ ).action(
7565
+ (key, raw) => runCommand(
7566
+ () => withTarget(
7567
+ raw,
7568
+ {
7569
+ local: async (ctx) => {
7570
+ if (!(key in readLocalEnv(ctx.envFile))) {
7571
+ p36.log.info(`${key} is not set \u2014 nothing to remove.`);
7572
+ return;
7573
+ }
7574
+ unsetLocalEnv(ctx.envFile, key);
7575
+ p36.log.success(`${key} removed ${pc14.dim("(local)")}`);
7576
+ await applyLocalEnvChange(ctx, [key]);
7577
+ },
7578
+ remote: async (ctx) => {
7579
+ const env2 = await readRemoteEnv(ctx.session, ctx.instance);
7580
+ if (!(key in env2)) {
7581
+ p36.log.info(`${key} is not set \u2014 nothing to remove.`);
7582
+ return;
7583
+ }
7584
+ delete env2[key];
7585
+ await writeRemoteEnv(ctx.session, ctx.instance, env2);
7586
+ p36.log.success(`${key} removed ${pc14.dim(`(${ctx.targetName})`)}`);
7587
+ await applyEnvRestart(ctx, [key]);
7588
+ }
7589
+ },
7590
+ { label: "env unset" }
7591
+ ),
7592
+ "Failed to remove the variable."
7593
+ )
7594
+ );
7595
+
7596
+ // src/commands/env/import.ts
7597
+ import fs35 from "node:fs";
7598
+ import path41 from "node:path";
7599
+ import process27 from "node:process";
7600
+ import { Command as Command76 } from "commander";
7601
+ import * as p37 from "@clack/prompts";
7602
+ import pc15 from "picocolors";
7603
+ var envImport = addTargetOptions(
7604
+ new Command76("import").description("merge a dotenv file into the environment").argument("<file>", "dotenv file to read").configureHelp(helpConfig),
7605
+ "local"
7606
+ ).action(
7607
+ (file, raw) => runCommand(
7608
+ () => withTarget(
7609
+ raw,
7610
+ {
7611
+ local: async (ctx) => {
7612
+ const source = resolve(file);
7613
+ if (source === ctx.envFile) {
7614
+ throw new Error(`${file} is the file you would be importing into.`);
7615
+ }
7616
+ const incoming = read(source, file);
7617
+ const keys = Object.keys(incoming);
7618
+ if (keys.length === 0) return;
7619
+ p37.log.step(`Importing ${keys.length} variable(s) from ${pc15.cyan(file)}`);
7620
+ editLocalEnv(
7621
+ ctx.envFile,
7622
+ (content) => keys.reduce((acc, key) => upsertEnvVar(acc, key, incoming[key]), content)
7623
+ );
7624
+ p37.log.success(`${keys.length} variable(s) updated ${pc15.dim("(local)")}`);
7625
+ await applyLocalEnvChange(ctx, keys);
7626
+ },
7627
+ remote: async (ctx) => {
7628
+ const incoming = read(resolve(file), file);
7629
+ const keys = Object.keys(incoming);
7630
+ if (keys.length === 0) return;
7631
+ p37.log.step(`Importing ${keys.length} variable(s) from ${pc15.cyan(file)}`);
7632
+ const existing = await readRemoteEnv(ctx.session, ctx.instance);
7633
+ await writeRemoteEnv(ctx.session, ctx.instance, { ...existing, ...incoming });
7634
+ p37.log.success(`${keys.length} variable(s) updated ${pc15.dim(`(${ctx.targetName})`)}`);
7635
+ await applyEnvRestart(ctx, keys);
7636
+ }
7637
+ },
7638
+ { label: "env import" }
7639
+ ),
7640
+ "Failed to import the environment."
7641
+ )
7642
+ );
7643
+ function resolve(file) {
7644
+ return path41.resolve(process27.cwd(), file);
7645
+ }
7646
+ function read(resolved, shown) {
7647
+ if (!fs35.existsSync(resolved)) throw new Error(`${shown} does not exist.`);
7648
+ const incoming = readLocalEnvFile(resolved);
7649
+ if (Object.keys(incoming).length === 0) p37.log.info(`${shown} has no variables to import.`);
7650
+ return incoming;
7651
+ }
7652
+
7653
+ // src/commands/env.ts
7654
+ var env = new Command77("env").description("manage environment variables, locally or on a target").configureHelp(helpConfig).addCommand(envList).addCommand(envSet).addCommand(envUnset).addCommand(envImport);
7655
+
7656
+ // src/commands/status.ts
7657
+ import { Command as Command78 } from "commander";
7658
+ import * as p38 from "@clack/prompts";
7659
+ import pc16 from "picocolors";
7660
+ var status = addTargetOptions(
7661
+ new Command78("status").description("show what is deployed").configureHelp(helpConfig),
7662
+ "production"
7663
+ ).option("--all", "show every app on the server, not just this project").option("--json", "print raw JSON").action(
7664
+ (raw) => runCommand(async () => {
7665
+ const options = raw;
7666
+ if (options.all) {
7667
+ await withServerSession(raw, async (session) => {
7668
+ report2(await readInstanceStates(session), options.json);
7669
+ });
7670
+ return;
7671
+ }
7672
+ await withTarget(
7673
+ raw,
7674
+ {
7675
+ remote: async (ctx) => {
7676
+ report2(await readInstanceStates(ctx.session, ctx.instance), options.json);
7677
+ }
7678
+ },
7679
+ {
7680
+ label: "status",
7681
+ localHint: "Nothing is deployed locally \u2014 `vela dev` reports what it is running."
7682
+ }
7683
+ );
7684
+ }, "Failed to read status.")
7685
+ );
7686
+ function report2(states, json) {
7687
+ if (json) {
7688
+ console.log(JSON.stringify(states, null, 2));
7689
+ return;
7690
+ }
7691
+ if (states.length === 0) {
7692
+ p38.log.info("Nothing is deployed here yet.");
7693
+ return;
7694
+ }
7695
+ for (const state of states) {
7696
+ p38.log.info(describe(state));
7697
+ }
7698
+ }
7699
+ function describe(state) {
7700
+ const health = (value) => value === "active" ? pc16.green(value) : pc16.red(value || "inactive");
7701
+ const rows = [
7702
+ ["Instance", state.instance],
7703
+ ["Environment", state.env],
7704
+ ["Release", state.activeRelease ?? "\u2014"],
7705
+ ["Previous", state.previousRelease || "\u2014"],
7706
+ ["Domain", state.domain || "\u2014"],
7707
+ ["Ports", `web ${state.webPort ?? "?"}${state.backend ? `, pb ${state.pbPort ?? "?"}` : ""}`],
7708
+ ["App", health(state.services?.web)],
7709
+ ...state.backend ? [["PocketBase", health(state.services?.pocketbase)]] : [],
7710
+ ["Deployed", state.deployedAt ?? "\u2014"],
7711
+ ...state.rolledBackAt ? [["Rolled back", state.rolledBackAt]] : [],
7712
+ ...state.gitSha ? [["Commit", state.gitSha.slice(0, 12)]] : []
7713
+ ];
7714
+ const width = Math.max(...rows.map(([label]) => label.length));
7715
+ return pc16.cyan(state.name) + "\n\n" + rows.map(([label, value]) => ` ${label.padEnd(width)} ${value}`).join("\n");
7716
+ }
7717
+
7718
+ // src/commands/rollback.ts
7719
+ import { Command as Command79 } from "commander";
7720
+ import * as p39 from "@clack/prompts";
7721
+ import pc17 from "picocolors";
7722
+ var rollback = addTargetOptions(
7723
+ new Command79("rollback").description("put the previous release back").configureHelp(helpConfig),
7724
+ "production"
7725
+ ).option("--to <release>", "roll back to a specific release instead of the previous one").action(
7726
+ (raw) => runCommand(async () => {
7727
+ const options = raw;
7728
+ await withTarget(
7729
+ raw,
7730
+ {
7731
+ remote: async (ctx) => {
7732
+ p39.log.step(
7733
+ `Rolling back ${pc17.cyan(ctx.appName)} ${pc17.dim(`(${ctx.targetName})`)} on ${ctx.server}`
7734
+ );
7735
+ const result = await runServerScript(
7736
+ ctx.session,
7737
+ "rollback.sh",
7738
+ {
7739
+ args: [ctx.instance, ...options.to ? ["--to", options.to] : []],
7740
+ stream: true
7741
+ }
7742
+ );
7743
+ p39.log.success(
7744
+ `Rolled back to ${pc17.cyan(result?.release ?? "the previous release")}` + (result?.from ? ` ${pc17.dim(`(was ${result.from})`)}` : "")
7745
+ );
7746
+ }
7747
+ },
7748
+ {
7749
+ label: "rollback",
7750
+ localHint: "Nothing is released locally \u2014 `vela dev` always runs the working tree."
7751
+ }
7752
+ );
7753
+ }, "Failed to roll back.")
7754
+ );
7755
+
7756
+ // src/commands/logs.ts
7757
+ import { Command as Command80 } from "commander";
7758
+ var logs = addTargetOptions(
7759
+ new Command80("logs").description("tail the logs of a deployed app").configureHelp(helpConfig),
7760
+ "production"
7761
+ ).option("-f, --follow", "keep streaming new output").option("-n, --lines <count>", "how many lines of history to show", "100").option("--pocketbase", "show the PocketBase service instead of the app").action(
7762
+ (raw) => runCommand(async () => {
7763
+ const options = raw;
7764
+ await withTarget(
7765
+ raw,
7766
+ {
7767
+ remote: async (ctx) => {
7768
+ const unit = options.pocketbase ? remotePaths.pbUnit(ctx.instance) : remotePaths.webUnit(ctx.instance);
7769
+ const command = [
7770
+ "journalctl",
7771
+ "-u",
7772
+ unit,
7773
+ "-n",
7774
+ options.lines ?? "100",
7775
+ "--no-pager",
7776
+ ...options.follow ? ["-f"] : []
7777
+ ];
7778
+ const code = await ctx.session.interactive(command);
7779
+ if (code !== 0 && !options.follow) {
7780
+ throw new Error(`journalctl exited ${code}.`);
7781
+ }
7782
+ }
7783
+ },
7784
+ {
7785
+ label: "logs",
7786
+ localHint: "There are no logs for the copy on this machine \u2014 `vela dev` prints them as it runs."
7787
+ }
7788
+ );
7789
+ }, "Failed to read logs.")
7790
+ );
7791
+
7792
+ // src/commands/admin.ts
7793
+ import { Command as Command82 } from "commander";
7794
+
7795
+ // src/commands/admin/create.ts
7796
+ import process28 from "node:process";
7797
+ import { Command as Command81 } from "commander";
7798
+ import * as p40 from "@clack/prompts";
7799
+ import pc18 from "picocolors";
7800
+ var MIN_PASSWORD = 10;
7801
+ var adminCreate = addTargetOptions(
7802
+ new Command81("create").description("create a login for the admin panel").argument("[email]", "email to sign in with \u2014 prompted for when omitted").configureHelp(helpConfig),
7803
+ "local"
7804
+ ).action(
7805
+ (email3, raw) => runCommand(
7806
+ () => withTarget(
7807
+ raw,
7808
+ {
7809
+ local: async (ctx) => {
7810
+ const creds = readLocalEnv(ctx.envFile);
7811
+ const address = email3 ?? await promptEmail();
7812
+ const password10 = await promptPassword2();
7813
+ let signIn = "";
7814
+ await withPocketbase(
7815
+ ctx.workspaceRootDir,
7816
+ async (pb) => {
7817
+ await upsertSuperuser(pb, address, password10);
7818
+ const settings = await pb.settings.getAll();
7819
+ signIn = settings.meta?.appURL ?? "";
7820
+ },
7821
+ {
7822
+ email: creds.POCKETBASE_SUPERUSER_EMAIL,
7823
+ password: creds.POCKETBASE_SUPERUSER_PASSWORD
7824
+ }
7825
+ );
7826
+ if (!signIn) {
7827
+ const metadata = getPocketbaseMetadata(ctx.workspaceRootDir);
7828
+ if (metadata) signIn = `http://${metadata.viteHost}:${metadata.vitePort}`;
7829
+ }
7830
+ p40.log.info(
7831
+ signIn ? `Sign in at ${pc18.cyan(`${signIn}/admin`)}` : `Sign in at ${pc18.cyan("/admin")} once ${pc18.cyan("vela dev")} is running.`
7832
+ );
7833
+ },
7834
+ remote: async (ctx) => {
7835
+ const address = email3 ?? await promptEmail();
7836
+ const password10 = await promptPassword2();
7837
+ const [state] = await readInstanceStates(ctx.session, ctx.instance);
7838
+ await withRemotePocketbase(
7839
+ ctx.session,
7840
+ ctx.instance,
7841
+ (pb) => upsertSuperuser(pb, address, password10)
7842
+ );
7843
+ const base2 = state?.domain ? `https://${state.domain.split(",")[0].trim()}` : "";
7844
+ p40.log.info(
7845
+ base2 ? `Sign in at ${pc18.cyan(`${base2}/admin`)}` : `Sign in at ${pc18.cyan("/admin")} once a domain is configured for this target.`
7846
+ );
7847
+ }
7848
+ },
7849
+ { label: "admin create" }
7850
+ ),
7851
+ "Failed to create the admin login."
7852
+ )
7853
+ );
7854
+ async function upsertSuperuser(pb, email3, password10) {
7855
+ const existing = await findSuperuser(pb, email3);
7856
+ if (existing) {
7857
+ const confirmed = await p40.confirm({
7858
+ message: `${email3} can already sign in. Reset its password?`,
7859
+ initialValue: false
7860
+ });
7861
+ if (p40.isCancel(confirmed) || !confirmed) {
7862
+ p40.cancel("Operation cancelled.");
7863
+ process28.exit(0);
7864
+ }
7865
+ await pb.collection("_superusers").update(existing, { password: password10, passwordConfirm: password10 });
7866
+ p40.log.success(`Password reset for ${pc18.cyan(email3)}`);
7867
+ return;
7868
+ }
7869
+ await pb.collection("_superusers").create({ email: email3, password: password10, passwordConfirm: password10 });
7870
+ p40.log.success(`${pc18.cyan(email3)} can now sign in`);
7871
+ }
7872
+ async function findSuperuser(pb, email3) {
7873
+ try {
7874
+ const record = await pb.collection("_superusers").getFirstListItem(pb.filter("email = {:email}", { email: email3 }));
7875
+ return record.id;
7876
+ } catch {
7877
+ return null;
7878
+ }
7879
+ }
7880
+ async function promptEmail() {
7881
+ const value = await p40.text({
7882
+ message: "Email to sign in with",
7883
+ validate: (input) => input?.includes("@") ? void 0 : "An email address is required"
7884
+ });
7885
+ if (p40.isCancel(value)) {
7886
+ p40.cancel("Operation cancelled.");
7887
+ process28.exit(0);
7888
+ }
7889
+ return value.trim();
7890
+ }
7891
+ async function promptPassword2() {
7892
+ const value = await p40.password({
7893
+ message: "Password",
7894
+ validate: (input) => (input?.length ?? 0) >= MIN_PASSWORD ? void 0 : `At least ${MIN_PASSWORD} characters is required`
7895
+ });
7896
+ if (p40.isCancel(value)) {
7897
+ p40.cancel("Operation cancelled.");
7898
+ process28.exit(0);
7899
+ }
7900
+ const again = await p40.password({
7901
+ message: "Password again",
7902
+ validate: (input) => input === value ? void 0 : "The two do not match"
7903
+ });
7904
+ if (p40.isCancel(again)) {
7905
+ p40.cancel("Operation cancelled.");
7906
+ process28.exit(0);
7907
+ }
7908
+ return value;
7909
+ }
7910
+
7911
+ // src/commands/admin.ts
7912
+ var admin = new Command82("admin").description("manage admin panel logins").configureHelp(helpConfig).addCommand(adminCreate);
7913
+
7914
+ // src/commands/targets.ts
7915
+ import { Command as Command83 } from "commander";
7916
+ import * as p41 from "@clack/prompts";
7917
+ import pc19 from "picocolors";
7918
+ import * as v8 from "valibot";
7919
+ var OptionsSchema4 = v8.object({
7920
+ ...SSH_OPTION_SCHEMA,
7921
+ json: v8.optional(v8.boolean()),
7922
+ offline: v8.optional(v8.boolean())
7923
+ });
7924
+ var targets = addSshOptions(
7925
+ new Command83("targets").description("list the targets this project can deploy to").configureHelp(helpConfig)
7926
+ ).option("--json", "print raw JSON").option("--offline", "skip connecting to servers").action(
7927
+ (raw) => runCommand(async () => {
7928
+ const options = parseOptions(OptionsSchema4, raw);
7929
+ const { workspaceRootDir } = await getWorkspace();
7930
+ const config = await loadDeployConfig(workspaceRootDir);
7931
+ const app = readAppIdentity(workspaceRootDir, config);
7932
+ const bindings = readBindings(workspaceRootDir);
7933
+ const metadata = getPocketbaseMetadata(workspaceRootDir);
7934
+ const rows = [
7935
+ {
7936
+ target: "local",
7937
+ kind: "local",
7938
+ server: "\u2014",
7939
+ domain: metadata ? `http://${metadata.viteHost}:${metadata.vitePort}` : "\u2014",
7940
+ release: "\u2014",
7941
+ reachable: true
7942
+ }
7943
+ ];
7944
+ const byServer = /* @__PURE__ */ new Map();
7945
+ for (const [envTag, binding] of Object.entries(bindings)) {
7946
+ byServer.set(binding.server, [...byServer.get(binding.server) ?? [], envTag]);
7947
+ }
7948
+ for (const [server, envTags] of byServer) {
7949
+ let states = [];
7950
+ let reachable = false;
7951
+ if (!options.offline && app) {
7952
+ try {
7953
+ await withSsh(server, sshOptionsFrom(options), async (session) => {
7954
+ await session.detectElevation();
7955
+ states = await readInstanceStates(session);
7956
+ });
7957
+ reachable = true;
7958
+ } catch {
7959
+ }
7960
+ }
7961
+ for (const envTag of envTags) {
7962
+ const binding = bindings[envTag];
7963
+ const instance = app ? instanceId(app.appId, envTag) : "";
7964
+ const state = states.find((candidate) => candidate.instance === instance);
7965
+ rows.push({
7966
+ target: envTag,
7967
+ kind: "remote",
7968
+ server,
7969
+ domain: state?.domain || binding.domain || "\u2014",
7970
+ release: state?.activeRelease ?? (reachable ? "not deployed" : "\u2014"),
7971
+ reachable
7972
+ });
7973
+ }
7974
+ }
7975
+ if (options.json) {
7976
+ console.log(JSON.stringify(rows, null, 2));
7977
+ return;
7978
+ }
7979
+ report3(rows, options.offline === true);
7980
+ }, "Failed to list targets.")
7981
+ );
7982
+ function report3(rows, offline) {
7983
+ const width = (heading, pick) => Math.max(heading.length, ...rows.map((row) => pick(row).length));
7984
+ const target = width("TARGET", (row) => row.target);
7985
+ const server = width("SERVER", (row) => row.server);
7986
+ const domain = width("DOMAIN", (row) => row.domain);
7987
+ const header = `${"TARGET".padEnd(target)} ${"SERVER".padEnd(server)} ${"DOMAIN".padEnd(domain)} RELEASE`;
7988
+ const lines = rows.map(
7989
+ (row) => `${row.kind === "local" ? pc19.dim(row.target.padEnd(target)) : pc19.cyan(row.target.padEnd(target))} ${row.server.padEnd(server)} ${row.domain.padEnd(domain)} ${row.release}`
7990
+ );
7991
+ p41.log.info(`${pc19.dim(header)}
7992
+ ${lines.join("\n")}`);
7993
+ const unreachable = rows.filter((row) => row.kind === "remote" && !row.reachable);
7994
+ if (!offline && unreachable.length > 0) {
7995
+ p41.log.warn(
7996
+ `Could not reach ${unreachable.map((row) => row.server).join(", ")}.
7997
+ Release and domain are shown from what this project recorded.`
7998
+ );
7999
+ }
5984
8000
  }
5985
8001
 
5986
8002
  // src/commands/test.ts
5987
- import path34 from "node:path";
5988
- import process21 from "node:process";
5989
- import { Command as Command70 } from "commander";
5990
- import PocketBase3 from "pocketbase";
5991
- import pc6 from "picocolors";
8003
+ import path42 from "node:path";
8004
+ import process29 from "node:process";
8005
+ import { Command as Command84 } from "commander";
8006
+ import PocketBase5 from "pocketbase";
8007
+ import pc20 from "picocolors";
5992
8008
  import { x as x5 } from "tinyexec";
5993
- import { detect as detect7 } from "package-manager-detector";
5994
- import { resolveCommand as resolveCommand7 } from "package-manager-detector/commands";
5995
- import fs28 from "node:fs";
5996
- var testServer = new Command70("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
5997
- const cwd = process21.cwd();
8009
+ import { detect as detect8 } from "package-manager-detector";
8010
+ import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
8011
+ import fs36 from "node:fs";
8012
+ var testServer = new Command84("test:server").description("run server tests").allowUnknownOption(true).allowExcessArguments(true).configureHelp(helpConfig).action(async (_opts, cmd) => {
8013
+ const cwd = process29.cwd();
5998
8014
  const email3 = `test-${Math.random().toString(36).slice(2)}@example.com`;
5999
- const password8 = "password";
6000
- const testDataDir = path34.join(cwd, "test-data");
6001
- fs28.rmSync(testDataDir, { recursive: true, force: true });
8015
+ const password10 = "password";
8016
+ const testDataDir = path42.join(cwd, "test-data");
8017
+ fs36.rmSync(testDataDir, { recursive: true, force: true });
6002
8018
  const { stop, url } = await launchPocketbase(cwd, {
6003
8019
  dir: testDataDir,
6004
- migrationsDir: path34.join(cwd, "migrations"),
8020
+ migrationsDir: path42.join(cwd, "migrations"),
6005
8021
  email: email3,
6006
- password: password8
8022
+ password: password10
6007
8023
  });
6008
- process21.env.POCKETBASE_URL = url;
6009
- process21.env.POCKETBASE_SUPERUSER_EMAIL = email3;
6010
- process21.env.POCKETBASE_SUPERUSER_PASSWORD = password8;
6011
- process21.env.TEST = "true";
6012
- console.log(`${pc6.greenBright("\u2713")} Created test database`);
6013
- const { createServer } = await import("vite");
8024
+ process29.env.POCKETBASE_URL = url;
8025
+ process29.env.POCKETBASE_SUPERUSER_EMAIL = email3;
8026
+ process29.env.POCKETBASE_SUPERUSER_PASSWORD = password10;
8027
+ process29.env.TEST = "true";
8028
+ console.log(`${pc20.greenBright("\u2713")} Created test database`);
8029
+ const { createServer } = await loadVite(cwd);
6014
8030
  const vite = await createServer({
6015
8031
  mode: "test",
6016
8032
  plugins: [stubPagesPlugin()],
@@ -6018,29 +8034,29 @@ var testServer = new Command70("test:server").description("run server tests").al
6018
8034
  });
6019
8035
  const vitePort = await findFreePort();
6020
8036
  await vite.listen(vitePort);
6021
- process21.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
6022
- console.log(`${pc6.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
6023
- console.log(`${pc6.greenBright("\u2713")} Started PocketBase: ${url}`);
8037
+ process29.env.VITE_TEST_URL = `http://localhost:${vitePort}`;
8038
+ console.log(`${pc20.greenBright("\u2713")} Started Vite: http://localhost:${vitePort}`);
8039
+ console.log(`${pc20.greenBright("\u2713")} Started PocketBase: ${url}`);
6024
8040
  const cleanup = async () => {
6025
8041
  stop();
6026
8042
  await vite.close();
6027
- fs28.rmSync(testDataDir, { recursive: true, force: true });
8043
+ fs36.rmSync(testDataDir, { recursive: true, force: true });
6028
8044
  };
6029
- const pb = new PocketBase3(url);
8045
+ const pb = new PocketBase5(url);
6030
8046
  try {
6031
- await authWithRetries(pb, email3, password8);
8047
+ await authWithRetries(pb, email3, password10);
6032
8048
  } catch (e) {
6033
8049
  await cleanup();
6034
- console.error(`${pc6.redBright("\u2717")} Auth failed: ${e.message}`);
6035
- process21.exit(1);
8050
+ console.error(`${pc20.redBright("\u2717")} Auth failed: ${e.message}`);
8051
+ process29.exit(1);
6036
8052
  }
6037
8053
  const extraArgs = (cmd.parent?.args ?? []).slice(1);
6038
8054
  let filter = extraArgs.find((arg) => !arg.startsWith("-"));
6039
8055
  const passthrough = filter ? extraArgs.filter((a) => a !== filter) : extraArgs;
6040
8056
  if (!filter) filter = "server";
6041
8057
  try {
6042
- const pm = (await detect7({ cwd }))?.name ?? "npm";
6043
- const resolved = resolveCommand7(pm, "execute", [
8058
+ const pm = (await detect8({ cwd }))?.name ?? "npm";
8059
+ const resolved = resolveCommand8(pm, "execute", [
6044
8060
  "vitest",
6045
8061
  "run",
6046
8062
  filter,
@@ -6050,7 +8066,7 @@ var testServer = new Command70("test:server").description("run server tests").al
6050
8066
  const resolvedArgs = resolved.args.slice();
6051
8067
  if (pm === "npm") resolvedArgs.unshift("--yes");
6052
8068
  await x5(resolved.command, resolvedArgs, {
6053
- nodeOptions: { cwd, stdio: "inherit", env: { ...process21.env, CI: "1" } },
8069
+ nodeOptions: { cwd, stdio: "inherit", env: { ...process29.env, CI: "1" } },
6054
8070
  throwOnError: true
6055
8071
  });
6056
8072
  } catch {
@@ -6076,9 +8092,9 @@ function stubPagesPlugin() {
6076
8092
  }
6077
8093
 
6078
8094
  // src/commands/routes.ts
6079
- import fs29 from "node:fs";
6080
- import path35 from "node:path";
6081
- import { Command as Command71 } from "commander";
8095
+ import fs37 from "node:fs";
8096
+ import path43 from "node:path";
8097
+ import { Command as Command85 } from "commander";
6082
8098
  var HTTP_METHODS = /* @__PURE__ */ new Set([
6083
8099
  "GET",
6084
8100
  "POST",
@@ -6089,32 +8105,32 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
6089
8105
  "HEAD",
6090
8106
  "fallback"
6091
8107
  ]);
6092
- var routes = new Command71("routes").description("list routes").configureHelp(helpConfig).action(async () => {
8108
+ var routes = new Command85("routes").description("list routes").configureHelp(helpConfig).action(async () => {
6093
8109
  const { workspaceRootDir, routesDir } = await getWorkspace();
6094
- const routesRoot = path35.join(workspaceRootDir, routesDir);
8110
+ const routesRoot = path43.join(workspaceRootDir, routesDir);
6095
8111
  const found = walk(routesRoot, routesRoot).filter((r) => r.methods.length > 0);
6096
8112
  found.sort((a, b) => a.urlPattern.localeCompare(b.urlPattern));
6097
8113
  printTable(found);
6098
8114
  });
6099
8115
  function walk(root, dir) {
6100
- const entries = fs29.readdirSync(dir, { withFileTypes: true });
8116
+ const entries = fs37.readdirSync(dir, { withFileTypes: true });
6101
8117
  const routes2 = [];
6102
8118
  const hasLeaf = entries.some((e) => e.isFile() && isRouteFile(e.name));
6103
8119
  if (hasLeaf) {
6104
- const id = "/" + path35.relative(root, dir).split(path35.sep).filter(Boolean).join("/");
8120
+ const id = "/" + path43.relative(root, dir).split(path43.sep).filter(Boolean).join("/");
6105
8121
  const urlPattern = id.replace(/\([^)]+\)\/?/g, "").replace(/\/$/, "") || "/";
6106
8122
  const methods = /* @__PURE__ */ new Set();
6107
8123
  for (const entry of entries) {
6108
8124
  if (!entry.isFile()) continue;
6109
8125
  if (entry.name.endsWith("+page.svelte")) methods.add("GET");
6110
8126
  if (entry.name.endsWith("+server.ts") || entry.name.endsWith("+server.js") || entry.name.endsWith("+page.server.ts") || entry.name.endsWith("+page.server.js")) {
6111
- extractMethods(path35.join(dir, entry.name)).forEach((m) => methods.add(m));
8127
+ extractMethods(path43.join(dir, entry.name)).forEach((m) => methods.add(m));
6112
8128
  }
6113
8129
  }
6114
8130
  routes2.push({ id: id || "/", urlPattern, methods: [...methods] });
6115
8131
  }
6116
8132
  for (const entry of entries) {
6117
- if (entry.isDirectory()) routes2.push(...walk(root, path35.join(dir, entry.name)));
8133
+ if (entry.isDirectory()) routes2.push(...walk(root, path43.join(dir, entry.name)));
6118
8134
  }
6119
8135
  return routes2;
6120
8136
  }
@@ -6123,7 +8139,7 @@ function isRouteFile(name) {
6123
8139
  }
6124
8140
  function extractMethods(file) {
6125
8141
  try {
6126
- const content = fs29.readFileSync(file, "utf8");
8142
+ const content = fs37.readFileSync(file, "utf8");
6127
8143
  const methods = [];
6128
8144
  const exportRegex = /export\s+(?:const|async\s+function|function)\s+(\w+)/g;
6129
8145
  let match;
@@ -6164,15 +8180,15 @@ function printTable(routes2) {
6164
8180
  }
6165
8181
 
6166
8182
  // src/commands/i18n.ts
6167
- import process22 from "node:process";
6168
- import { Command as Command72 } from "commander";
8183
+ import process30 from "node:process";
8184
+ import { Command as Command86 } from "commander";
6169
8185
  import { x as x6 } from "tinyexec";
6170
- import { detect as detect8 } from "package-manager-detector";
6171
- import { resolveCommand as resolveCommand8 } from "package-manager-detector/commands";
8186
+ import { detect as detect9 } from "package-manager-detector";
8187
+ import { resolveCommand as resolveCommand9 } from "package-manager-detector/commands";
6172
8188
  async function runWuchale(extraArgs) {
6173
- const cwd = process22.cwd();
6174
- const pm = (await detect8({ cwd }))?.name ?? "npm";
6175
- const resolved = resolveCommand8(pm, "execute", ["wuchale", ...extraArgs]);
8189
+ const cwd = process30.cwd();
8190
+ const pm = (await detect9({ cwd }))?.name ?? "npm";
8191
+ const resolved = resolveCommand9(pm, "execute", ["wuchale", ...extraArgs]);
6176
8192
  const args = resolved.args.slice();
6177
8193
  if (pm === "npm") args.unshift("--yes");
6178
8194
  await x6(resolved.command, args, {
@@ -6180,11 +8196,11 @@ async function runWuchale(extraArgs) {
6180
8196
  throwOnError: true
6181
8197
  });
6182
8198
  }
6183
- var extract = new Command72("extract").description("extract translatable strings").configureHelp(helpConfig).action(() => runWuchale([]));
6184
- var watch = new Command72("watch").description("watch and extract translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--watch"]));
6185
- var status = new Command72("status").description("show i18n status").configureHelp(helpConfig).action(() => runWuchale(["status"]));
6186
- var clean = new Command72("clean").description("clean unused translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--clean"]));
6187
- var i18n3 = new Command72("i18n").description("i18n utilities").configureHelp(helpConfig).addCommand(extract, { isDefault: true }).addCommand(watch).addCommand(status).addCommand(clean);
8199
+ var extract = new Command86("extract").description("extract translatable strings").configureHelp(helpConfig).action(() => runWuchale([]));
8200
+ var watch = new Command86("watch").description("watch and extract translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--watch"]));
8201
+ var status2 = new Command86("status").description("show i18n status").configureHelp(helpConfig).action(() => runWuchale(["status"]));
8202
+ var clean = new Command86("clean").description("clean unused translatable strings").configureHelp(helpConfig).action(() => runWuchale(["--clean"]));
8203
+ var i18n3 = new Command86("i18n").description("i18n utilities").configureHelp(helpConfig).addCommand(extract, { isDefault: true }).addCommand(watch).addCommand(status2).addCommand(clean);
6188
8204
 
6189
8205
  // src/commands/oauth.ts
6190
8206
  var oauth = stubCommand("oauth", "configure OAuth providers");
@@ -6205,42 +8221,52 @@ var NO_BACKEND_COMMMANDS = /* @__PURE__ */ new Set([
6205
8221
  "routes",
6206
8222
  "i18n",
6207
8223
  "generate schema",
6208
- "generate form"
8224
+ "generate form",
8225
+ // Server commands talk to a VPS over SSH, never to the local database.
8226
+ "provision",
8227
+ "env",
8228
+ "status",
8229
+ "rollback",
8230
+ "logs",
8231
+ "admin",
8232
+ "targets",
8233
+ "link"
6209
8234
  ]);
6210
8235
  var BACKEND_OPTIONAL_COMMANDS = /* @__PURE__ */ new Set(["dev", "build", "preview", "deploy"]);
6211
- var program = new Command73().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
8236
+ var program = new Command87().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
6212
8237
  program.hook("preAction", (_thisCommand, actionCommand) => {
6213
8238
  if (isStub(actionCommand)) return;
6214
- dotenv.config({ quiet: true });
6215
- const path36 = getCommandPath(actionCommand);
6216
- if (NO_BACKEND_COMMMANDS.has(path36)) return;
6217
- const top = path36.split(" ", 1)[0];
8239
+ const envRoot = findWorkspaceRoot() ?? process31.cwd();
8240
+ dotenv2.config({ path: nodePath.join(envRoot, ".env"), quiet: true });
8241
+ const path44 = getCommandPath(actionCommand);
8242
+ if (NO_BACKEND_COMMMANDS.has(path44)) return;
8243
+ const top = path44.split(" ", 1)[0];
6218
8244
  if (NO_BACKEND_COMMMANDS.has(top)) return;
6219
8245
  if (!hasBackend()) {
6220
8246
  if (BACKEND_OPTIONAL_COMMANDS.has(top)) return;
6221
- p29.log.error(
6222
- `${pc7.cyan(`vela ${path36}`)} needs a backend, and this project does not have one.
8247
+ p42.log.error(
8248
+ `${pc21.cyan(`vela ${path44}`)} needs a backend, and this project does not have one.
6223
8249
 
6224
8250
  Static projects have no database to talk to.
6225
8251
 
6226
- To add a backend to this project, run ${pc7.cyan("vela bless")}.`
8252
+ To add a backend to this project, run ${pc21.cyan("vela bless")}.`
6227
8253
  );
6228
- p29.log.message();
6229
- p29.cancel("Operation failed.");
6230
- process23.exit(1);
8254
+ p42.log.message();
8255
+ p42.cancel("Operation failed.");
8256
+ process31.exit(1);
6231
8257
  }
6232
- if (!process23.env.POCKETBASE_SUPERUSER_EMAIL || !process23.env.POCKETBASE_SUPERUSER_PASSWORD) {
6233
- p29.log.error(
8258
+ if (!process31.env.POCKETBASE_SUPERUSER_EMAIL || !process31.env.POCKETBASE_SUPERUSER_PASSWORD) {
8259
+ p42.log.error(
6234
8260
  `PocketBase superuser credentials are required.
6235
8261
 
6236
- Set ${pc7.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc7.cyan("POCKETBASE_SUPERUSER_PASSWORD")} in your .env file.
8262
+ Set ${pc21.cyan("POCKETBASE_SUPERUSER_EMAIL")} and ${pc21.cyan("POCKETBASE_SUPERUSER_PASSWORD")} in your .env file.
6237
8263
 
6238
- To set up a new project, run ${pc7.cyan("vela create")}.
6239
- To set up an existing project, run ${pc7.cyan("vela bless")}.`
8264
+ To set up a new project, run ${pc21.cyan("vela create")}.
8265
+ To set up an existing project, run ${pc21.cyan("vela bless")}.`
6240
8266
  );
6241
- p29.log.message();
6242
- p29.cancel("Operation failed.");
6243
- process23.exit(1);
8267
+ p42.log.message();
8268
+ p42.cancel("Operation failed.");
8269
+ process31.exit(1);
6244
8270
  }
6245
8271
  });
6246
8272
  function getCommandPath(cmd) {
@@ -6274,6 +8300,13 @@ for (const command of [
6274
8300
  sync,
6275
8301
  provision,
6276
8302
  deploy,
8303
+ rollback,
8304
+ status,
8305
+ logs,
8306
+ env,
8307
+ admin,
8308
+ targets,
8309
+ link,
6277
8310
  testServer,
6278
8311
  routes,
6279
8312
  i18n3,
@@ -6284,14 +8317,14 @@ for (const command of [
6284
8317
  }
6285
8318
 
6286
8319
  // src/bin.ts
6287
- var argv = normalizeArgv(process24.argv.slice(2));
8320
+ var argv = normalizeArgv(process32.argv.slice(2));
6288
8321
  var delegatedExitCode = delegateToLocalCli({
6289
8322
  argv,
6290
8323
  selfPath: fileURLToPath2(import.meta.url),
6291
8324
  selfVersion: package_default.version
6292
8325
  });
6293
8326
  if (delegatedExitCode !== null) {
6294
- process24.exit(delegatedExitCode);
8327
+ process32.exit(delegatedExitCode);
6295
8328
  }
6296
8329
  program.parse(argv, { from: "user" });
6297
8330
  //# sourceMappingURL=bin.js.map